提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
最近公司在做系统整合, 要将原来一个系统按领域划分成不同系统, 但是登录还是交由门户登录, 考虑各方面后, 最后选择了使用Jwt做系统间交互凭证
一、什么是Jwt?
参考: https://blog.csdn.net/weixin_45070175/article/details/118559272
二、使用步骤
1.引入jwt
System.IdentityModel.Tokens.Jwt
2.生成jwtToken
代码如下(示例):
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(txtKey.Text));
var loginUser = txtUser.Text;
DateTime utcNow = dateTimePicker1.Value;
string uuid = Guid.NewGuid().ToString();
var claims = new List<Claim>()
{
new Claim("uuid", uuid),
new Claim(ClaimsName, loginUser),
new Claim(ClaimTypes.Name, loginUser)
};
JwtSecurityToken jwtToken = new JwtSecurityToken(
issuer: "aaa", //签发人
claims: claims, //token内容
notBefore: utcNow, //签发时间
expires: utcNow.AddHours(4), //过期时间
signingCredentials: new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256) //加密方式和加密key
);
//生成token
txtToken.Text= new JwtSecurityTokenHandler().WriteToken(jwtToken);
2.解析jwtToken
try
{
//解析方式1, 不校验token, 直接强制解析, 不推荐使用
var jwtToken = new JwtSecurityTokenHandler().ReadJwtToken(token);
var jwtPayload = jwtToken.Payload;
txtJson.Text = ConvertJsonString(JsonHelper.ToJson(jwtToken.Payload));
//解析后的内容
var expDataStr = Convert.ToInt64(jwtPayload["exp"]);
var iatDataStr = Convert.ToInt64(jwtPayload["iat"]);
//过期时间
txtExpData.Text= DateHelper.ToLocalDateTimeText(expDataStr);
//签发时间
txtIatTime.Text= DateHelper.ToLocalDateTimeText(iatDataStr);
//解析方式2, 使用加密key进行校验解析
SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key));
//解析模型跟上面生成模型保持一致
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = false,
ValidateAudience = false,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "aaa",
IssuerSigningKey = securityKey
};
SecurityToken securityToken = null;
var claimsPrincipal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out securityToken);
//解析后的内容
jwtPayload = ((JwtSecurityToken)securityToken).Payload;
}
catch (SecurityTokenException ex)
{
MessageBox.Show("token校验失败"+ex.Message);
}
3. 具体使用jwt校验
- 前端Request发送请求时, 在header中附加上Authorization, 写入jwt-token
- 后台添加过滤器AuthorizeFilterAttribute,继承AuthorizeAttribute,重写OnAuthorization方法, 在方法中校验jwttoken是否合法, 如果不合法,直接return掉
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace Dji.LMSRule.App.Filters
{
public class AuthorizeFilterAttribute : AuthorizeAttribute
{
private readonly string Authorization = "Authorization";
private readonly string Bearer = "Bearer ";
private readonly string ClaimsName = "name";
/// <summary>
/// 加密key
/// </summary>
private readonly SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("这里是秘钥"));
public override void OnAuthorization(AuthorizationContext filterContext)
{
var token = filterContext.HttpContext.Request.Headers.Get(Authorization);
if (string.IsNullOrWhiteSpace(token))
{
AuthFailResponse(filterContext);
return;
}
string userName = String.Empty;
try
{
//校验token是否有Bearer前缀
if (token.Length < Bearer.Length || !token.StartsWith(Bearer))
{
return;
}
token = token.Replace(Bearer, "");
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = false,
ValidateAudience = false,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "aaa",
IssuerSigningKey = securityKey
};
SecurityToken securityToken = null;
var claimsPrincipal = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out securityToken);
var jwtPayload = ((JwtSecurityToken)securityToken).Payload;
//这里取出token里的用户名, 如果有需要用的, 可以放到session里
userName = jwtPayload[ClaimsName] + "";
//过期时间, 时间戳这里转换用的是int32, 超过int最大长度的时候, 这个时间戳转Exp时间会挂, 取出来是个null,
//这种时候,可以使用jwtPayload["exp"]
var expTime = jwtPayload.Exp ?? jwtPayload["exp"];
//token还有半小时就过期的时候, 自动续期, 这个理论上不应该在这里做, token的签发和续期应该都由一个系统提供接口, 其他系统只负责校验就行, 这里比较特殊, 所以写了个续期
var tokenTime = DateHelper.ToLocalDateTime(Convert.ToInt64(expTime));
if (tokenTime > DateTime.Now)
{
if (tokenTime.Value < (DateTime.Now.AddMinutes(30)))
{
token = GenerateToken(userName);
//校验成功
ClaimsIdentity identity = new ClaimsIdentity(new List<Claim>()
{
new Claim(ClaimsName, userName),
new Claim(ClaimTypes.Name, userName)
}, Bearer.Trim());
filterContext.HttpContext.User = new ClaimsPrincipal(identity);
}
}
return;
}
catch (SecurityTokenExpiredException ex)
{
//表示过期
}
catch (Exception ex)
{
//系统异常
}
catch (SecurityTokenException se)
{
//token错误
AuthFailResponse(filterContext);
}
}
private void AuthFailResponse(AuthorizationContext filterContext)
{
filterContext.Result = new JsonResult
{
Data = new AuthResult
{
IsSuccess = true,
AuthErrorCode = 401,
ErrorMessage = "无权限访问!"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
总结
这里就不写登录的地方了, 理论上来说, token的续期不应该放在这里, 这边是有特殊业务要求, 所以写的, 正常情况下, 应该是要登录系统提供一个接口出来, 签发和续期都交给该系统, 甚至校验也可以都交给一个系统来做, 只不过这样对接口性能要求就比较高了