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机制,测试身份验证是否正常,结果如下