zoukankan      html  css  js  c++  java
  • webapi中使用token验证(JWT验证)

    本文介绍如何在webapi中使用JWT验证

    1. 准备

      安装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);
          })
      
    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";
          }
      到此一切搞定。
      
  • 相关阅读:
    Web安全测试之XSS(转)
    轻松学习RSA加密算法原理 (转)
    firewall 允许app访问网络
    点击了一个link button,查看后台调用
    kentico中提示Message: An invalid SQL query was used.
    iis browse的时候,直接通过本地的局域网ip打开页面
    asp.net web site中reference的version的autoupdate
    Adding Kentico controls to the Visual Studio toolbox
    sql server 数据库展开变慢
    kentico中的page template的使用
  • 原文地址:https://www.cnblogs.com/ye-hcj/p/8151385.html
Copyright © 2011-2022 走看看