Spring MVC使用JWT做token验证

发布于 2020-04-19  390 次阅读


为什么选择JWT?

其最大的优点就是不需要存储介质,比如redis,JWT给我的感觉就是特别轻巧、简单便捷。

JWT是无状态的,token也都是一次性的,如果你想让这个token立即失效,是没办法的,只能等过期。

我也查阅了一下发现会有以下问题

注销登录等场景下token还有效,比如

  • 退出登录 (这个可以清除客户端cookie存储的token)
  • 修改密码 (由于我的JWT加的盐是包含了用户密码,所以这个问题也规避了)
  • 用户的帐户被删除/暂停 (这个正常的话直接删掉token,让用户重新登陆,JWT的话就需要在拦截器里验证token对应的用户时,判断下状态,ps:好像正常也需要每次判断下,我之前都给忽略了)
  • 用户由管理员注销 (这个。。。总不能注销时将用户加到一个集合里,拦截器里每次验证token时都判断下这集合吧,太不好了,真这样的话用Redis搞个黑名单)

综合来说,还是得利用其它介质辅助存储,比如Redis,还是给token加上了状态

JWT最适合的场景是不需要服务端保存用户状态的场景,比如如果考虑到 token 注销和 token 续签等场景的话,没有特别好的解决方案,大部分解决方案都给 token 加上了状态

JWT根据密钥进行加密组成一段字符串作为token,客户端每次请求时带上这个token,后端拦截器取到token后,会取出token包含的用户id等信息,然后用JWT解密验证是否有效即可。

jwt流程

JWT生成的token如下,可以看到由两个"."所隔开,一共是三部分

  • 第一部分称为头部(header), 声明类型以及加密的算法,然后base64加密得到
  • 第二部分称为载荷(payload),存放的就是有效信息,比如签发者,过期时间,标识等, 然后base64加密得到
  • 第三部分称为签证(signature),由 header +payload+secret(盐), 然后base64加密得到
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxMyIsImV4cCI6MTU4NzMxNjQwM30.EFbQK_qocj1kQLt19mJKGulya2o9LQ_jHTRp3p7iCzk 

要配合一些注解,拦截器的使用就不多说了,和下面这篇文章大概一样,最下面放下我所使用的代码,由于以前用的是Redis存储token,而我又不想直接删掉以前的代码,所以就让共存了,根据配置选择是Redis还是JWT。

首先添加JWT的以来,在pom.xml中

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.2</version>
        </dependency>

TokenModel

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TokenModel implements Serializable {
    private SysUserEntity user;
    private String token;
}

JWT token操作类

@Component
public class JwtToken {

    private final SysUserService sysUserService;

    private final RedisUtil redisUtil;

    public JwtToken(RedisUtil redisUtil, SysUserService sysUserService) {
        this.redisUtil = redisUtil;
        this.sysUserService = sysUserService;
    }

    /**
     * 过期时长(秒)
     */
    private static final int EXPIRE_DURATION = 60 * 60;

    /**
     * 生成token
     *
     * @param user
     * @return
     */
    public TokenModel makeToken(SysUserEntity user) {
        //过期时间
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(Calendar.SECOND, EXPIRE_DURATION);
        Date expireTime = nowTime.getTime();
        //生成token
        String token = JWT.create().withAudience(user.getId().toString())
                .withExpiresAt(expireTime)
                .sign(Algorithm.HMAC256(user.getPassword()));
        user.setPassword("");
        return new TokenModel(user, token);
    }

    /**
     * 根据token查询
     *
     * @param token
     * @return
     */
    public TokenModel query(String token) {
        Object tokenStatus = redisUtil.get(token);
        if (tokenStatus != null && "0".equals(tokenStatus.toString())) {
            return null;
        }
        String userId = "";
        try {
            //解密并获得用户id
            userId = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException e) {
            return null;
        }
        if (StringUtils.isEmpty(userId)) {
            return null;
        }
        var user = sysUserService.getById(userId);
        if (user == null) {
            return null;
        }
        // 验证token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
        try {
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            return null;
        }
        return new TokenModel(user, token);
    }
    /**
     * 删除token
     * @param token
     */
    public void deleteToken(String token) {
        //设置该token禁用
        redisUtil.set(token, "0", EXPIRE_DURATION);
    }
}

Redis token操作类

@Component
public class RedisToken {

    /**
     * 过期时长(秒)
     */
    private static final long EXPIRE_DURATION = 60 * 60L;
    private final RedisUtil redisUtil;

