JWT基本使用及常用验证方式对比

传统验证方式

在前后端分离的场景中,包括APP端和WEB端,由于http协议是无状态的,那么用户登录认证之后,再向服务器发送的请求的时候,服务器是不知道这个这个用户是否已经登录认证过了。

  1. 传统的方式是通过cookie和session,用户登录之后将用户信息存储在session中,下次请求的时候cookie里面会携带sessionId,以此来判断用户是否登录过。
    这样做的话,每次用户认证通过之后,服务器都需要保持一份用户信息存在session中,这样登录的用户变多的时候,服务器的开销会很大。
    另一方面,在扩展性方面,由于session是在服务器端的,在搭建分布式服务的话,需要每一台服务器都认证一下,这样显示是不合理的。
  2. 将token存在redis中,这种方案也是大多数实行的方案,这种方案的弊端是,在每一次请求的时候,都需要拦截请求,获取token,然后再判断redis中是否有这个数据,这种方案弊端是对redis的依赖太大了,如果redis宕机了,那么整个项目都不能运行了。

JWT是什么

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT流程大概是,用户登录之后,给用户生成jwt并返回给客户端,客户端每次请求的时候会携带这个jwt,然后再在拦截器中拦截请求,判断当前是否正确或者过期。

JWT机制优缺点

优点:

  1. 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
  2. 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
  3. 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
  4. 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
  5. 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
  6. CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
  7. 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
  8. 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)

缺点:

  1. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  2. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令×××的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  3. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

使用

依赖

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

jwtUtil

@Component
@ConfigurationProperties("config.jwt")
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Slf4j
public class JwtUtil {
    /**
     * 过期时间为一天
     */
    private long expire;
    /**
     * token私钥
     */
    private  String secret;
    private String header;
    /**
     * 生成签名,15分钟后过期
     * @param username
     * @param userId
     * @return
     */
    public String sign(String username,String userId){
        //过期时间
        Date date = new Date(System.currentTimeMillis() + expire* 1000);
        //私钥及加密算法
        Algorithm algorithm = Algorithm.HMAC256(secret);
        //设置头信息
        HashMap<String, Object> header = new HashMap<>(2);
        header.put("typ", "JWT");
        header.put("alg", "HS256");
        //附带username和userID生成签名
        return JWT.create().withHeader(header).withClaim("username",username)
                .withClaim("userId",userId).withExpiresAt(date).sign(algorithm);
    }

    public  boolean verity(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            log.info("================认证成功===============");
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        } catch (JWTVerificationException e) {
            return false;
        }
    }
}

拦截器

@Component
@Slf4j
public class JwtInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 地址过滤
        String uri = request.getRequestURI() ;
        if (uri.contains("/login")){
            return true ;
        }
        String token = request.getHeader(jwtUtil.getHeader());
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtUtil.getHeader());
        }
        log.info("token:{}",token);
        if(StringUtils.isEmpty(token)){
            throw new BizException(jwtUtil.getHeader()+ "不能为空");
        }
        if(!jwtUtil.verity(token)){
            throw new BizException(jwtUtil.getHeader() + "失效,请重新登录");
        }
        return true;
    }
}

配置拦截器和跨域

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Autowired
    private JwtInterceptor jwtInterceptor;
    /**
     * 设置跨域访问
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "TRACE")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器
        registry.addInterceptor(jwtInterceptor);
    }
}

问题:

因为JWT一旦签发,不可撤销,为了防止被盗用,所以设置过期时间比较短,那如果JWT过期了,怎么处理?

在生成JWT的时候,比如设置过期时间为15分钟,同时保存该token到redis中,设置过期时间比如为7天。
当用户在登录后,比如登录后20分钟后,再发送请求的时候,拦截器会判断该token已过期,这时再查一下redis里面有没有这个缓存,如果有的话,代表没有过期,则重新生成JWT返回前端,并且重新刷新redis的过期的时间。
这样就可以解决JWT过期的问题,同时对redis的依赖也没有那么大。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值