webapi中使用token验证(JWT验证)
本文介绍如何在webapi中使用JWT验证
- 准备 - 安装JWT安装包 System.IdentityModel.Tokens.Jwt
 你的前端api登录请求的方法,参考
 axios.get("api/token?username=cuong&password=1").then(function (res) {
 // 返回一个token
 /*
 token示例如下
 "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6Inllamlhd2VpIiwibmJmIjoxNTE0NjQyNTA0LCJleHAiOjE1MTQ2NDk3MDQsImlhdCI6MTUxNDY0MjUwNH0.ur97ZRviC_sfeFgDOHgaRpDePcYED6qmlfOvauPt9EA"
 */
 }).catch(function (err) {
 console.log(err);
 })
 你的前端请求后端数据执行的任意方法,传递token,参考
 var axiosInstance = window.axios.create({
 headers: {
 common: {
 Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNTE0NjE4MDgzLCJleHAiOjE1MTQ2MjUyODMsImlhdCI6MTUxNDYxODA4M30.khgxAzTEgQ86uoxJjACygTkB0Do6i_9YcmLLh97eZtE"
 }
 /*
 上面Authorization会自动映射成后端request.Headers.Authorization对象
 {
 Parameter: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNTE0NjE4MDgzLCJleHAiOjE1MTQ2MjUyODMsImlhdCI6MTUxNDYxODA4M30.khgxAzTEgQ86uoxJjACygTkB0Do6i_9YcmLLh97eZtE"
 Scheme: "Bearer"
 }
 */ }
 })
 axiosInstance.get("api/value").then(function (res) {
 }).catch(function (err) {
 console.log(err);
 })
 
- 创建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;
 }
 }
 }
 }
 
- 创建过滤器类 - 当前端发送一个请求,需要接收并处理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;
 }
 }
 }
 
- 配置 - 在你的WebApiConfig.cs文件中添加
 config.Filters.Add(new AuthorizeAttribute()); // 开启全局验证服务
 
- 代码使用 - 创建一个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";
 }
 到此一切搞定。
 
webapi中使用token验证(JWT验证)的更多相关文章
- WebApi中关于base64和jwt的联合验证
		用到了如鹏的代码 jwt验证 public class MyAuthoFilterPostOrgInfoAttribute: AuthorizationFilterAttribute { public ... 
- IOS 中使用token机制来验证用户的安全性
		登录的业务逻辑{ http:是短连接. 服务器如何判断当前用户是否登录? // 1. 如果是即时通信类:长连接. // 如何保证服务器跟客户端保持长连接状态? // ... 
- WebApi 中使用 Token
		1.登陆的时候根据用户信息生成Token var token = FormsAuthentication.Encrypt( new FormsAuthenticationTicket( , " ... 
- 基于 Token 的身份验证:JSON Web Token(附:Node.js 项目)
		最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强, ... 
- 基于 Token 的身份验证:JSON Web Token
		最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,Github 等等,比起传统的身份验证方法,Token 扩展性更强, ... 
- C# 获取Header中的token值
		public CurrentUser currentUser { get { CurrentUser result = new CurrentUser(); //jwt 解密token IJsonSe ... 
- DDD实战11 在项目中使用JWT的token 进行授权验证
		步骤: 1.首先要在webapi的管道中 使用认证(Authentication) 2.要在webapi的服务中注册验证条件 代码如下: namespace Dealer.WebApi { publi ... 
- c# asp.net 中使用token验证
		基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息.这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提 ... 
- Blazor client-side + webapi (.net core 3.1) 添加jwt验证流程(非host)第二步 添加Identity
		添加Identity数据上下文 安装nuget包:Microsoft.AspNetCore.Identity.EntityFrameworkCore 创建ApplicationDbContext类 创 ... 
随机推荐
- 4950: [Wf2017]Mission Improbable
			4950: [Wf2017]Mission Improbable Time Limit: 1 Sec Memory Limit: 512 MBSubmit: 608 Solved: 222[Sub ... 
- 汇编笔记 RET
			assume cs:code,ss:stack stack segment db dup() stack ends code segment mov ax,4c00h int 21h start: m ... 
- Spring data jpa 实现简单动态查询的通用Specification方法
			本篇前提: SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法 这篇文章中的第二种方法 实现Specification 这块的方法 只适用于一个对象针对某一个固定字 ... 
- 微信小程序申请。很蛋疼的流程。
			微信小程序申请. 营业执照,食品许可证,身份证正面,身份证反面. 1.先要申请服务号. 需要一个QQ邮箱,申请服务号. 填写各种信息,营业执照信息. 法人信息. 管理员用自己人的.方便开发操作. 申请 ... 
- CSS3中的变形功能
			一.变形主要值得是利用transform功能来实现文字或图片的旋转,缩放,倾斜,移动这四种处理. 1.旋转-----transform:rotate(xxdeg);( IE9以上,safari 3.1 ... 
- CentOS 7.3 关闭默认防火墙&远程登录
			小编作为一个运维新人,踩坑之路是必不可少的. 这不,新来了一家公司,做云运维工程师,新的环境,网络和之前的都不一样,VMware Workstation虚拟机上的网 ... 
- 图片放大器——wpf
			<Grid> <Image x:Name="imgSource" Source="{Binding Web ... 
- 解决: PyInstaller打包后exe文件打开时出现failed to execute script
			def resource_path(self, relative): if hasattr(sys, "_MEIPASS"): return os.path.join(sys._M ... 
- Shiro安全配置
			主要还是整合了本地ehcache,集群session管理过段时间放出 <?xml version="1.0" encoding="UTF-8"?> ... 
- 炫酷自定义翻转View--第三方开源--TagCloudView
			下载地址:https://github.com/ChinaZeng/3dTagCloudAndroid 贴上Demo代码: <com.moxun.tagcloudlib.view.TagClou ... 
