基于上次做的前后端项目的sso是shiro整合的jwt,jwt这块还不太熟悉,所以去回顾了下知识点,简单记录一下。主要是端午节三天天气太反常了,坐着都出汗,啥也没干,所以节前做的都忘了也不知道记啥了。
session和JWT
之前shrio自己都是集成session的,整合JWT需要自己去写。先说说session,session在服务器端存储,客户端在一次登录成功后以后再次登录会向服务端发送带有session返回的cookie去服务端查对应session来实现自动登录。例:
package com.jin.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @author jinyunlong
* @date 2021/6/11 10:39
* @profession ICBC锅炉房保安
*/
@RestController
public class SessionController {
@GetMapping(value = "test")
public String session(String username, HttpServletRequest httpServletRequest){
httpServletRequest.getSession().setAttribute("username",username);
return "login ok";
}
}
sesssion存在的问题:
1、session存在服务端,大部分是在内存中,要是用户多了,服务器端压力会变大
2、基于cookie的话,容易被跨站伪造
3、以后多节点部署的话,还要做session共享,不方便做集群
4、session只是一段特征值,实际没啥意义,我们到服务器端还是要根据这个session值去数据库该查查数据,数据库压力也是一样的大。
JWT认证流程:用户在第一次登录成功后,后端会根据jwt的格式(header+payload(不要放敏感信息)+sign)生成一个token返回给前端存在本地localstorge中。然后前端以后再去请求后端接口时会把token放在http header中传入后端,后端再根据自己定义的规则(过期时间,有效性等)去校验从而实现自动登录,通过的话去调用对应接口业务逻辑,失败则抛出相应异常。
JWT token的好处:
1、简单:可以通过post参数或者放在http header中请求,数据量小
2、在本地存储,减少服务端压力
3、自包含:token的payload段中可以包含想要的相关用户信息,拿来即用,可以避免多次查询数据库
4、token是json格式,所以跨语言
JWT结构
token组成:header(标头)+payload(有效载荷)+signature(签名) header.payload.signature
header:声明jwt中使用的签名算法:如HMAC、RSA、SHA256等
payload:在这里头就能放想要的数据了
signature:签名秘钥
一个springboot实例
首先,生成token util类:
package com.jin.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* @author jinyunlong
* @date 2021/6/11 17:12
* @profession ICBC锅炉房保安
*/
public class JWTUtils {
private static final String SIGN = "123456";
//生成token header.payload.sign
public static String getToken(Map<String,String> map){
Calendar instance =Calendar.getInstance();
instance.add(Calendar.DATE,7); //令牌有效时间,7天过期
//创建jwt builder
JWTCreator.Builder builder = JWT.create();
//payload
map.forEach((k,v)->{
builder.withClaim(k,v);
});
String token = builder.withExpiresAt(instance.getTime()) //指定令牌过期时间
.sign(Algorithm.HMAC256(SIGN));
System.out.println(token);
return token;
}
//验证token合法性,不合法就直接抛异常了,合法就当我们无事发生过 - -
public static void verify(String token){
JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
}
//获取token信息方法
public static DecodedJWT getTokenInfo(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
return verify;
}
}
controller登录接口去查库并用postman测试,成功的话生成token:
package com.jin.controller;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.jin.entity.User;
import com.jin.service.UserService;
import com.jin.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**
* @author jinyunlong
* @date 2021/6/11 18:06
* @profession ICBC锅炉房保安
*/
@RestController
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/user")
public Map<String,Object> login(User user){
log.info("用户名:[{}]",user.getName());
log.info("密码:[{}]",user.getPassword());
Map<String,Object> map = new HashMap<>();
try {
User userDB = userService.login(user);
Map<String,String> payload = new HashMap<>();
payload.put("id",userDB.getId());
payload.put("name",userDB.getName());
//生成jwt令牌
String token = JWTUtils.getToken(payload);
map.put("state",true);
map.put("msg","认证成功");
map.put("token",token); //响应token
} catch (Exception e) {
map.put("state",false);
map.put("msg",e.getMessage());
}
return map;
}
@GetMapping("/user1")
public Map<String,Object> test(HttpServletRequest request){
Map<String,Object> map = new HashMap<>();
String token = request.getHeader("token");
DecodedJWT decodedJWT = JWTUtils.getTokenInfo(token);
log.info("用户id:[{}]",decodedJWT.getClaim("id").asString());
log.info("用户名:[{}]",decodedJWT.getClaim("name").asString());
//处理自己的业务逻辑
map.put("state",true);
map.put("msg","请求成功");
return map;
// log.info("当前token为:[{}]",token);
// try {
// JWTUtils.verify(token); //验证令牌
// map.put("state",true);
// map.put("msg","请求成功");
// return map;
// } catch (SignatureVerificationException e) {
// e.printStackTrace();
// map.put("msg","无效签名");
// } catch (TokenExpiredException e){
// e.printStackTrace();
// map.put("msg","token过期");
// } catch (AlgorithmMismatchException e){
// e.printStackTrace();
// map.put("msg","token算法不一致");
// } catch (Exception e){
// e.printStackTrace();
// map.put("msg","token无效");
// }
// map.put("state",false);
// return map;
//有了拦截器就不用了
}
}
然后写个拦截器并注入到容器中
package com.jin.interceptor;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jin.utils.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* @author jinyunlong
* @date 2021/6/15 10:39
* @profession ICBC锅炉房保安
*/
public class JWTInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中的令牌
String token = request.getHeader("token");
Map<String,Object> map = new HashMap<>();
try {
JWTUtils.verify(token); //验证令牌
return true; //放行请求
} catch (SignatureVerificationException e) {
e.printStackTrace();
map.put("msg","无效签名");
} catch (TokenExpiredException e){
e.printStackTrace();
map.put("msg","token过期");
} catch (AlgorithmMismatchException e){
e.printStackTrace();
map.put("msg","token算法不一致");
} catch (Exception e){
e.printStackTrace();
map.put("msg","token无效");
}
map.put("state",false);//设置状态
//将map转为json 用jackson就行
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
return false;
}
}
package com.jin.config;
import com.jin.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author jinyunlong
* @date 2021/6/15 10:48
* @profession ICBC锅炉房保安
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JWTInterceptor()) //拦他
// .addPathPatterns("/**") //全拦
// .excludePathPatterns("/user/**"); //排除
.addPathPatterns("/user1") //全拦
.excludePathPatterns("/user"); //或者这样一拦一放
}
}
那根据上面两段代码写的,/user接口我是放行了,但是调用user1接口时会先去走拦截器,拦截器会去调用http header存入的刚才生成的token的调util 类中校验方法去校验,没抛异常就放行到接口中去实现业务逻辑,抛异常就进异常。用postman测试一下:
第一次我没加token请求user1接口,进入拦截器被拦,抛出token无效的异常:
然后我在请求头中带上token传:
可以看见返回了user1接口的成功内容
map.put("state",true);
map.put("msg","请求成功");
带上一点业务处理逻辑,看下log:
可见token没问题的话会通过拦截器成功到接口下,获取到了token中的payload内容。
总结下JWT的逻辑,总体不难:
1、先通过util类写生成token、获取token、校验token的方法
2、登录成功时一定要调用生成token方法
3、写拦截器,里面调校验token的方法,通过就放行,不通过就直接抛异常,所以校验写void就可以
4、拦截器注入容器,在这个config类中设置那些接口放行,哪些需要拦截
5、前端传参可以把token通过参数post,或者放进header请求至后端,拦截器校验通过的话,到对应接口下,接口可以通过上面两种方式接参(我上面用的httprequest接header)获取token值后,再去获取token中的payload值。
6、hashMap(我也不知道要说啥。。。)
哎,端午节不该回家的,闷热的不行,吃刨冰去了。