SpringBoot项目整合jwt和redis完成登录token校验(含原理解析)

SpringBoot项目整合jwt和redis完成登录token校验(含原理解析)


步骤:依次复制粘贴即可

1、引入依赖

		<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2、工具类(jwt 原生带的生成token和校验token真伪的工具类)

/**
 * 这里是生成token的工具类
 */
@Slf4j
public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);
        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);
        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }
}

3、登录接口(演示生成token)

/**
     * 用户登录方法。
     *
     * @param userLoginRequest 用户登录请求的数据载体,包含登录所需的信息,如用户名和密码。
     * @return 返回登录操作的响应,包含用户信息和JWT令牌。
     */
    @PostMapping("/login")
    public BaseResponse<LoginUserVo> login(@RequestBody UserLoginRequest userLoginRequest) {
        // 1. 检查请求体是否为空。如果是空,则抛出参数错误异常。
        if (userLoginRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        // 2. 调用后端服务进行登录操作。该服务将验证用户凭据。
        LoginUserVo loginUserVo = userBackendService.login(userLoginRequest);

        // 3. 登录成功后,准备生成JWT令牌。首先创建一个用于存放令牌信息的HashMap。
        HashMap<String, Object> map = new HashMap<>();
        map.put("userId", loginUserVo.getId()); // 将用户ID加入map,用于生成JWT中的claims。

        // 4. 调用后端服务的getJwtToken方法,生成JWT令牌。
        String jwtToken = userBackendService.getJwtToken(adminSecretKey, adminTtl, map);

        // 5. 将生成的JWT令牌设置到登录用户信息对象(LoginUserVo)中。
        loginUserVo.setJwt(jwtToken);

        // 6. 返回成功的响应,包含用户信息和JWT令牌。
        return ResultUtils.success(loginUserVo);
    }

4、实现类

String getJwtToken(String adminSecretKey, Long adminTtl, HashMap<String, Object> map);

@Override
    public String getJwtToken(String adminSecretKey, Long adminTtl, HashMap<String, Object> map) {
        String jwtToken = JwtUtil.createJWT(adminSecretKey, adminTtl, map);
        //拿到用户的id
        String userId = map.get("userId").toString();
        redisTemplate.opsForValue().set(adminTokenKey + "-" + userId, jwtToken, 7200, TimeUnit.SECONDS);
        return jwtToken;
    }

5、拦截器(拦截器的作用是带token访问其他接口做校验的,利用了redis 和jwt本身的校验)

/**
 * JWT验证token是否合法的预处理方法。
 * 
 * @param request  传入的HTTP请求对象,用于获取请求头中的信息。
 * @param response HTTP响应对象,用于设置响应状态码。
 * @param handler  处理器对象,用于判断请求是否是一个方法处理器。
 * @return 返回布尔值,表示是否继续执行下一个拦截器或处理器。
 * @throws IOException 可能由于I/O操作引发异常。
 */
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
    // 1. 判断处理器是否为方法处理器的实例,如果不是,则直接放行。
    if (!(handler instanceof HandlerMethod)) {
        return true;
    }

    // 2. 从请求头中获取"Authorization"字段,此处存放着JWT令牌。
    String token = request.getHeader("Authorization");

    try {
        // 3. 使用JwtUtil的parseJWT方法解析token,并获取其声明(Claims)。
        Claims claims = JwtUtil.parseJWT(adminSecretKey, token);

        // 4. 从Claims中获取用户ID,这是存放在token中的一个自定义声明。
        String userId = claims.get("userId").toString();

        // 5. 检查Redis中是否存在以adminTokenKey + "-" + userId为key的数据,以验证token的有效性。
        Boolean redisResult = redisTemplate.hasKey(adminTokenKey + "-" + userId);

        // 6. 如果在Redis中找不到对应的key,说明token无效。
        if (!redisResult) {
            response.setStatus(401); // 设置HTTP状态码为401,表示未授权。
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "Invalid Token"); // 抛出业务异常。
        }

        // 7. 如果验证通过,则返回true,继续执行请求。
        return true;
    } catch (Exception e) {
        response.setStatus(401); // 验证过程中出现异常也设置HTTP状态码为401。
        // 抛出业务异常。
        throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "Invalid Token");
    }
}

6、配置类(指定哪些路径拦截生效)

