一:简介
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
二:安装依赖
首先要安装对应的nuget包,名为JwtBearer
三:编写配置
这里我们将需要用到的配置项单独抽离出来,当然也可以从json配置中读取,这里我选择新建名为StaticConfig的类
//公共配置类
public static class StaticConfig
{
//jwt颁发者
public static string issuer = "core775";
//jwt密钥
public static string secret = "789124791277681664122";
//jwt受众
public static string audience = "Audience";
}
四:测试获取加密token
随便找一个Controller,这里参考关关长语的博客,将加密的过程抄过来
[ApiController]
[Route("Api")]
public class ApiController : Controller
{
[HttpGet]
[Route("jwt")]
public object Get()
{
//秘钥
byte[] secret = System.Text.Encoding.UTF8.GetBytes(StaticConfig.secret);
//生成秘钥
var key = new SymmetricSecurityKey(secret);
//生成数字签名的签名密钥、签名密钥标识符和安全算法
var credential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//构建JwtSecurityToken类实例
var token = new JwtSecurityToken(
//添加颁发者
issuer: StaticConfig.issuer,
//添加受众
audience: StaticConfig.audience,
//添加其他需要加密的信息
new List<Claim> {
new Claim(ClaimTypes.Name,"zhangsan"),
new Claim(ClaimTypes.Role,"admin")
},
//自定义过期时间
expires: DateTime.UtcNow.AddHours(32)
, signingCredentials: credential);
//签发token
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
}
ApiFox中测试请求
五:测试解密token
已知我们先前的加密是使用的JwtSecurityTokenHandler()的WriteToken方法,于是我们进Handler的源码中看一看是否存在解密的方法,如果有的话大概率也叫什么什么Token,于是在距离WriteToken的不远处找到了ReadToken
它的返回值是一个JwtSecurityToken,于是我们解密的步骤大致可以写成如下
string token = "13asdasd";
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
但是我们不知道JwtSecurityToken如何获取我们想要的数据,譬如颁布者,我们封装的Claim信息等,于是进入JwtSecurityToken的源码,然后发现该类中包含着很多属性,比如Issuer
//
// 摘要:
// Gets the 'value' of the 'issuer' claim { iss, 'value' }.
//
// 言论:
// If the 'issuer' claim is not found, an empty string is returned.
public override string Issuer { get; }
这样一来,调用该属性就能获取到颁布者的信息
string issuer = tj.Issuer;//获取颁发者
但我们还想要获取Claim中加密的Name或者Role信息,于是去调它的IEnumerable<Claim> Claims
属性
//
// 摘要:
// Gets the System.Security.Claims.Claim(s) for this token. If this is a JWE token,
// this property only returns the encrypted claims; the unencrypted claims should
// be read from the header seperately.
//
// 言论:
// System.Security.Claims.Claim(s) returned will NOT have the System.Security.Claims.Claim.Type
// translated according to System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.InboundClaimTypeMap
public IEnumerable<Claim> Claims { get; }
获取到一个IEnumerable,参考我们先前加密时候的配置,可以知道我们的在封装Claim的时候,是使用了ClaimTypes类中的一些固定类型,因此这里就可以反过来照着它对应的key来取出Value
//添加其他声明
new List<Claim> {
new Claim(ClaimTypes.Name,"zhangsan"),
new Claim(ClaimTypes.Role,"admin")
},
获取zhangsan的接口
[HttpGet]
[Route("decodeJWT")]
public object decodeJWT(string token) {
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
获取Claim 相关数据
IEnumerable<Claim> j = new JwtSecurityTokenHandler().ReadJwtToken(token).Claims;
//查找类型为ClaimTypes.Name的数据,可以通过Value获取内容
var result = from s in j
where s.Type == ClaimTypes.Name
select s;
Claim jk = result.First();
return Json(jk.Value);
}
接口测试:
以此类推,想要获取其他数据也都是同样的方式
六:封装工具类
把常用功能抽离出来,单独走一个JWTUtil
/// <summary>
/// jwt工具类,用来加工token和解析token
/// </summary>
public class JWTUtil
{
/// <summary>
/// 获取加密token
/// </summary>
/// <param name="id"></param>
/// <param name="name"></param>
/// <param name="role"></param>
/// <returns> object token </returns>
public static object getToken(string id, string name, string role) {
if (role == null) {
role = "user";
}
//秘钥
byte[] secret = System.Text.Encoding.UTF8.GetBytes(StaticConfig.secret);
//生成秘钥
var key = new SymmetricSecurityKey(secret);
//生成数字签名的签名密钥、签名密钥标识符和安全算法
var credential = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//构建JwtSecurityToken类实例
var token = new JwtSecurityToken(
//添加颁发者
issuer: StaticConfig.issuer,
//添加受众
audience: "Audience",
//添加其他声明
new List<Claim> {
new Claim(ClaimTypes.Name,name),
new Claim(ClaimTypes.Sid,id),
new Claim(ClaimTypes.Role,role)
},
//过期时间是一天
expires: DateTime.UtcNow.AddHours(32)
, signingCredentials: credential);
//签发token
return new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
};
}
/// <summary>
/// 返回当前token状态,0是通过校验,1是颁布者不一致,2是过期了
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
public static int check(string token) {
if (JWTUtil.getIssuer(token).Equals(StaticConfig.issuer)) {
//颁布者不一致,存在伪造token的危险
return 1;
}
if (!JWTUtil.checkTokenDate(token)) {
//token过期
return 2;
}
return 0;
}
/// <summary>
/// 检查当前token有没有过期,如果返回true说明没有过期,返回false是已经过期
/// </summary>
/// <param name="token"></param>
/// <returns> bool </returns>
public static bool checkTokenDate(string token) {
//获取token
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
//当前时间
DateTime now = DateTime.Now;
//判断tj.ValidTo是不是比now大,也就是验证token有没有过期
return DateTime.Compare(tj.ValidTo, now) >= 0;
}
/// <summary>
/// 获取当前token中的用户名
/// </summary>
/// <param name="token"></param>
/// <returns> 获取Claim</returns>
public static string getName(string token) {
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
//获取Claim 相关数据
IEnumerable<Claim> j = new JwtSecurityTokenHandler().ReadJwtToken(token).Claims;
//查找类型为ClaimTypes.Name的数据,可以通过Value获取内容
var result = from s in j
where s.Type == ClaimTypes.Name
select s;
Claim claim = result.First();
return claim.Value;
}
/// <summary>
/// 获取当前token中的ID
/// </summary>
/// <param name="token"></param>
/// <returns> 获取Claim</returns>
public static string getId(string token)
{
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
//获取Claim 相关数据
IEnumerable<Claim> j = new JwtSecurityTokenHandler().ReadJwtToken(token).Claims;
//查找类型为ClaimTypes.Sid的数据
var result = from s in j
where s.Type == ClaimTypes.Sid
select s;
Claim claim = result.First();
return claim.Value;
}
/// <summary>
/// 获取当前token中的role角色
/// </summary>
/// <param name="token"></param>
/// <returns> 获取Claim</returns>
public static string getRole(string token)
{
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
//获取Claim 相关数据
IEnumerable<Claim> j = new JwtSecurityTokenHandler().ReadJwtToken(token).Claims;
//查找类型为ClaimTypes.Role的数据
var result = from s in j
where s.Type == ClaimTypes.Role
select s;
Claim claim = result.First();
return claim.Value;
}
/// <summary>
/// 获取当前token的颁布者
/// </summary>
/// <param name="token"></param>
/// <returns> string颁布者 </returns>
public static string getIssuer(string token)
{
JwtSecurityToken tj = new JwtSecurityTokenHandler().ReadJwtToken(token);
return tj.Issuer;
}
}
因此,接下来在Controller中我们就可以直接调工具类去加密解密
[ApiController]
[Route("Api")]
public class ApiController : Controller
{
[HttpGet]
[Route("jwt")]
public object Get()
{
return JWTUtil.getToken("878712371922131", "jackone","admin");
}
[HttpGet]
[Route("decodeJWT")]
public string decodeJWT(string token) {
return JWTUtil.getName(token);
}
}