webapi框架搭建-安全機制(二)-身份驗證

webapi框架搭建系列博客

  身份驗證(authentication)的責任是識別出http請求者的身份,除此以外儘可能不要管其它的事。webapi的authentication我用authentication filter技術去解決。html

參考資料:git

  https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/authentication-filtersgithub

步驟以下web

建立authentication filter

  在項目裏新建文件夾Security,並在此文件夾裏建立IdentityBasicAuthentication類,代碼以下json

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;

namespace webapi.Security
{
    public class IdentityBasicAuthentication:IAuthenticationFilter
    {
        public bool AllowMultiple { get; }
        public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }
    }
}

  繼承自IauthenticationFilter,實現本身的業務代碼(後面再實現)api

註冊authentication filter

  在webapi的config里加入filter,修改項目代碼以下框架

 /// <summary>
        /// 返回webapi的httpconfiguration配置
        /// 用於webapi應用於owin技術時使用
        /// </summary>
        /// <returns></returns>
        public static HttpConfiguration OwinWebApiConfiguration(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();//開啓屬性路由
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            config.Filters.Add(new WebApiExceptionFilterAttribute());
            config.Filters.Add(new IdentityBasicAuthentication());
            return config;
        }

  即上一句:config.Filters.Add(new IdentityBasicAuthentication());ide

 

如今webapi的authentication機制的結構已經完成,剩下要作的就是在結構裏填其它的代碼了。post

加入JWT機制

  其實jwt的代碼咱們要寫的不多,不要本身去實現jwt規範,能夠用已經有的jwt的.net包。測試

在nuget裏添加jwt.net包

 

修改authentication filter代碼,加入jwt邏輯

using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
using System.Web.Http.Results;
using webapi.Common;
using webapi.Configs;

namespace webapi.Security
{
    public class IdentityBasicAuthentication:IAuthenticationFilter
    {
        public bool AllowMultiple { get; }
        /// <summary>
        /// 請求先通過AuthenticateAsync
        /// </summary>
        /// <param name="context"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
        {
            // 一、獲取token
            context.Request.Headers.TryGetValues("token", out var tokenHeaders);
            // 二、若是沒有token,不作任何處理
            if (tokenHeaders == null || !tokenHeaders.Any())
            {
                return Task.FromResult(0);
            }
            // 三、若是token驗證經過,則寫入到identity,若是未經過則設置錯誤
            var jwtHelper=new JWTHelper();
            var payLoadClaims=jwtHelper.DecodeToObject(tokenHeaders.FirstOrDefault(),Config.JWTKey, out bool isValid, out string errMsg);
            if (isValid)
            {
                var identity = new ClaimsIdentity("jwt", "userId", "roles");//只要ClaimsIdentity設置了authenticationType,authenticated就爲true,後面的authority根據authenticated=true來作權限
                foreach (var keyValuePair in payLoadClaims)
                {
                    identity.AddClaim(new Claim(keyValuePair.Key, keyValuePair.Value.ToString()));
                }
                // 最好是http上下文的principal和進程的currentPrincipal都設置
                context.Principal = new ClaimsPrincipal(identity);
                Thread.CurrentPrincipal = new ClaimsPrincipal(identity);
            }
            else
            {
                context.ErrorResult = new ResponseMessageResult(new HttpResponseMessage()
                {
                    StatusCode = HttpStatusCode.ProxyAuthenticationRequired,
                    Content = new StringContent(errMsg)
                });
            }
            return Task.FromResult(0);
        }

        /// <summary>
        /// 請求後通過AuthenticateAsync
        /// </summary>
        /// <param name="context"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
        {
            return Task.FromResult(0);
        }
    }
}

 

附上JWTHelper.cs代碼

JWT.net的用法參考:https://github.com/jwt-dotnet/jwt

using JWT;
using JWT.Algorithms;
using JWT.Serializers;
using System;
using System.Collections.Generic;

