javaApi JWT token生成以及验证

JWT简介

JSON Web token简称JWT, 是用于对应用程序上的用户进行身份验证的标记。也就是说, 使用 JWTS 的应用程序不再需要保存有关其用户的 cookie 或其他session数据。此特性便于可伸缩性, 同时保证应用程序的安全。

在身份验证过程中, 当用户使用其凭据成功登录时, 将返回 JSON Web token, 并且必须在本地保存 (通常在本地存储中)。

每当用户要访问受保护的路由或资源 (端点) 时, 用户代理(user agent)必须连同请求一起发送 JWT, 通常在授权标头中使用Bearer schema。后端服务器接收到带有 JWT 的请求时, 首先要做的是验证token。

格式

  • JWT就是一个字符串,经过加密处理与校验处理的字符串,形式为:A.B.C

  • A由JWT头部信息header经过base64加密得到

在这里插入图片描述
B是payload,存放有效信息的地方,这些信息包含三个部分:

  • 标准中注册的声明 (建议但不强制使用)

  • iss: jwt签发者

  • sub: jwt所面向的用户

  • aud: 接收jwt的一方

  • exp: jwt的过期时间,这个过期时间必须要大于签发时间

  • nbf: 定义在什么时间之前,该jwt都是不可用的.

  • iat: jwt的签发时间

  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

  • 公共的声明

  • 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

  • 私有的声明

  • 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

  • 在这里插入图片描述

  • C由A和B通过加密算法得到,用作对token进行校验,看是否有效

  • 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

  • 在这里插入图片描述

流程

在这里插入图片描述

导入依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

jwt工具类

public class AppJwtUtil {

    // TOKEN的有效期一天(S)
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

    // 生产ID
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("heima") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

    /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    public static void main(String[] args) {
        String token = AppJwtUtil.getToken(1102L);
        System.out.println(token);
        Jws<Claims> jws = AppJwtUtil.getJws(token);
        Claims claims = jws.getBody();
        System.out.println(claims);

        Claims claimsBody = AppJwtUtil.getClaimsBody(token);
        System.out.println(claimsBody.get("id"));
        System.out.println(claims.get("id"));

    }

}

生成token

	public String createToken(Long id){
        String token = AppJwtUtil.getToken(id);
        return token;
    }

解析token

	public Integer solutionToken(String token) {
        Claims claimsBody = AppJwtUtil.getClaimsBody(token);
        Object id = claimsBody.get("id");
        return (Integer) id;
    }

controller测试

	@Autowired
    private UserService userService;
    
	@GetMapping("/token/{id}")
    public String createToken(@PathVariable Long id){
        String token = userService.createToken(id);
        return token;
    }

    @PostMapping("/token")
    public Integer solutionToken(@RequestHeader String token){
        Integer integer = userService.solutionToken(token);
        System.out.println(integer);
        return integer;
    }

我在这里用的postman工具进行端口测试
生成token
在这里插入图片描述
解析token
在这里插入图片描述
但是这种方法,需要每次都对token进行验证,比较麻烦,也耗费资源。

filter过滤器验证token

UserThreadLocalUtil工具类

ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

public class UserThreadLocalUtil {
    private final static  ThreadLocal<Integer> userThread =new ThreadLocal<>();

    /**
     * 存储id
     * @param id
     */
    public static void setUser(Integer id){
        userThread.set(id);
    }

    /**
     * 获得id
     * @return
     */
    public static Integer getUser(){
        return userThread.get();
    }
    
	/**
     * 移除
     */
    public static void remove() {
        userThread.remove();
    }
}

filter过滤器

@Order(1)
@WebFilter(filterName = "UserTokenFilter",urlPatterns = "/*")
public class UserTokenFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //得到header中的信息
        String token = request.getHeader("token");
        Integer id=null; //设置id为null
        if (token!=null){ //如果token不为空,解析token,token为空则不处理
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            id = (Integer) claimsBody.get("id");
        }
        if(id!=null){ //判断解析token得出的id是否为null
            UserThreadLocalUtil.setUser((Integer) id);//不为null则加入到线程种
        }//为null不做处理
        filterChain.doFilter(request,response); //放行
    }
}

要想使@WebFilter生效。需要加入一个注解

@SpringBootApplication
@ServletComponentScan //@WebFilter生效
public class TokenApplication {
    public static void main(String[] args) {
        SpringApplication.run(TokenApplication.class,args);
    }
}

测试

@GetMapping("/token")
    public String createToken(){
        Integer user = UserThreadLocalUtil.getUser(); //在线程种读取id
        if (user==null){
            return "错误";
        }
        return "这是一个验证器";
    }

没有token,报错
在这里插入图片描述
有token时,通行
在这里插入图片描述

拦截器验证token

自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented //标记注解
public @interface NoAuthorization {

}

自定义拦截器

@Component
public class UserTokenInterceptor implements HandlerInterceptor {

    @Autowired
    private UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if(!(handler instanceof HandlerMethod)){
            return true;  //放行
        }

        //判断是否有@NoAuthorization注解   如果包含,直接放行,结束
        if (((HandlerMethod) handler).hasMethodAnnotation(NoAuthorization.class)) {
            return true;
        }

        //从请求头中获取token
        String token = request.getHeader("token");
        if(token!=null){
            Integer integer = userService.solutionToken(token);
            if(integer != null){
                //token有效
                //将User对象放入到ThreadLocal中
                UserThreadLocalUtil.setUser(integer);
                return true;
            }
        }

        //token无效,响应状态为401
        response.setStatus(401); //无权限

        return false;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //从ThreadLocal中移除User对象
        System.out.println("移除对象");
        UserThreadLocalUtil.remove();
    }
}

注册拦截器

@Configuration
public class WebConfig implements WebMvcConfigurer {



    @Autowired
    private UserTokenInterceptor userTokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(this.userTokenInterceptor).addPathPatterns("/**");

    }
}

启动测试

需要在生成token的接口上使用自定义注解,不然会无法生成token

@NoAuthorization
    @GetMapping("/token/{id}")
    public String createToken(@PathVariable Long id){
        String token = userService.createToken(id);
        return token;
    }

验证接口

  @GetMapping("/token")
    public String createToken(){
        Integer user = UserThreadLocalUtil.getUser();
        if (user==null){
            return "错误";
        }
        return "这是一个验证器";
    }

不加token
在这里插入图片描述

加token
在这里插入图片描述

  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值