<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
dependency>
<dependency>
<groupId>com.auth0groupId>
<artifactId>java-jwtartifactId>
dependency>
通过该工具类来生成Token,以及从请求头中获取token来获取当前用户的信息。
package com.tick.tack.utils;
import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.tick.tack.manager.entity.User;
import com.tick.tack.manager.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Component
public class TokenUtils {
// 日志类
private static final Logger log= LoggerFactory.getLogger(TokenUtils.class);
private static IUserService staticUserService;
@Resource
private IUserService userService;
@PostConstruct
public void setUserService() {
//必须加@Component注解后才会执行该段代码,在spring容器中初始化
staticUserService = userService;
}
public static String getToken(String userId, String password) {
return JWT.create().withAudience(userId) //将userId保存到token里面,作为载荷
.withExpiresAt(DateUtil.offsetHour(new Date(), 2))//2小时候过期
.sign(Algorithm.HMAC256(password));//以password作为token的密钥
}
/**
* 获取当前登录的用户信息
*/
public static User getCurrentUser() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 从请求头中获取token信息
String token = request.getHeader("token");
if (StringUtils.isNotBlank(token)) {
String userAccount = JWT.decode(token).getAudience().get(0);
return staticUserService.queryUserByAccount(userAccount);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
AuthAccess是一个自定义的注解,在拦截器中判断如果方法上有加入该注解,则放行,不校验token
package com.tick.tack.common.interceptor;
import cn.hutool.jwt.JWTException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.tick.tack.common.Constants;
import com.tick.tack.config.AuthAccess;
import com.tick.tack.exception.ServiceException;
import com.tick.tack.manager.entity.User;
import com.tick.tack.manager.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JWTInterceptor implements HandlerInterceptor {
@Autowired
private IUserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("token");
//如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
} else {
// 判断是否为自定义注解AuthAccess,如果是,就不校验了,直接放行
HandlerMethod h = (HandlerMethod) handler;
AuthAccess authAccess = h.getMethodAnnotation(AuthAccess.class);
if (authAccess != null) {
return true;
}
}
//执行认证
if (StringUtils.isBlank(token)) {
throw new ServiceException(Constants.CODE_401, "无token,请重新登录");
}
//获取token中的user id,验证是否合法
String userAccount;
try {
userAccount = JWT.decode(token).getAudience().get(0);
} catch (JWTException jwt) {
throw new ServiceException(Constants.CODE_401, "token验证失败");
}
//根据token中的用户账号查询数据库信息
User user = userService.queryUserByAccount(userAccount);
if (user == null) {
throw new ServiceException(Constants.CODE_401, "用户不存在,请重新登录");
}
//用户密码加签验证token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
//通过 verifier.verify() 方法检验 token,如果token不符合则抛出异常
jwtVerifier.verify(token);
} catch (Exception e) {
throw new ServiceException(Constants.CODE_401, e.getMessage());
}
return true;
}
}
自定义注解:
package com.tick.tack.config;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}
将拦截器注册到SpringMVC中
package com.tick.tack.config;
import com.tick.tack.common.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
//拦截的路径
.addPathPatterns("/**") //拦截所有请求,通过判断token是否合法来决定是否需要登录
//排除不校验的接口
.excludePathPatterns(
"/loginUser", //排除路径的时候不用考虑全局上下文context-path
"/register",
//Swagger页面拦截取消
"/swagger-resources/**", "/webjars/**", "/v3/**", "/swagger-ui.html/**", "doc.html", "/error");
}
//考虑到UserService,此处需要注入一下
@Bean
public JWTInterceptor jwtInterceptor() {
return new JWTInterceptor();
}
}
@Data
public class LoginUser {
// 登入用户名
private String userAccount;
// 登录密码
private String password;
}
@Data
public class TickToken {
// 用户名
private String userAccount;
// 密码
private String password;
// token
private String token;
// 到期时间
private Date expireTime;
}
@RestController
public class LoginController {
@Autowired //按照类型注入
@Qualifier(value = "loginServiceImpl")
private ILoginService ILoginService;
//登录系统
@PostMapping("/loginUser")
public Result loginSystem(@RequestBody LoginUser user) {
if (StringUtils.isBlank(user.getUserAccount()) || StringUtils.isBlank(user.getPassword())) {
return Result.error(Constants.CODE_400, "参数错误");
}
TickToken tickToken = ILoginService.loginSystem(user);
return Result.success(tickToken);
}
}
public TickToken loginSystem(LoginUser user) {
User one = userService.queryUserByAccount(user.getUserAccount());
if (one != null && one.getPassword().equals(user.getPassword())) {
TickToken tickToken = new TickToken();
//生成token信息并返回
String token = TokenUtils.getToken(one.getUserAccount(), one.getPassword());
tickToken.setToken(token);
// 设置过期时间:当前时间两小时以后
tickToken.setExpireTime(DateUtil.offsetHour(new Date(),2));
// 处理用户的菜单信息,在登录的时候返回给用户
List<Menu> roleMenus = getRoleMenus(one);
//tickToken.setMenus(roleMenus);
return tickToken;
} else {
throw new ServiceException(Constants.CODE_600, "用户名或密码错误");
}
}
@RestController
@RequestMapping("/demo")
public class DemoController {
@GetMapping("/{id}")
//@AuthAccess
public Result getUser(@PathVariable("id") Integer id) {
User user = new User(1, "zhangSan");
return Result.success(user);
}
}
该token是基于账户和密码来生成的一串字符串,并指定了过期时间,假如登录请求是在A机器实现,下一次请求在经过负载均衡后负载到B机器,B机器也可对其验证,因为token已经包括了全部的验证信息,服务器不保存相关信息,这样在分布式环境下也可正常使用。