namespace webapi.Common
{
    public class JWTHelper
    {
        private IJsonSerializer _jsonSerializer;
        private IDateTimeProvider _dateTimeProvider;
        private IJwtValidator _jwtValidator;
        private IBase64UrlEncoder _base64UrlEncoder;
        private IJwtAlgorithm _jwtAlgorithm;
        private IJwtDecoder _jwtDecoder;
        private IJwtEncoder _jwtEncoder;
        public JWTHelper()
        {
            //非fluent寫法
            this._jsonSerializer = new JsonNetSerializer();
            this._dateTimeProvider = new UtcDateTimeProvider();
            this._jwtValidator = new JwtValidator(_jsonSerializer, _dateTimeProvider);
            this._base64UrlEncoder = new JwtBase64UrlEncoder();
            this._jwtAlgorithm = new HMACSHA256Algorithm();
            this._jwtDecoder = new JwtDecoder(_jsonSerializer, _jwtValidator, _base64UrlEncoder);
            this._jwtEncoder = new JwtEncoder(_jwtAlgorithm, _jsonSerializer, _base64UrlEncoder);


        }
        public string Decode(string token, string key, out bool isValid, out string errMsg)
        {
            isValid = false;
            var result = string.Empty;
            try
            {
                result = _jwtDecoder.Decode(token, key, true);
                isValid = true;
                errMsg = "正確的token";
                return result;
            }
            catch (TokenExpiredException)
            {
                errMsg = "token過時";
                return result;
            }
            catch (SignatureVerificationException)
            {
                errMsg = "簽名無效";
                return result;
            }
            catch (Exception)
            {
                errMsg = "token無效";
                return result;
            }
        }

        public T DecodeToObject<T>(string token, string key, out bool isValid, out string errMsg)
        {
            isValid = false;
            try
            {
                var result = _jwtDecoder.DecodeToObject<T>(token, key, true);
                isValid = true;
                errMsg = "正確的token";
                return result;
            }
            catch (TokenExpiredException)
            {
                errMsg = "token過時";
                return default(T);
            }
            catch (SignatureVerificationException)
            {
                errMsg = "簽名無效";
                return default(T);
            }
            catch (Exception)
            {
                errMsg = "token無效";
                return default(T);
            }
        }

        public IDictionary<string, object> DecodeToObject(string token, string key, out bool isValid, out string errMsg)
        {
            isValid = false;
            try
            {
                var result = _jwtDecoder.DecodeToObject(token, key, true);
                isValid = true;
                errMsg = "正確的token";
                return result;
            }
            catch (TokenExpiredException)
            {
                errMsg = "token過時";
                return null;
            }
            catch (SignatureVerificationException)
            {
                errMsg = "簽名無效";
                return null;
            }
            catch (Exception)
            {
                errMsg = "token無效";
                return null;
            }
        }

        #region 解密 
        public string Encode(Dictionary<string, object> payload, string key, int expiredMinute = 30)
        {
            if (!payload.ContainsKey("exp"))
            {
                var exp = Math.Round((_dateTimeProvider.GetNow().AddMinutes(expiredMinute) - new DateTime(1970, 1, 1)).TotalSeconds);
                payload.Add("exp", exp);
            }
            return _jwtEncoder.Encode(payload, key);
        }
        #endregion

    }
}

  

測試結果

建立SecurityTestController.cs控制器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Web.Http;
using webapi.Common;

namespace webapi.example
{
    [RoutePrefix("api/security")]
    public class SecurityTestController : ApiController
    {
        /// <summary>
        /// 經過get請求裏傳過來的值生成token
        /// </summary>
        /// <returns></returns>
        [Route("token"),HttpGet]
        public IHttpActionResult GetToken()
        {
            var dic=new Dictionary<string,object>();
            foreach (var queryNameValuePair in Request.GetQueryNameValuePairs())
            {
                dic.Add(queryNameValuePair.Key,queryNameValuePair.Value);
            }
            var token=new JWTHelper().Encode(dic, "shengyu",30);
            return Ok(token);
        }

        /// <summary>
        /// 返回token里加密的信息
        /// </summary>
        /// <returns></returns>
        [Route("GetUserInfoFromToken"),HttpGet]
        public IHttpActionResult GetUser()
        {
            var user = (ClaimsPrincipal)User;
            var dic=new Dictionary<string,object>();
            foreach (var userClaim in user.Claims)
            {
                dic.Add(userClaim.Type,userClaim.Value);
            }
            return Ok(dic);
        }
    }
    
}

 

獲取一個測試token

 

如今將上面生成的token以附加到http request的head裏,經過authentication機制,測試身份驗證是否正常,結果以下

相關文章
相關標籤/搜索