JSON Web Token
- 1、什么是JWT
- 2、JWT解决了什么问题
- 3、早期的SSO认证
- 4、JWT认证
- 5、JWT优势
- 6、JWT结构
- 7、代码实现
- 8、工具类
- 9、JWT整合Web
- 10、拦截器校验
- 11、网关路由校验
- 12、解决多用户登录的问题
- 13、客户端保存/携带token
- 14、抽取ajax工具类
- 15、a标签跳转如何传递token
1、什么是JWT
官网地址: https://jwt.io/introduction/
通俗来讲,JWT是一个含签名并携带用户相关信息的加密串,页面请求校验登录接口时,客户端请求头中携带JWT串到后端服务,后端通过签名加密串匹配校验,保证信息未被篡改。校验通过则认为是可靠的请求,将正常返回数据。
2、JWT解决了什么问题
3、早期的SSO认证
我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
4、JWT认证
5、JWT优势
JWT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:
6、JWT结构
Header 标头
标头通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法。它会使用 Base64 对header做编码,组成而来JWT结构的第一部分。
Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。
{
"alg": "HS256", # 签名算法
"typ": "JWT" # 类型
}
Payload 负载
这部分就是我们存放信息的地方了,你可以把用户 ID 等信息放在这里,JWT 规范里面对这部分有进行了比较详细的介绍,常用的由 iss(签发者),exp(过期时间),sub(面向的用户),aud(接收方),iat(签发时间)。同样的,它也会使用 Base64 编码组成 JWT 结构的第二部分。
{
"iss": "demo JWT",
"iat": 1342513302,
"exp": 1342513302,
"name": "admin",
"sub": "dev"
}
Signature 签名
签名的目的:
信息安全性:
7、代码实现
添加依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
生成Token
@Test
void testCreateToken() {
// 1.设置超时时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND,30); // 超时时间是30s
// 2.创建JWTbuilder
JWTCreator.Builder builder = JWT.create();
// 3.设置头,负载,签名
String token = builder
// .withHeader(map) 设置头信息,可以不设置有默认值
.withClaim("name", "admin")
.withClaim("id", 10) // 设置用户自定义属性
.withExpiresAt(calendar.getTime()) // 设置令牌超时时间
.sign(Algorithm.HMAC256("dalaoshi"));// 设置用户签名
// 4.输出结果
System.out.println(token);
}
认证token
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYWRtaW4iLCJpZCI6MTAsImV4cCI6MTU5OTQwNTQ2NH0.7YFYieOC-ChS32He7DqyVtECCvM4nFWmb7hKLiPAIXY\n";
// 1.根据用户签签名获取JTW校验器
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("dalaoshi")).build();
// 2.验证token
DecodedJWT verify = jwtVerifier.verify(token);
// 3.获取token的数据
System.out.println(verify.getClaim("name").asString()); // 字符串使用asString()
System.out.println(verify.getClaim("id").asInt()); // int使用asInt
System.out.println(verify.getExpiresAt()); // 获取过期时间
- SignatureVerificationException: 签名不一致异常
- TokenExpiredException: 令牌过期异常
- AlgorithmMismatchException: 算法不匹配异常
- InvalidClaimException: 失效的payload异常
8、工具类
public class JWTUtils {
private static String sign = "dalaoshi";
public static String createToken(Map<String, String> map) {
// 1.设置超时时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 7); // 7天
// 2.创建JWTbuilder
JWTCreator.Builder builder = JWT.create();
// 设置负载数据
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entrie : entries) {
builder.withClaim(entrie.getKey(), entrie.getValue());
}
// 3.设置签名,过期时间
String token = builder
.withExpiresAt(calendar.getTime()) // 设置令牌超时时间
.sign(getSignature());// 设置用户签名
// 4.返回
return token;
}
// 获取起签名
public static Algorithm getSignature() {
return Algorithm.HMAC256(sign);
}
// 校验
public static DecodedJWT require(String token) {
return JWT.require(getSignature()).build().verify(token);
}
// 获取token中的数据
public static Claim getPayload(String token, String key) {
return require(token).getClaim(key);
}
}
9、JWT整合Web
@Autowired
private IUserService userService;
@RequestMapping("/login")
public ResultEntity login(String username,String password){
ResultEntity resultEntity = userService.login(username, password);
if(ResultEntity.SUCEESS.equals(resultEntity.getStatus())){
Map<String,String> map = new HashMap<>();
map.put("id","10");
map.put("username",username);
String token = JWTUtils.createToken(map);
return ResultEntity.success(token);
}else{
return ResultEntity.error("登录失败");
}
}
@RequestMapping("/require")
public ResultEntity require(String token){
try {
DecodedJWT require = JWTUtils.require(token);
return ResultEntity.response(require);
}catch (TokenExpiredException e){
return ResultEntity.error("token过期");
}catch (SignatureVerificationException e){
return ResultEntity.error("用户签名不一致");
} catch (InvalidClaimException e){
return ResultEntity.error("payload数据有误");
}catch (Exception e){
return ResultEntity.error("校验失败");
}
}
@RequestMapping(value = "/getPayLoad")
public ResultEntity getPayLoad(String token){
DecodedJWT decodedJWT = JWTUtils.require(token);
Map<String, Claim> claims = decodedJWT.getClaims();
Map<String,String> map = new HashMap<>();
Set<Map.Entry<String, Claim>> entries = claims.entrySet();
for (Map.Entry<String, Claim> entrie:entries) {
map.put(entrie.getKey(),entrie.getValue().asString());
}
return ResultEntity.success(map);
}
10、拦截器校验
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取token
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try {
// 2.校验
JWTUtils.verify(token);
return true;
}catch (TokenExpiredException e){
return ResultEntity.error("token过期");
}catch (SignatureVerificationException e){
return ResultEntity.error("用户签名不一致");
} catch (InvalidClaimException e){
return ResultEntity.error("payload数据有误");
}catch (Exception e){
return ResultEntity.error("校验失败");
}
// 3.校验失败响应数据
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
11、网关路由校验
@Component
public class SSOFilter extends ZuulFilter{
@Autowired
private ISSOService ssoService;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER-1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
StringBuffer requestURL = request.getRequestURL();
System.out.println(requestURL);
// 1.该服务是否需要验证
if("http://localhost/shop-back/user/getUserPage".equals(requestURL.toString())){
String token = request.getHeader("token");
// 2.验证服务
ResultEntity resultEntity = ssoService.require(token);
System.out.println(resultEntity);
if(!ResultEntity.SUCEESS.equals(resultEntity.getStatus())){
requestContext.setSendZuulResponse(false); // 不能往下执行了
HttpServletResponse response = requestContext.getResponse();
response.setContentType("application/json;charset=utf-8"); // 设置响应数据类型
requestContext.setResponseBody(JSON.toJSONString(ResultEntity.error("校验未通过"))); // 设置响应数据
}
}
return null;
}
}
12、解决多用户登录的问题
// 伪代码
// login
public String login(String name,String password){
// 1.查询数据库认证
// 2.生成token
String token = "";
// 3.把用户最新的token放入到reids中
redisTemp.set(username,token); // username作为key,多次登录key会被覆盖
}
// 路由校验
// 1.获取用户token
// 2.根据用户名查询用户最新的token
// 3.对比两个token是否一致,如果不一致就说明用户进行了第二次登陆,就不让认证通过。
13、客户端保存/携带token
// 登录获取token,保存到本地
function login(){
var username ="admin";
var password ="123";
var param = new Object();
param.username=username;
param.password=password;
$.post("http://localhost/shop-sso/sso/login",param,function (data) {
if(data.status ="success"){
// 获取token
var token = data.data;
// 保存toke到客户端
localStorage.setItem("login-token",token);
}
},"JSON");
}
// 发送请求是把token放到请求头中保存
function sendRequest(){
$.ajax({
url: "http://localhost/shop-sso/addXxxxx",
type: "post",
dataType: 'json',
beforeSend: function (XMLHttpRequest) {
// 获取本地储存的token,添加到请求头中
XMLHttpRequest.setRequestHeader("Authorization", localStorage.getItem("login-token"));
},
success: function (result) {
}
});
}
14、抽取ajax工具类
window.utils={
ajax:function(param){
$.ajax({
url: param,
type: "post",
dataType: 'json',
data:param.data,
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("Authorization", localStorage.getItem("login-token"));
},
success: function (result) {
param.success(result);
}
});
}
}
// 调用
utils.ajax({
url:"http://localhost/shop-sso/sso/login",
data:param,
success:function(data){
if(data.status ="success"){
// 获取token
var token = data.data;
// 保存toke到客户端
localStorage.setItem("login-token",token);
}
}
})
15、a标签跳转如何传递token
后记
👉👉💕💕美好的一天,到此结束,下次继续努力!欲知后续,请看下回分解,写作不易,感谢大家的支持!! 🌹🌹🌹