    public RedisToken(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    /**
     * @param user
     * @描述: 生成token并放到redis中
     * @返回值:
     * @创建人: LoneKing
     * @创建时间: 19:56 2020/1/12
     */
    public TokenModel makeToken(SysUserEntity user) {
        //主要是根据用户密码和当前时间戳生成token
        String tokenString = SecurityUtil.getMD5(user.getPassword() + System.currentTimeMillis());
        TokenModel tokenModel = new TokenModel();
        tokenModel.setToken(tokenString);
        user.setPassword("");
        tokenModel.setUser(user);
        redisUtil.set(tokenString, tokenModel, EXPIRE_DURATION);
        return tokenModel;
    }

    /**
     * @param token
     * @描述: 获取token Model
     * @返回值:
     * @创建人: LoneKing
     * @创建时间: 19:57 2020/1/12
     */
    public TokenModel query(String token) {
        Object tokenModelJson = redisUtil.get(token);
        if (tokenModelJson == null) {
            return null;
        }
        try {
            return JsonUtil.deserialize(tokenModelJson.toString(), TokenModel.class);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * @param token
     * @描述: 删除token
     * @返回值:
     * @创建人: LoneKing
     * @创建时间: 19:58 2020/1/12
     */
    public void deleteToken(String token) {
        redisUtil.del(token);
    }
}

TokenService

public interface TokenService {
    SysUserEntity queryUser(String token);
    void deleteUser(String token);
    TokenModel makeToken(SysUserEntity user);
}

@Service
public class TokenServiceImpl implements TokenService {
    /**
     * 是否使用JWT
     */
    public static boolean useJwt = true;

    private final RedisToken redisToken;
    private final JwtToken jwtToken;

    public TokenServiceImpl(RedisToken redisToken, JwtToken jwtToken) {
        this.redisToken = redisToken;
        this.jwtToken = jwtToken;
    }


    @Override
    public SysUserEntity queryUser(String token) {
        TokenModel tokenModel = null;
        if (useJwt) {
            tokenModel = jwtToken.query(token);
        } else {
            redisToken.query(token);
        }
        if (tokenModel == null) {
            return null;
        } else {
            return tokenModel.getUser();
        }
    }

    @Override
    public void deleteUser(String token) {
        if (useJwt) {
            jwtToken.deleteToken(token);
        } else {
            redisToken.deleteToken(token);
        }

    }

    @Override
    public TokenModel makeToken(SysUserEntity user) {
        if (useJwt) {
            return jwtToken.makeToken(user);
        } else {
            return redisToken.makeToken(user);
        }

    }
}

拦截器

@Component
public class TokenHandleInterceptor implements HandlerInterceptor {

    private static final String TOKEN = "Token";
    private static final String USER = "User";
    private static final String RANDOM = "Random";

    private final TokenService tokenService;

    public TokenHandleInterceptor(TokenService tokenService) {
        this.tokenService = tokenService;
    }


    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {

        //如果不是映射到方法,直接通过
        if (!(o instanceof HandlerMethod)) {
            return true;
        }

        HandlerMethod handlerMethod = ((HandlerMethod) o);
        Method method = handlerMethod.getMethod();

        //获取方法和控制器上的TokenValid注解

        var methodTokenValid = AnnotationUtils.findAnnotation(method, TokenValid.class) != null;
        var methodNoTokenValid = AnnotationUtils.findAnnotation(method, NoTokenValid.class) != null;
        var controllerTokenValid = AnnotationUtils.findAnnotation(handlerMethod.getBean().getClass(), TokenValid.class) != null;
        var allowValid = (!methodNoTokenValid && controllerTokenValid) || methodTokenValid;
        //如果控制器有注解但方法无 无需token验证
        if (allowValid) {
            //获取请求的token信息
            String token = httpServletRequest.getHeader(TOKEN);
            if (token == null || token.isEmpty()) {
                token = httpServletRequest.getParameter(TOKEN);
            }
            if (token == null || token.isEmpty()) {
                throw new SystemException(ErrorEnum.ERR_100);
            }

            //根据token获取到user
            var user = tokenService.queryUser(token);
            if (user != null) {
                if (!UserStatusEnum.已激活.getValue().equals(user.getStatus())) {
                    throw new SystemException(ErrorEnum.ERR_100, "账户状态为" + UserStatusEnum.getNameByValue(user.getStatus()));
                }
                //将user 添加到 request中,以便后续操作获取user
                httpServletRequest.setAttribute(USER, user);
                return true;
            } else {
                throw new SystemException(ErrorEnum.ERR_100);
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

    }
}