随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
JWT实际上就是一个字符串,它由三部分组成:头部、载荷与签名。JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数。适用于多客户端的前后端解决方案,JWT 是无状态化的,更适用于 RESTful 风格的接口验证。本文主要介绍使用JWT进行接口身份认证。
一、jwt介绍
1. JWT的结构体
JWT由三部分组成,分别是头信息、有效载荷、签名,中间以(.)分隔
(1)header(头信息) 描述 JWT 的元数据的JSON对象
由两部分组成,令牌类型(即:JWT)、散列算法(HMAC、RSASSA、RSASSA-PSS等)
{"alg":"HS256","typ":"JWT"}
(2)Payload(有效载荷)
JWT的第二部分是payload,其中包含claims。claims是关于实体(常用的是用户信息)和其他数据的声明,claims有三种类型: registered, public, and private claims。
一个用来存放实际需要传递的数据的JSON 对象。如:
}
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "admin",
"exp": 1610877510,
"iss": "cba",
"aud": "cba"
}
(3)Signature
要创建签名部分,必须采用编码的Header,编码的Payload,秘钥,Header中指定的算法,并对其进行签名。 对前两部分(Header、Payload)的签名,防止数据篡改。
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
2. JWT示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE2MTA4Nzc1MTAsImlzcyI6ImNiYSIsImF1ZCI6ImNiYSJ9.O9lbZwfqRuA6vKcRCfYieA1zLkTPppdSvTc8UzwCkNw
二、准备工作
1. 添加NuGet程序包
Microsoft.AspNetCore.Authentication.JwtBearer
(2).appsettings.json
"JwtConfig": {
"SecretKey": "123123123123", // 密钥 可以是guid 也可以是随便一个字符串
"Issuer": "zhangsan", // 颁发者
"Audience": "zhangsan", // 接收者
"Expired": 30 // 过期时间(30min)
},
2. 创建JWT配置类
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// jwt配置
/// </summary>
public class JwtConfig : IOptions<JwtConfig>
{
public JwtConfig Value => this;
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int Expired { get; set; }
public DateTime NotBefore => DateTime.UtcNow;
public DateTime IssuedAt => DateTime.UtcNow;
public DateTime Expiration => IssuedAt.AddMinutes(Expired);
private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
public SigningCredentials SigningCredentials =>
new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
}
3. 创建JWT工具类
public class GenerateJwt
{
private readonly JwtConfig _jwtConfig;
public GenerateJwt(IOptions<JwtConfig> jwtConfig)
{
_jwtConfig = jwtConfig.Value;
}
/// <summary>
/// 生成token
/// </summary>
/// <param name="sub"></param>
/// <param name="customClaims">携带的用户信息</param>
/// <returns></returns>
public JwtTokenResult GenerateEncodedTokenAsync(string sub, LoginUserModel customClaims)
{
//创建用户身份标识,可按需要添加更多信息
var claims = new List<Claim>
{
new Claim("userid", customClaims.userid),
new Claim("username", customClaims.username),
//new Claim("realname",customClaims.realname),
//new Claim("roles", string.Join(";",customClaims.roles)),
//new Claim("permissions", string.Join(";",customClaims.permissions)),
//new Claim("normalPermissions", string.Join(";",customClaims.normalPermissions)),
new Claim(JwtRegisteredClaimNames.Sub, sub),
};
//创建令牌
var jwt = new JwtSecurityToken(
issuer: _jwtConfig.Issuer,
audience: _jwtConfig.Audience,
claims: claims,
notBefore: _jwtConfig.NotBefore,
expires: _jwtConfig.Expiration,
signingCredentials: _jwtConfig.SigningCredentials);
string access_token = new JwtSecurityTokenHandler().WriteToken(jwt);
return new JwtTokenResult()
{
access_token = access_token,
expires_in = _jwtConfig.Expired * 60,
token_type = JwtBearerDefaults.AuthenticationScheme,
user = customClaims
};
}
}
4. JwtTokenResult
登录成功生成jwt返回给前端的model,LoginUserModel是用户信息model,我这里携带是为了避免前端解析token。
/// <summary>
/// 登录成功返回model
/// </summary>
public class JwtTokenResult
{
public string access_token { get; set; }
public string refresh_token { get; set; }
/// <summary>
/// 过期时间(单位秒)
/// </summary>
public int expires_in { get; set; }
public string token_type { get; set; }
public LoginUserModel user { get; set; }
}
public class LoginUserModel
{
public string userid { get; set; }
public string username { get; set; }
public string realname { get; set; }
public string roles { get; set; }
public string permissions { get; set; }
public string normalPermissions { get; set; }
}
5. Startup配置
(1) .ConfigureServices注入jwt
//注入jwt
services.AddScoped<GenerateJwt>();
services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
#region jwt验证
var jwtConfig = new JwtConfig();
Configuration.Bind("JwtConfig", jwtConfig);
services.AddAuthentication(option =>
{
//认证middleware配置
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//Token颁发机构
ValidIssuer = jwtConfig.Issuer,
//颁发给谁
ValidAudience = jwtConfig.Audience,
//这里的key要进行加密
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecretKey)),
//是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
ValidateLifetime = true,
};
});
#endregion
(2).Configure
app.UseAuthentication();//要在授权之前认证
6. 调用获取token
using JwtDemo.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace JwtDemo.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : Controller
{
private readonly GenerateJwt _generateJwt;
public TestController(GenerateJwt generate) {
this._generateJwt = generate;
}
[HttpGet]
public ActionResult GetLogin(string name,string userid) {
var claims = new LoginUserModel()
{
userid = userid,
username = name,
realname = name,
//roles = string.Join(";", user.roles),
//permissions = string.Join(";", user.permissions),
};
var refreshToken = Guid.NewGuid().ToString();
//当然 你要在生成token之前要验证一下账户是否在数据库存在 存在则生成
/*
数据库查询
*/
var jwtTokenResult = _generateJwt.GenerateEncodedTokenAsync(userid, claims);
jwtTokenResult.refresh_token = refreshToken;
return Json(jwtTokenResult);//这里可按需返回 如果不想返回用户信息 比如密码 可以在_generateJwt.GenerateEncodedTokenAsync去掉哦
}
}
}
swagger调试
postman请求
7. 在后台中有时候我们需要获取当前用户的一些属性
JwtUser,用户解析jwt 获取当前用户信息
public static class JwtUser
{
/// <summary>
/// 解析jwt 获取当前用户信息
/// </summary>
/// <param name="request"></param>
/// <returns>用户信息</returns>
public static LoginUserModel GetRequestUser(this HttpRequest request)
{
var authorization = request.Headers["Authorization"].ToString();
var auth = authorization.Split(" ")[1];
var jwtArr = auth.Split('.');
var dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
//解析Claims
var reqUser = new LoginUserModel
{
userid = dic["userid"],
username = dic["username"],
realname = dic["realname"],
roles = dic["roles"],
permissions = dic["permissions"],
normalPermissions = dic["normalPermissions"],
};
return reqUser;
}
}
控制器中这样获取
[HttpGet]
public ActionResult GetUser() {
var aa= JwtUser.GetRequestUser(this.Request);
return Json(aa.userid+"===="+ aa.username); //获取到当前用户的姓名 角色等等就可以做一些业务处理
}
8. 使用
在接口上标记身份验证
[AllowAnonymous] 标记此标识的接口是不需要验证的
[Authorize] 标记此标识的接口需要验证
[Authorize]
[HttpGet]
public ActionResult GetDataList(string 参数)
{
/*
业务代码
*/
return Json("");
}
也可以直接标记在控制器上
获取token的接口最好单独写在一个控制器
9. Swagger UI添加认证
在项目中通常都添加了Swagger UI来展示接口及基础测试,那么如果添加了认证后,如何在调用接口前添加认证信息呢?
#region 启用swagger验证功能
//添加一个必须的全局安全信息,和AddSecurityDefinition方法指定的方案名称一致即可。
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 在下方输入Bearer {token} 即可,注意两者之间有空格",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer",
});
#endregion
添加token即可
输入时 要输入Bearer +token 注意Bearer后有空格
其他方案
https://www.cnblogs.com/indexlang/p/indexlang.html