参考教程: JWT认证原理、流程整合springboot实战应用,前后端分离认证的解决方案!
创建JWTUtils工具类
@Component
public class JWTUtils {
private static final String salt = "!hfuw3y*&";
/**
* 生成token
* @param map 传入的参数 如用户名,用户id等信息
* @return
*/
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(salt));
return token;
}
/**
* 验证合法性并获取token信息,如果非法则直接抛异常
* @param token
* @return
*/
public static DecodedJWT verifyToken(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(salt)).build().verify(token);
return verify;
}
/**
* 从jwt中获取userId
*/
public static String getUserIdFromToken(String token){
DecodedJWT verify = JWT.require(Algorithm.HMAC256(salt)).build().verify(token);
return verify.getClaim("userId").asString();
}
}
JWT使用
用户首次访问,请求数据库,进行验证
/**
*登录
* @param request
* @return
*/
@Override
public UserLoginResponse login(UserLoginRequest request) {
//提前设置错误的返回信息
UserLoginResponse response = new UserLoginResponse();
response.setUserId(Convert.toLong(0));//0表示登录失败
//状态码是用户名或密码不正确
response.setCode(StatusCode.USERORPASSWORD_ERRROR.getCode());
response.setMsg(StatusCode.USERORPASSWORD_ERRROR.getMessage());
try {
//查询名字相同的用户
QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name",request.getUsername());
UserEntity user = userDao.selectOne(queryWrapper);
//如果存在名字相同且密码相同的用户则返回
if(user!=null && user.getUuid()>0){
String md5Password = MD5Util.encrypt(request.getPassword());
//密码相同,设置响应信息
if (user.getUserPwd().equals(md5Password)) {
response.setUserId(user.getUuid());
response.setCode(StatusCode.SUCCESS.getCode());
response.setMsg(StatusCode.SUCCESS.getMessage());
}
}
} catch (Exception e) {
log.error("login:",e);
return response;
}
return response;
}
验证通过,服务端使用JWT生成token
- 调用上述登录功能,得到返回结果
- 若结果中的userId等于0,表示登录失败,返回账号或密码错误
- 否则登录成功。将userId填入payload对象中,调用JWTUtils的getToken方法生成令牌,并把令牌写入response中返回。
@RequestMapping("/auth")
public ResponseData generateToken(AuthRequest authRequest){
log.info("用户名:[{}]",authRequest.getUserName());
log.info("密码:[{}]",authRequest.getPassword());
//获取用户传入的用户名和密码,封装成request对象
UserLoginRequest request = new UserLoginRequest();
request.setUsername(authRequest.getUserName());
request.setPassword(authRequest.getPassword());
UserLoginResponse response = userService.login(request); //进行登录
//0表示登录失败
if(response.getUserId()!=0) {
Map<String, String> payload = new HashMap<>();
payload.put("userId", response.getUserId().toString());
//生成JWT的令牌
String token = JWTUtils.getToken(payload);
response.setToken(token);
return new ResponseUtil<UserLoginResponse>().setData(response);
}else{
return new ResponseUtil<>().setErrorMsg("账号或密码错误");
}
}
用户下次请求携带token,需要验签
1. 编写拦截器类,实现preHandle 方法
- 该方法在请求处理之前调用,适合用来验签
- 首先获取请求头中携带的token,然后调用JWTUtils的verifyToken方法进行验证。
- 若方法报错,则通过try-catch模块进行捕获,异常包括签名异常、token过期异常或算法不匹配异常等。
- 将异常状态记录到map中,转为json写入到响应报文中。
- 代码实现:```java
@Slf4j
@Component
public class JWTInteceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("被拦截到了");
//1.获取请求头中的token
String token = request.getHeader("token");
//2.验证token
Map<String,Object> map = new HashMap<>();
try {
JWTUtils.verifyToken(token);//验证令牌
return true;
} catch (SignatureVerificationException e) { //签名异常
map.put("msg", "无效签名");
}catch (TokenExpiredException e){ //token过期异常
map.put("msg", "token过期");
}catch (AlgorithmMismatchException e){ //算法不匹配异常
map.put("msg", "算法不匹配");
}catch (Exception e){
map.put("msg", "无效签名");
}
map.put("state",false); //设置状态
//将map转为json
String json = new ObjectMapper().writeValueAsString(map);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);//写到响应报文中
return false;
}
}
```
2. 编写拦截器配置类
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//原则上拦截所有请求,但需要配置忽略列表
registry.addInterceptor(new JWTInteceptor())
.addPathPatterns("/**")
.excludePathPatterns("/auth")
.excludePathPatterns("/user/register")
.excludePathPatterns("/user/checkName")
.excludePathPatterns("/bus);
}
}
3. 验签通过,放行请求
测试JWT的SSO单点登录
未携带token,直接访问不在忽略列表中的url
用户首次登录返回token