//参照链接:SpringBoot 集成token实践详解-CSDN博客
spring boot中登录的验证与授权
1.引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
2.admin表
数据库创建admin表,要有账号(account)、密码(password)、角色(role)
创建实体类
@Data
public class Admin {
private int adminId;
private String account;
private String password;
private String role;
}
3.JwtUtil
Jwt具有产生token和破解token的作用。
加密:使用Jwt工具类将用户的账号、角色、时间封装起来,根据某种加密算法进行加密。
解密:对加密过后的信息使用DecodedJWT中的getClaim方法进行解密。
@Slf4j
@Component
public class JwtUtil {
@Value("${token.privateKey}")
private String privateKey;
/**
* 加密token.
*/
public String getToken(String userId, String userRole) {
log.info("输入参数 userId为 : "+userId);
log.info("输入参数 userRole为 : "+userRole);
//将用户信息、用户角色和创建时间封装加密
String token = JWT
.create()
.withClaim("userId" ,userId)
.withClaim("userRole",userRole)
.withClaim("timeStamp", System.currentTimeMillis())
.sign(Algorithm.HMAC256(privateKey));
return token;
}
/**
* 解析token.
*/
public Map<String, String> parseToken(String token) {
HashMap<String, String> map = new HashMap<>();
DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(privateKey))
.build().verify(token);
//对封装的用户信息进行解密
Claim userId = decodedjwt.getClaim("userId");
Claim userRole = decodedjwt.getClaim("userRole");
Claim timeStamp = decodedjwt.getClaim("timeStamp");
//将解密的用户信息放到map集合中,返回集合
map.put("userId", userId.asString());
map.put("userRole", userRole.asString());
map.put("timeStamp", timeStamp.asLong().toString());
return map;
}
}
在application.yml中添加参数,设置加密方式、年轻token时间和老年token时间
token:
privateKey: 'fdasfgdsagaxgsregdfdjyghjfhebfdgwe45ygrfbsdfshfdsag'
yangToken: 1000000
oldToken: 3000000000
4.配置拦截路径
AuthWebMvcConfigurer实现WebMvcConfigurer接口;
addPathPatterns(需要拦截的路径)
excludePathPatterns(不需要拦截的路径)
/*
* 配置拦截路径
* */
@Configuration
public class AuthWebMvcConfigurer implements WebMvcConfigurer {
@Autowired
AuthHandlerInterceptor authHandlerInterceptor;
/**
* 给除了 /login 的接口都配置拦截器,拦截转向到 authHandlerInterceptor
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authHandlerInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/api/user/login");
}
}
5.异常处理
TokenAuthExpiredException继承RuntimeException类
public class TokenAuthExpiredException extends RuntimeException{
}
6.拦截器
所有的接口请求均需要通过拦截器的拦截,除了配置拦截路径中excludePathPatterns("/api/user/login");不需要经过拦截器。
AuthHandlerInterceptor实现HandlerInterceptor接口;
@Slf4j
@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {
@Autowired
JwtUtil jwtUtil;
@Value("${token.privateKey}")
private String privateKey;
@Value("${token.yangToken}")
private Long youngToken;
@Value("${token.oldToken}")
private Long oldToken;
/**
* 权限认证的拦截操作.
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
log.info("=======进入拦截器========");
// 如果不是映射到方法直接通过,可以访问资源.
if (!(object instanceof HandlerMethod)) {
return true;
}
//为空就返回错误
String token = httpServletRequest.getHeader("token");
if (null == token || "".equals(token.trim())) {
log.info("=========验证token失败,原因:请求的head没有token参数");
return false;
}
log.info("=========验证====从请求的head中获取到token:" + token);
Map<String, String> map = jwtUtil.parseToken(token);
log.info("=========验证====解析token数据");
String userId = map.get("userId");
String userRole = map.get("userRole");
log.info("=========验证====解析token数据 userId:"+userId);
log.info("=========验证====解析token数据 userRole:"+userRole);
long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp"));
//1.判断 token 是否过期
//年轻 token
//token 距离发布token 2 个小时内的token为新生token
if (timeOfUse < youngToken) {
log.info("年轻 token");
log.info("token时间失效");
return true;
}
//老年 token 就刷新 token
else if (timeOfUse >= youngToken && timeOfUse < oldToken) {
log.info("token刷新");
httpServletResponse.setHeader("token",jwtUtil.getToken(userId,userRole));
}
//过期 token 就返回 token 无效.
else {
throw new TokenAuthExpiredException();
}
log.info("判断当前登录的用户权限"+userRole+";");
//2.角色匹配.
if ("user".equals(userRole)) {
log.info("========user账户============");
return true;
}
if ("admin".equals(userRole)) {
log.info("========admin账户============");
return true;
}
return false;
}
}
如果token过期, throw new TokenAuthExpiredException();
下面的代码是我暂未使用过的token过期,没用上
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 用户 token 过期
* @return
*/
@ExceptionHandler(value = TokenAuthExpiredException.class)
@ResponseBody
public String tokenExpiredExceptionHandler(){
log.warn("用户 token 过期");
return "用户 token 过期";
}
}
7.测试
login模块查询数据库中的用户信息,进行加密,发放token
testToken模块就是模拟接口请求
@RestController
@Slf4j
@CrossOrigin
@RequestMapping("api/user")
public class AdminController {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private AdminService adminService;
@PostMapping("/login")
public String login(HttpServletRequest request, HttpServletResponse response){
String account = request.getParameter("account");
String password = request.getParameter("password");
String role = request.getParameter("role");
QueryWrapper<Admin> wrapper = new QueryWrapper<>();
wrapper.eq("account",account)
.eq("role",role)
.eq("password",password);
Admin admin = adminService.getOne(wrapper);
if (admin != null){
return jwtUtil.getToken(account,role);
}
return "false";
}
@RequestMapping("/test-token")
public Map<String,String> testToken(HttpServletRequest request){
log.info("==拦截器执行完成后,进入此方法,开始======TestToken验证============");
String token = request.getHeader("token");
Map<String,String> vals= jwtUtil.parseToken(token);
//String json=JSON.toJSONString(vals);
//log.info("========json============"+json);
log.info("==拦截器执行完成后,进入此方法,结束======TestToken验证============");
return vals;
}
}
输入账号密码角色,产生token
如果不带token是无法使用接口的
Header中携带token后即可使用接口