webapi框架搭建-安全机制(二)-身份验证

webapi框架搭建系列博客

  身份验证(authentication)的责任是识别出http请求者的身份,除此之外尽量不要管其它的事。webapi的authentication我用authentication filter技术去解决。

参考资料:

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

步骤如下

创建authentication filter

  在项目里新建文件夹Security,并在此文件夹里创建IdentityBasicAuthentication类,代码如下

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,实现自己的业务代码(后面再实现)

注册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());

 

现在webapi的authentication机制的结构已经完成,剩下要做的就是在结构里填其它的代码了。

加入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)
        {
            // 1、获取token
            context.Request.Headers.TryGetValues("token", out var tokenHeaders);
            // 2、如果没有token,不做任何处理
            if (tokenHeaders == null || !tokenHeaders.Any())
            {
                return Task.FromResult(0);
            }
            // 3、如果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机制,测试身份验证是否正常,结果如下

 

posted @ 2018-01-14 16:45  shengyu_kmust  阅读(8338)  评论(4编辑  收藏  举报