前置工作
controller类
由于是简单介绍,所以不会使用service和mapper。
@RestController
public class UserController {
@GetMapping("/health")
public String healthCheck() {
return "OK";
}
@GetMapping("/login")
public String login() {
Map<String, Object> map = new HashMap<>();
map.put("id",1);
map.put("name","username");
String token = JwtUtil.creatToken(map, JwtProperties.EXPIRET_TIME, JwtProperties.SECRET_KEY);
return "token = " + token;
}
}
jwt配置类
把jwt令牌的签名秘钥和过期时间等解耦出来,下面还会有更好的写法。
public class JwtProperties {
/**
* jwt令牌相关配置
*/
public static final String SECRET_KEY = "secretKey";//签名秘钥
public static final int EXPIRET_TIME = 7;//过期时间,单位:天
}
一、Jwt的使用
Jwt常用来生成token和解析token去实现登录检验功能。
jwt生成的token是一个字符串,由三部分组成,分别是Header、Payload、Signature
本文不会重点介绍jwt的各个部分,对jwt组成感兴趣请参考博客JWT详解-CSDN博客
1.在pom文件中引入依赖
<!--jwt令牌依赖-->
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
2.编写一个 jwt 的 util 类,实现生成token
1)第一种写法
这种写法不会使用jwt配置类。
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Map;
@Component
@Slf4j
public class JwtUtil {
private static final String SECRET_KEY = "secretKey";//签名秘钥
private static final int EXPIRET_TIME = 7;//过期时间,单位为天
/**
* 生成token : header-payload-singature
*/
public static String creatToken(Map<String, Object> map) {
Calendar instance = Calendar.getInstance();
// 默认7天过期
instance.add(Calendar.DATE, EXPIRET_TIME);
String token = JWT.create()
.withPayload(map)//载荷内容 payload
.withExpiresAt(instance.getTime()) // 指定令牌的过期时间
.sign(Algorithm.HMAC256(SECRET_KEY));//签名
return token;
}
/**
* 获取token信息方法
*/
public static Map parseToken(String token) {
// 通过签名生成验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET_KEY)).build();
DecodedJWT verify = jwtVerifier.verify(token);
log.info("payload = {} ", verify.getClaims());
return verify.getClaims();
}
}
这里生成token的秘钥签名和过期时间也可以是由自己传入的,如下:
2)第二种写法
使用配置类的写法。
本人用的这种写法。
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Map;
@Component
@Slf4j
public class JwtUtil {
/**
* 生成token : header-payload-singature
*/
public static String creatToken(Map<String, Object> map, int expiretTime, String secretKey) {
Calendar instance = Calendar.getInstance();
// 默认7天过期
instance.add(Calendar.DATE, expiretTime);
String token = JWT.create()
.withPayload(map)//载荷内容 payload
.withExpiresAt(instance.getTime()) // 指定令牌的过期时间
.sign(Algorithm.HMAC256(secretKey));//签名
return token;
}
/**
* 获取token信息方法
*/
public static Map parseToken(String token, String secretKey) {
// 通过签名生成验证对象
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secretKey)).build();
DecodedJWT verify = jwtVerifier.verify(token);
log.info("payload = {} ", verify.getClaims());
return verify.getClaims();
}
}
如果要使用 jwt 就调用 util 类的静态方法就行。
3.获得token
启动springboot 项目,访问/login 即可获得token返回值。
或者编写test类去获得token。
二、token的登录拦截校验
1.拦截器的功能实现
编写LoginInterceptor 类,去实现HandlerInterceptor 接口,重写preHandle 方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
/**
* 校验jwt(token)
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader("token");
if (token == null) {
throw new Exception("未登录");
}
String secretKey = JwtProperties.SECRET_KEY;
int expiretTime = JwtProperties.EXPIRET_TIME;
//2、校验令牌
try {
log.info("jwt校验: token = {}", token);
Map claims = JwtUtil.parseToken(token, JwtProperties.SECRET_KEY);
//获得负载数据,注意写法
Integer id = Integer.valueOf(claims.get("id").toString());
String name = claims.get("name").toString();
//3、通过,放行
return true;
} catch (Exception exception) {
//4、不通过,响应 401 状态码
response.setStatus(401);
throw new Exception("token检验未通过,exception = " + exception);
}
}
}
2.将拦截器注册进spring配置中
编写配置类,注册拦截器,并排除不需要的拦截地址
@Configuration
@Slf4j
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**") // 其他接口需要token验证
//排除knife4j的接口文档页面
.excludePathPatterns("/**.html")
.excludePathPatterns("/v3/api-docs/**")
//排除登录注册页面
.excludePathPatterns("/login")
.excludePathPatterns("/register");
}
}
3.启动项目,验证拦截是否成功
1)请求头中不添加token,访问/health
显示错误页面,这里可以编写全局异常类进行捕获,本次求简,就没写全局异常类。
2)将访问/login得到的token 加入请求头,再次访问/health
请求成功
自此,token生成和拦截校验结束。
三、拓展:
下面介绍,将jwt配置类解耦出来更好的办法
1.把jwt配置类的属性与application.yml 联系起来。
@Configuration
@ConfigurationProperties(prefix = "jwt")
@Data
public class JwtProperties {
/**
* jwt令牌相关配置
*/
private String secretKey;//签名秘钥
private int expiretTime;//过期时间
private String tokenName;//设置前端传递过来的令牌名称
}
2.application.yml 配置
#jwt(token)配置
jwt:
#签名秘钥
secretKey: secretKey
#过期时间(单位 day)
expiretTime: 7
#设置前端传递过来的令牌名称
tokenName: token
3.jwt的util类不变。
4.token拦截校验类需要更改一点
/**
* jwt令牌的拦截检验(token)
*/
@Component
@Slf4j
public class LoginTokenInterceptor implements HandlerInterceptor {
@Resource
private JwtProperties jwtProperties;
/**
* 校验jwt(token)
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getTokenName());
if (StringUtils.isBlank(token)) {
throw new Exception("未登录");
}
String secretKey = jwtProperties.getSecretKey();
int expiretTime = jwtProperties.getExpiretTime();
String tokenName = jwtProperties.getTokenName();
//2、校验令牌
try {
log.info("jwt校验: token = {}", token);
Map claims = JwtUtil.parseToken(token, jwtProperties.getSecretKey());
Integer id = Integer.valueOf(claims.get("id").toString());
String account = claims.get("account").toString();
//3、通过,放行
return true;
} catch (Exception exception) {
//4、不通过,响应 401 状态码
response.setStatus(401);
throw new Exception("token检验未通过,exception = " + exception);
}
}
}
5.拦截类注册不需要改变
太晚了,明天在写(~_~)。
以上,完成。