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機制,測試身份驗證是否正常,結果如下

