webapi中使⽤token验证(JWT验证)
本⽂介绍如何在webapi中使⽤JWT验证
1. 准备
安装JWT安装包 System.IdentityModel.Tokens.Jwt
你的前端api登录请求的⽅法,参考
<("api/token?username=cuong&password=1").then(function (res) {
// 返回⼀个token
/*
token⽰例如下
"J1bmlxdWVfbmFtZSI6Inllamlhd2VpIiwibmJmIjoxNTE0NjQyNTA0LCJleHAiOjE1MTQ2NDk3MDQsImlhdCI6MTUxNDY0MjUwNH0.ur97ZRviC        */
}).catch(function (err) {
console.log(err);
})
你的前端请求后端数据执⾏的任意⽅法,传递token,参考
var axiosInstance = ate({
headers: {
common: {
Authorization: "J1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNTE0NjE4MDgzLCJleHAiOjE1MTQ2MjUyODMsImlhdCI6MTUxNDYxO            }
/*
上⾯Authorization会⾃动映射成后端request.Headers.Authorization对象
{
Parameter: "J1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNTE0NjE4MDgzLCJleHAiOjE1MTQ2MjUyODMsImlhdCI6MTUxNDYxODA4M3                    Scheme: "Bearer"
}
*/
}
})
<("api/value").then(function (res) {
}).catch(function (err) {
console.log(err);
})
2. 创建TokenHelper类
在项⽬跟⽬录下创建⼀个TokenHelper.cs类,代码如下
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
namespace TokenTest
{
public class TokenHelper
{
/// <summary>
/// Use the below code to generate symmetric Secret Key
/
//    var hmac = new HMACSHA256();
///    var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 120)
{ // 此⽅法⽤来⽣成 Token
var symmetricKey = Convert.FromBase64String(Secret);  // ⽣成⼆进制字节数组
var tokenHandler = new JwtSecurityTokenHandler(); // 创建⼀个JwtSecurityTokenHandler类⽤来⽣成Token
var now = DateTime.UtcNow; // 获取当前时间
var tokenDescriptor = new SecurityTokenDescriptor // 创建⼀个 Token 的原始对象
{
Subject = new ClaimsIdentity(new[] // Token的⾝份证,类似⼀个⼈可以有⾝份证,户⼝本
{
new Claim(ClaimTypes.Name, username) // 可以创建多个
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)), // Token 有效期
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(symmetricKey), SecurityAlgorithms.HmacSha256)
// ⽣成⼀个Token证书,第⼀个参数是根据预先的⼆进制字节数组⽣成⼀个安全秘钥,说⽩了就是密码,第⼆个参数是编码⽅式
};
var stoken = tokenHandler.CreateToken(tokenDescriptor); // ⽣成⼀个编码后的token对象实例
var token = tokenHandler.WriteToken(stoken); // ⽣成token字符串,给前端使⽤
return token;
}
public static ClaimsPrincipal GetPrincipal(string token)
{ // 此⽅法⽤解码字符串token,并返回秘钥的信息对象
try
{
var tokenHandler = new JwtSecurityTokenHandler(); // 创建⼀个JwtSecurityTokenHandler类,⽤来后续操作
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken; // 将字符串token解码成token对象
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret); // ⽣成编码对应的字节数组
var validationParameters = new TokenValidationParameters() // ⽣成验证token的参数
{
RequireExpirationTime = true, // token是否包含有效期
ValidateIssuer = false, // 验证秘钥发⾏⼈,如果要验证在这⾥指定发⾏⼈字符串即可
ValidateAudience = false, // 验证秘钥的接受⼈,如果要验证在这⾥提供接收⼈字符串即可
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey) // ⽣成token时的安全秘钥
};
SecurityToken securityToken; // 接受解码后的token对象
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal; // 返回秘钥的主体对象,包含秘钥的所有相关信息
}
catch (Exception ex)
{
return null;
}
}
}
}
3. 创建过滤器类
当前端发送⼀个请求,需要接收并处理token
在当前项⽬下创建⼀个名为Filter的⽂件夹
创建⼀个AuthenticationAttribute类,代码如下
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.Filters;
namespace TokenTest.Filter
{
/
/ IAuthenticationFilter⽤来⾃定义⼀个webapi控制器⽅法属性
public class AuthenticationAttribute : Attribute, IAuthenticationFilter
{
public bool AllowMultiple => false;
public string Realm { get; set; }
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
// 当api发送请求,⾃动调⽤这个⽅法
var request = context.Request; // 获取请求的请求体
var authorization = request.Headers.Authorization; // 获取请求的token对象
if (authorization == null || authorization.Scheme != "Bearer") return;
if(string.IsNullOrEmpty(authorization.Parameter))
{
// 给ErrorResult赋值需要⼀个类实现了IHttpActionResult接⼝
// 此类声明在AuthenticationFailureResult.cs⽂件中,此⽂件⽤来处理错误信息。
context.ErrorResult = new AuthenticationFailureResult("Missing Jwt Token", request);
return;
}
var token = authorization.Parameter; // 获取token字符串
var principal = await AuthenticateJwtToken(token); // 调⽤此⽅法,根据token⽣成对应的"⾝份证持有⼈"
if(principal == null)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
}
else
{
context.Principal = principal; // 设置⾝份验证的主体
}
// 此法调⽤完毕后,会调⽤ChallengeAsync⽅法,从⽽来完成WWW-Authenticate验证
}
private Task<IPrincipal> AuthenticateJwtToken(string token)
{
string userName;
if(ValidateToken(token, out userName))
{
// 这⾥就是验证成功后要做的逻辑,也就是处理WWW-Authenticate验证
var info = new List<Claim>
{
new Claim(ClaimTypes.Name, userName)
}; // 根据验证token后获取的⽤户名重新在建⼀个声明,你个可以在这⾥创建多个声明
// 作者注: claims就像你⾝份证上⾯的信息,⼀个Claim就是⼀条信息,将这些信息放在ClaimsIdentity就构成⾝份证了                    var infos = new ClaimsIdentity(info, "Jwt");
// 将上⾯的⾝份证放在ClaimsPrincipal⾥⾯,相当于把⾝份证给持有⼈
IPrincipal user = new ClaimsPrincipal(infos);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
private bool ValidateToken(string token, out string userName)
{
userName = null;
var simplePrinciple = TokenHelper.GetPrincipal(token); // 调⽤⾃定义的GetPrincipal获取Token的信息对象
var identity = simplePrinciple?.Identity as ClaimsIdentity; // 获取主声明标识
if (identity == null) return false;
if (!identity.IsAuthenticated) return false;
var userNameClaim = identity.FindFirst(ClaimTypes.Name); // 获取声明类型是ClaimTypes.Name的第⼀个声明
userName = userNameClaim?.Value; // 获取声明的名字,也就是⽤户名
if (string.IsNullOrEmpty(userName)) return false;
return true;
// 到这⾥token本⾝的验证⼯作已经完成了,因为⽤户名可以解码出来
// 后续要验证的就是浏览器的 WWW-Authenticate
/*
什么是WWW-Authenticate验证
WWW-Authenticate是早期的⼀种验证⽅式,很容易被破解,浏览器发送请求给后端,后端服务器会解析传过来的Header验证如果没有类似于本⽂格式的token,那么会发送WWW-Authenticate: Basic realm= "." 到前端浏览器,并返回401                */
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
// 此⽅法在AuthenticateAsync⽅法调⽤完成之后⾃动调⽤
ChallengeAsync(context);
return Task.FromResult(0);
}
private void ChallengeAsync(HttpAuthenticationChallengeContext context)
{
string parameter = null;
if (!string.IsNullOrEmpty(Realm))
{
parameter = "realm=\"" + Realm + "\"";
} // token的parameter部分已经通过jwt验证成功,这⾥只需要验证scheme即可
context.ChallengeWith("Bearer", parameter); // 这个⾃定义扩展⽅法定义在HttpAuthenticationChallengeContextExtensions.cs⽂件中
// 主要⽤来验证token的Schema是不是Bearer
}
}
}
创建AuthenticationFailureResult类,代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace TokenTest.Filter
{
// 此类⽐较简单不做过多注释
public class AuthenticationFailureResult : IHttpActionResult
{
public string _FailureReason { get; }
public HttpRequestMessage _Request { get; }
public AuthenticationFailureResult(string FailureReason, HttpRequestMessage request)
{
_FailureReason = FailureReason;
_Request = request;
}
HttpResponseMessage HandleResponseMessage()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized)
{
RequestMessage = _Request,
ReasonPhrase = _FailureReason
};
return response;
}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
return Task.FromResult(HandleResponseMessage());
}
}
}
创建HttpAuthenticationChallengeContextExtensions类,写的context的扩展⽅法,代码如下
using System;
using System.Net.Http.Headers;
using System.Web.Http.Filters;
namespace TokenTest.Filter
{
public static class HttpAuthenticationChallengeContextExtensions
{
public static void ChallengeWith(this HttpAuthenticationChallengeContext context, string scheme)
{
ChallengeWith(context, new AuthenticationHeaderValue(scheme));
}
private static void ChallengeWith(HttpAuthenticationChallengeContext context, AuthenticationHeaderValue challenge)
{
if(context == null)
{
throw new ArgumentNullException(nameof(context));
}
context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
}
public static void ChallengeWith(this HttpAuthenticationChallengeContext context, string scheme, string parameter)
{
// 第⼆个参数的作⽤是根据传进来的scheme也就是"Bearer"和parameter这⾥为null,创建⼀个验证头,和前端传过来的token是⼀样的
ChallengeWith(context, new AuthenticationHeaderValue(scheme, parameter));
}
}
}
创建AddChallengeOnUnauthorizedResult类,代码如下
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http;
namespace TokenTest.Filter
{
public class AddChallengeOnUnauthorizedResult: IHttpActionResult
{
public AuthenticationHeaderValue _Challenge { get; }
public IHttpActionResult _InnerResult { get; }
public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
{
_Challenge = challenge;
_InnerResult = innerResult;
}
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
// 这⾥讲schemee也就是"Bearer"⽣成后的response返回给浏览器去做判断,如果浏览器请求的Authenticate中含有含有名为"Bearer"的scheme会返回200状态码否则返回401状态码                HttpResponseMessage response = await _InnerResult.ExecuteAsync(cancellationToken);
if(response.StatusCode == HttpStatusCode.Unauthorized)
{
// 如果这⾥不成⽴,但是我们之前做的验证都是成功的,这是不对的,可能出现意外情况啥的
// 这时我们⼿动添加⼀个名为"Bearer"的sheme,让请求⾛通
// 到此,完毕。
if (response.Headers.WwwAuthenticate.All(h => h.Scheme != _Challenge.Scheme))
{
response.Headers.WwwAuthenticate.Add(_Challenge);
}
}
return response;
}
}
}
4. 配置
后端字符串转数组
在你的WebApiConfig.cs⽂件中添加
config.Filters.Add(new AuthorizeAttribute()); // 开启全局验证服务
5. 代码使⽤
创建⼀个webapi的控制器
测试,⽤户登录
[Route("yejiawei/haha")]
[HttpGet]
[AllowAnonymous] // 这个属性是必须的,表⽰这个类是不需要token验证的    public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return TokenHelper.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// 在这⾥你可以在数据库中查看⽤户名是否存在
return true;
}
测试,访问后端api数据
[Route("yejiawei/haha")]
[HttpGet]
[Authentication] // 此⽅法验证的token需要调⽤Authentication属性⽅法    public string Get()
{
return "value";
}
到此⼀切搞定。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。