JWT:Json web token 是为了在网络应用环境间传递声明而执行的一种基于JSON传输格式的开放标准,可实现无状态、分布式的Web应用授权。
缺点:用户主动注销,服务器不能让token主动失效。
认证过程大致如下:
用户登陆服务器,服务端验证用户账号密码,使用secret生成JWT令牌【一般还会设置过期时间,在生成token的时候指定】,然后将令牌返回给客户端。
客户端访问服务端的时候,在请求头中带上这个令牌,服务端使用secret去验证令牌是否合法,合法则让用户访问服务器接口,不合法则拒绝。
服务器并不保存token,生成的JWT令牌是按照某种规则生成的,它可以包含用户信息,比如我们把用户id或带有权限标识的用户JSON数据[做权限校验]存到JWT令牌,他第二次带着JWT令牌登录时,解析JWT令牌得到他的用户id,此时拿着用户id去查询那个用户信息,得到账号密码进行校验。
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
//用来跳过验证的PassToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
//需要登录才能进行操作的注解LoginToken
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginToken {
boolean required() default true;
}
@Data
@ApiModel("登陆用户信息")
public class TSBaseUser {
@ApiModelProperty("id")
private String id;
@ApiModelProperty("用户名")
private String userName;
@ApiModelProperty("密码")
@JsonIgnore
private String password;
}
public interface IBaseUserService extends IService<TSBaseUser> {
TSBaseUser login(TSBaseUser loginUser);
TSBaseUser getUser(String id);
}
@Service
public class BaseUserServiceImpl extends ServiceImpl<BaseUserMapper, TSBaseUser> implements IBaseUserService {
@Override
public TSBaseUser login(TSBaseUser loginUser) {
LambdaQueryWrapper<TSBaseUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(TSBaseUser::getUserName , loginUser.getUserName())
.eq(TSBaseUser::getPassword , loginUser.getPassword());
TSBaseUser baseUser = baseMapper.selectOne(wrapper);
if (ObjectUtils.isNotEmpty(baseUser)){
return baseUser;
}
return null;
}
@Override
public TSBaseUser getUser(String id) {
return baseMapper.selectById(id);
}
}
@Service
public class TokenService {
/**
* 过期时间5分钟
*/
private static final long EXPIRE_TIME = 5 * 60 * 1000;
public String getToken(TSBaseUserVo user) {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
String token="";
token= JWT.create().withAudience(user.getId()) // 将 user id 保存到 token 里面
.withExpiresAt(date) //五分钟后token过期
.sign(Algorithm.HMAC256(user.getPassword())); // 以 password 作为 token 的密钥
return token;
}
}
public class BaseUserInfo {
private static final ThreadLocal<Map<String, String>> THREAD_LOCAL = new ThreadLocal<>();
//判断线程map是否为空,为空就添加一个map
public static Map<String, String> getLocalMap() {
Map<String, String> map = THREAD_LOCAL.get();
if (map == null) {
map = new HashMap<>(10);
THREAD_LOCAL.set(map);
}
return map;
}
//把用户信息添加到线程map中
public static void set(String key, String name) {
Map<String, String> map = getLocalMap();
map.put(key, name);
}
//获得线程map中的数据
public static String get(String key) {
Map<String, String> map = getLocalMap();
return map.get(key);
}
//把用户信息添加到线程map中
public static void setUser(String username, String userId) {
Map<String, String> map = getLocalMap();
map.put("username", username);
map.put("userId", userId);
}
// 获取登陆用户名
public static String getUserName() {
Map<String, String> map = getLocalMap();
return map.get("username");
}
// 获取登陆用户id
public static String getUserId() {
Map<String, String> map = getLocalMap();
return map.get("userId");
}
}
public class JwtInterceptor implements HandlerInterceptor{
@Autowired
private IBaseUserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
//检查是否有passtoken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
// 检查有没有需要用户权限的注解
if (method.isAnnotationPresent(LoginToken.class)) {
LoginToken loginToken = method.getAnnotation(LoginToken.class);
if (loginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
TSBaseUser user = userService.getUser(userId);
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
// 存储登陆用户信息
BaseUserInfo.setUser(user.getUserName() , userId);
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
//注册TestInterceptor拦截器
// InterceptorRegistration registration = registry.addInterceptor(jwtInterceptor());
// registration.addPathPatterns("/**"); //添加拦截路径
// registration.excludePathPatterns( //添加不拦截路径
// "/**/*.html", //html静态资源
// "/**/*.js", //js静态资源
// "/**/*.css", //css静态资源
// "/**/*.woff",
// "/**/*.ttf",
// "/swagger-ui.html",
// "/doc.html/**"
// );
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
package com.qyzb.handle;
import com.qyzb.base.result.R;
import com.qyzb.base.result.ResultCode;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* @title: GlobalExceptionHandler
* @Author gjt
* @Date: 2020-12-22
* @Description:
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public R<?> handleException(Exception e) {
return R.failure(ResultCode.ERROR);
}
}
@RestController
@RequestMapping("/login")
@Api(value = "登陆", tags = "登陆")
public class LoginController {
@Resource
private IBaseUserService baseUserService;
@Resource
private TokenService tokenService;
@Resource
private RedisCache redisCache;
@RequestMapping(value = "" , method = {RequestMethod.GET,RequestMethod.POST})
@ApiOperation("登陆")
@PassToken
public R<?> login(TSBaseUser loginUser){
// 获取登陆用户信息
TSBaseUser user = baseUserService.login(loginUser);
// 从jwt存储的用户信息获取 BaseUserInfo.getUserName()
if (ObjectUtils.isEmpty(user)){
return R.failure(ResultCode.of("用户名或密码错误"));
}else {
Map<String,Object> data = new HashMap<>();
data.put("user",user);
data.put("token",tokenService.getToken(user));
return R.success(data);
}
}
}
@Data
@ApiModel("返回结果")
public class R<T> {
/**
* 错误码
*/
@ApiModelProperty("错误码")
private Integer code;
/**
* 错误消息
*/
@ApiModelProperty("错误消息")
private String msg;
/**
* 内容
*/
@ApiModelProperty("内容")
private T data;
public static <U> R<U> toResult(int rows) {
return rows > 0 ? R.success() : R.failure();
}
public static <U> R<U> success() {
return of(SUCCESS, null, null);
}
public static <U> R<U> success(U data) {
return of(SUCCESS, null, data);
}
public static <U> R<U> failure() {
return of(FAILURE, null, null);
}
public static <U> R<U> failure(ResultCode resultCode) {
return of(resultCode, null, null);
}
public static <U> R<U> failure(ResultCode resultCode, String detail) {
return of(resultCode, detail, null);
}
public static <U, E extends CodeException> R<U> exception(E ex) {
return failure(ex.getCode(), ex.getDetail());
}
public static <U> R<U> of(ResultCode resultCode, String detail, U data) {
R<U> result = new R<>();
result.code = resultCode.code();
if (StringUtils.isEmpty(detail)) {
result.msg = resultCode.message();
} else {
//覆盖消息
result.msg = detail;
}
result.data = data;
return result;
}
}
public enum ResultCode {
SUCCESS(200,"成功"),
FAILURE(500,"失败"),
ERROR(10000,"未知原因出错"),
SERVICE_ERROR(50000,"服务器异常");
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
private Integer code;
private String message;
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
public static ResultCode of(String message) {
if (ObjectUtils.isEmpty(message)) {
throw new RuntimeException("参数错误");
}
for (ResultCode resultCode : values()) {
if (resultCode.message.equals(message)) {
return resultCode;
}
}
return ERROR;
}
}
参考文章:SpringBoot集成JWT实现token验证