SpringBoot+JWT实现登陆token验证并存储用户信息

基于Token的JWT认证

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;

}

用户service

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);
    }
}

JWT生成Token

@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);
    }
}

controller层

@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验证

你可能感兴趣的:(spring,boot,java)