@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    JwtTokenAdminHandler jwtTokenAdminHandler;

    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("自定义拦截器开始");
        registry.addInterceptor(jwtTokenAdminHandler)
                .addPathPatterns("/user/**")
                .addPathPatterns("/order/**")
                .excludePathPatterns("/user/login");
    }
}

7、解析说明

1JWT (JSON Web Tokens): JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表达方式。JWT的结构包括三个部分:Header(头部)、Payload(载荷)、Signature(签名)。在这个案例中,JWT用于创建一个用户登录后的令牌,该令牌包含用户的一些基本信息,并且可以被服务器验证其真伪。

2Redis: Redis是一个高性能的键值对数据库,用于缓存数据。在这个案例中,Redis用来存储JWT令牌,以便于对用户的后续请求进行快速的Token验证。

3、拦截器: 拦截器用于在用户发起请求时拦截请求,并验证JWT Token的有效性。如果Token有效,则请求继续执行;如果无效,则返回错误信息。

8、步骤总结

1、引入JWT依赖: 通过在项目的pom.xml中添加JWT依赖,使得项目能够使用JWT相关的功能。

2、实现JWT工具类: 创建一个JwtUtil类,用于生成和解析JWT。createJWT方法用于创建一个新的JWT,而parseJWT方法用于验证和解析一个已存在的JWT3、登录接口实现: 在登录接口中,首先验证用户的登录信息,然后使用JwtUtil生成JWT。这个JWT会被返回给用户,并存储在Redis中,用于后续的请求验证。

4、实现Token生成方法: 在服务类中实现getJwtToken方法,该方法调用JwtUtil生成JWT,并将其存储在Redis中。

5、拦截器实现: 实现一个拦截器,用于检查请求的Token。如果Token存在且有效,请求继续执行;如果无效,则返回错误信息。

6、配置拦截器:SpringBoot的配置类中配置拦截器,指定哪些路径需要进行Token验证。

通过这个流程,您的SpringBoot应用能够有效地处理用户身份验证和授权,保障应用的安全性。这种方式既利用了JWT的便捷和安全性,又结合了Redis的高效存储能力,确保了整个认证过程的高效和安全。(图片、解析和原理均由AI生成)
JWTRedis 都可以用于实现 Token 校验,但具体的实现方式有所不同,下面分别介绍一下它们的优劣。 JWT 实现 Token 校验的优点: 1. 无状态:JWT 实现 Token 校验时不需要在服务器端存储任何信息,这样可以减轻服务器的负担,提高系统的性能。 2. 轻量级:JWT Token 的体积较小,可以减少网络传输的开销。 3. 可扩展性:JWT 可以很容易地与其他技术集成,如 OAuth、SAML 等,可以实现单点登录和跨域身份验证。 JWT 实现 Token 校验的缺点: 1. 无法注销:一旦颁发了 JWT,就无法撤回或注销,除非等到 JWT 过期或更改密钥。 2. 密钥管理困难:JWT安全性依赖于密钥的保护,如果密钥泄露或被攻击者获取,就可能会导致系统的安全问题。 3. 数据量较大:由于 JWT了用户信息和签名等信息,因此数据量较大,可能会影响网络传输的性能。 Redis 实现 Token 校验的优点: 1. 数据存储在内存中:Redis 的数据存储在内存中,可以快速地对 Token 进行校验。 2. 高可用性:Redis 支持数据复制和故障转移,可以保证系统的高可用性。 3. 可扩展性:Redis 支持分布式部署和数据复制,可以实现数据的高可用和扩展性。 Redis 实现 Token 校验的缺点: 1. 数据存储成本高:Redis 的数据存储在内存中,如果数据量过大,可能会导致内存不足的问题。 2. 数据持久化成本高:Redis 的数据持久化可能会影响系统的性能,尤其是在大数据量或高并发的情况下。 3. 高可用性需要额外配置:Redis 的高可用性需要额外的配置和维护,可能会增加运维的成本。 综上所述,JWTRedis 都有其优点和缺点,具体的选择应根据具体的场景和需求进行考虑。如果需要实现无状态的 Token 校验并且数据量较小,可以考虑使用 JWT;如果需要实现高可用的 Token 校验并且数据量较大,可以考虑使用 Redis
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值