介绍

JWT全称为JSON Web Token。它采用了 JSON 格式来对 Token 进行编码和解码,并携带了更多的信息,例如用户ID、角色、权限等。

JWT共分为三个部分以 . 进行分割。三个部分分别为header(头部)、payload(数据)、signature(签名)

例:

eyJ0eXAiOiJKV1QiLCJjdHkiOiJERVMiLCJ6aXAiOiJIUzI1NiIsImFsZyI6IkhTMjU2In0.
eyJqdGkiOiI5ZTU5NmQ0Zi0wMDNlLTRhZjctOGFkOS1jODU1MmYxY2U2MGIiLCJzdWIiOiLlpJYiLCJleHAiOjE3MzQ5NTI1MTksImlzcyI6IuWGhSIsImlhdCI6MTczNDk1MDcxOSwiYXVkIjoi5o6l5pS25Lq6IiwibmJmIjoxNzM0OTUwNzE4fQ.
snvBg6iY1UgzMFsWS4UeIPYbvB8vfXQkxZ4ul4VM_nU

base64解码后

头部

头部是令牌描述(编码的JSON)一般都不需要我们去手动配置,除非我们对内容进行加密,又想将加密内容的加密告诉前端人员用什么方法解密可以配置cty 参数。当然我们除了下面的几个参数外,还可以自定义一些参数。

  • typ :TYPE 表明这个令牌的类型 (即JWT)

  • cty :CONTENT_TYPE 如果对载荷进行了加密这个字段可用来表明载荷用了哪种加密算法,也可以忽略不填

  • alg :COMPRESSION_ALGORITHM 令牌采用的压缩签名算法

载荷

数据的有效载荷,一般存储用于描述用户信息、权限、过期时间等等。下面介绍一些预定义的声明(也称为公共声明),除下面这几个公共声明外,我们还可以自定义一些其他的声明,(自定义的声明又称为私有声明)通常用于传递敏感信息或特定于应用的数据‌

  • jti :唯一标识、JWT的唯一身份标识‌

  • sub :主题,标识JWT所面向的用户,通常用于验证用户的身份。

  • exp :到期时间,标识JWT的过期时间,超过这个时间后JWT将不再有效。

  • iss :签发者,标识JWT的签发者,通常用于验证JWT的来源。

  • iat :签发时间,标识JWT的签发时间,通常用于记录和验证JWT的生成时间。

  • aud :接受者,标识接收JWT的实体,确保JWT被正确的实体接收。

  • nbf :生效时间,标识JWT生效的时间点,在此之前JWT不会被接受。

签名

通过加密算法对头部信息和有效载荷信息进行加密压缩,确保令牌的内容不被修改

使用

代码使用

引入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>${version}</version>
</dependency>

创建工具类

public class JwtUtil {

    private static final String SECRET = "c2VjcmV0";

    private static final Long EXPIRATION_TIME = 1800000L;

    public static String generateToken(String key) {
        return createToken(key);
    }

    /**
     * 私有方法,用于实际生成 JWT
     *
     * @param subject
     * @return
     */
    private static String createToken(String subject) {
        Claims claims = Jwts.claims()
                .setId(UUID.randomUUID().toString())
                .setSubject(subject)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .setIssuer("gateway-auth")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setNotBefore(new Date(System.currentTimeMillis()));
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.HS256,SECRET)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    private static String createToken(Claims claims) {
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.HS256,SECRET)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    /**
     * 判断令牌是否是系统签发,且是否在使用时间范围内
     * @param token 令牌
     * @return 校验正常 返回 true,校验失败返回false
     */
    public static boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
            if (!isTokenTimeValidate(token)) {
                return false;
            }
        } catch (Exception e) {
            log.error("令牌校验失败,失败令牌信息为:{}", token);
            return false;
        }
        return true;
    }

    /**
     * 判断令牌是否到了刷新时间,如果到了刷新时间返回新令牌,否则返回旧令牌
     * @param token
     * @return
     */
    public static String refreshToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        Date expiration = claims.getExpiration();
        long difference = expiration.getTime()-System.currentTimeMillis();
        if((double)difference/EXPIRATION_TIME<0.3d){
            claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
            return createToken(claims);
        }
        return token;
    }

    public static <T> T getClaimParam(String token, String paraName, Class<T> type) {
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        return claims.get(paraName, type);
    }


    private static Boolean isTokenTimeValidate(String token) {
        Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        if (claims.getNotBefore() != null && !claims.getNotBefore().before(new Date())) {
            return false;
        }
        if (claims.getExpiration() != null && new Date().after(claims.getExpiration())) {
            return false;
        }
        return true;
    }

}

创建Jwt拦截器及在SpringSecurity中使用

WebFlux中使用示例

JwtTokenFilter
@Component
public class JWTTokenFilter implements WebFilter {

    @Autowired
    private RedisUtil<LoginUser> redisUtil;

    public static final String LOGIN_USER="LOGIN_USER:";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 获取http协议请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 获取http协议返回对象
        ServerHttpResponse response = exchange.getResponse();
        //获取请求头里面的令牌参数
        List<String> authorizations = request.getHeaders().get("Authorization");
        //校验令牌参数是否存在。不存在则放行到下一个过滤器
        if(CollectionUtils.isEmpty(authorizations)){
            return chain.filter(exchange);
        }
        // 获取请求的令牌信息
        String authToken = authorizations.get(0);
        // 判断令牌是否为空和令牌作用提示信息与要判断的是否一致
        if(authToken==null||!authToken.startsWith("Bearer ")){
            //放行到下一个过滤器
            return chain.filter(exchange);
        }
        //获取真正的jwtToken信息
        String jwtToken = authToken.substring(7);
        //校验token是否可信
        if(!JwtUtil.validateToken(jwtToken)){
            return chain.filter(exchange);
        }
        //获取token里面的负载信息
        String proof = JwtUtil.getClaimParam(jwtToken, Claims.SUBJECT, String.class);
        String loginUserRedisKey = LOGIN_USER + proof;
        //根据负载信息获取存储redis的登录用户信息
        LoginUser loginUserInfo = redisUtil.get(loginUserRedisKey);
        if(loginUserInfo==null){
            return chain.filter(exchange);
        }
        //判断token是否到了更新过期时间,如果是则返回新的token,不是则返回原有的token
        String newToken = JwtUtil.refreshToken(jwtToken);
        //如果新token和旧token不一致则更新redis存储用户信息的键的过期时间
        if(!jwtToken.equals(newToken)){
            redisUtil.expire(loginUserRedisKey,30, TimeUnit.MINUTES);
        }
        //将token信息返回给前端,让前端存储或更新
        response.getHeaders().add("Authorization","Bearer " + newToken);
        //将登录用户信息放入到请求的上下文中,方便后面的服务层获取当前请求的登录人信息
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUserInfo, authToken, loginUserInfo.getAuthorities());
        return chain.filter(exchange).contextWrite(ReactiveSecurityContextHolder.withAuthentication(authenticationToken));
    }
}
返回对校验异常信息返回进行拦截并返回准确的异常信息,以便前端进行处理
@Slf4j
@Component
public class CustomerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {

    @Autowired
    private RedisUtil<LoginUser> redisUtil;

    public static final String LOGIN_USER="LOGIN_USER:";

    @Override
    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        List<String> authorizations = request.getHeaders().get("Authorization");
        if(CollectionUtils.isEmpty(authorizations)){
            return backAuthFailMsg(response,null);
        }
        String authToken = authorizations.get(0);
        if(authToken==null||!authToken.startsWith("Bearer ")){
            return backAuthFailMsg(response,null);

        }
        String jwtToken = authToken.substring(7);
        if(!JwtUtil.validateToken(jwtToken)){
            return backAuthFailMsg(response,null);
        }
        String proof = JwtUtil.getClaimParam(jwtToken, Claims.SUBJECT, String.class);
        String loginUserRedisKey = LOGIN_USER + proof;
        LoginUser loginUserInfo = redisUtil.get(loginUserRedisKey);
        if(loginUserInfo==null){
            return backAuthFailMsg(response,"Certificate is invalid, please login again");

        }
        String newToken = JwtUtil.refreshToken(jwtToken);
        if(!jwtToken.equals(newToken)){
            redisUtil.expire(loginUserRedisKey,30, TimeUnit.MINUTES);
        }
        return null;
    }

    public Mono<Void> backAuthFailMsg(ServerHttpResponse response, String msg){
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        String errorMsg = "Authorization header is missing or invalid";
        if(msg!=null){
            errorMsg = msg;
        }
        try {
            return response.writeWith(Mono.just(response.bufferFactory().wrap(errorMsg.getBytes("UTF-8"))));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}
在SpringSecurity中进行配置
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {

    @Autowired
    private CustomerAuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private LogoutHandler logoutHandler;

    @Autowired
    private JWTTokenFilter jwtTokenFilter;

    @Autowired
    private AuthUserService authUserService;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        // 禁用请求头传输用户名密码登录请求和表单登录请求
        http.httpBasic().disable().formLogin().disable();
        //允许任何人可以访问请求登录接口,其他接口需要鉴权
        http.authorizeExchange().pathMatchers("/auth/login").permitAll().anyExchange().authenticated()
                //设置鉴权失败异常信息处理点
                .and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                //配置退出登录接口和退出登录操作处理器
                .and().logout().logoutUrl("/logout").logoutHandler(logoutHandler);
        //将自己配置的jwtToken拦截器替换掉原有的认证拦截器
        http.addFilterAt(jwtTokenFilter, SecurityWebFiltersOrder.AUTHENTICATION);
        //开启允许跨域和关闭csrf(跨站请求攻击)安全拦截
        http.cors().and().csrf().disable();
        return http.build();
    }

    @Bean
    public ReactiveAuthenticationManager authenticationManager() {
        UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(authUserService);
        authenticationManager.setPasswordEncoder(passwordEncoder());
        return authenticationManager;
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
@Component
public class AuthUserService implements ReactiveUserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public Mono<UserDetails> findByUsername(String username) {
        return Mono.justOrEmpty(userService.loadUserByUsername(username));
    }
}
登录接口代码
@Slf4j
@Service
public class LocalAuthServiceImpl implements AuthService {

    public static final String INIT_USERNAME="admin";

    public static final String INIT_PASSWORD="123456";

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserService userService;



    @Autowired
    private ReactiveAuthenticationManager authorizationManager;

    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    private AuthenticationFailureHandler failureHandler;



    @Override
    public Mono<Void> authenticate(String username, String password, ServerWebExchange exchange) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                username,
                password
        );
        return authorizationManager.authenticate(authenticationToken)
                .flatMap(authentication -> successHandler.onAuthenticationSuccess(exchange, authentication))
                .onErrorResume(exception -> failureHandler.onAuthenticationFailure(exchange, exception));


    }

    @Override
    public synchronized void initUser(){
        LoginUser loginUser = userService.loadUserByUsername(INIT_USERNAME);
        if(loginUser!=null) {
            return;
        }
        LocalDateTime maxExpireTime = LocalDateTime.parse("9999-12-31T23:59:59");
        loginUser = new LoginUser(INIT_USERNAME,passwordEncoder.encode(INIT_PASSWORD),"初始化管理员","M",maxExpireTime);
        userService.addUser(loginUser.getUser());
    }
}
@Slf4j
@Component
public class AuthenticationFailureHandler  {
    public Mono<Void> onAuthenticationFailure(ServerWebExchange exchange, Throwable exception) {
        ServerHttpResponse response = exchange.getResponse();
        if (log.isWarnEnabled()) {
            log.warn("User {} failed authentication",exchange.getRequest().getQueryParams().getFirst("username"));
        }
        HttpHeaders headers = response.getHeaders();
        headers.add(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
        response.setStatusCode(HttpStatus.PRECONDITION_FAILED);
        String jsonString = JSON.toJSONString(Result.failure(HttpStatus.PRECONDITION_FAILED,exception.getMessage()));
        DataBuffer dataBuffer = null;
        try {
            dataBuffer = response.bufferFactory().wrap(jsonString.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return response.writeWith(Mono.just(dataBuffer));
    }
}

@Slf4j
@Component
public class AuthenticationSuccessHandler  {

    public static final String LOGIN_USER="LOGIN_USER:";

    @Autowired
    private RedisUtil<LoginUser> loginUserRedisUtil;

    public Mono<Void> onAuthenticationSuccess(ServerWebExchange exchange, Authentication authentication) {
        ServerHttpResponse response = exchange.getResponse();
        String simpleUUID = UUID.randomUUID().toString().replace("-", "");
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        loginUserRedisUtil.set(LOGIN_USER+simpleUUID, loginUser,30, TimeUnit.MINUTES);
        String token = JwtUtil.generateToken(simpleUUID);
        response.getHeaders().add("Authorization", "Bearer " + token);
        log.info("User {} login success", authentication.getName());
        response.setStatusCode(HttpStatus.ACCEPTED);
        HttpHeaders headers = response.getHeaders();
        headers.add("Content-Type", "application/json; charset=utf-8");
        String responseJson = JSON.toJSONString(Result.success());
        DataBuffer dataBuffer = null;
        try {
            dataBuffer = response.bufferFactory().wrap(responseJson.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return response.writeWith(Mono.just(dataBuffer)) ;
    }
}

public interface AuthService {
    Mono<Void> authenticate(String username, String password, ServerWebExchange exchange);
}



@Slf4j
@Service
public class LocalAuthServiceImpl implements AuthService {

    public static final String INIT_USERNAME="admin";

    public static final String INIT_PASSWORD="123456";

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private UserService userService;



    @Autowired
    private ReactiveAuthenticationManager authorizationManager;

    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    private AuthenticationFailureHandler failureHandler;



    @Override
    public Mono<Void> authenticate(String username, String password, ServerWebExchange exchange) {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                username,
                password
        );
        return authorizationManager.authenticate(authenticationToken)
                .flatMap(authentication -> successHandler.onAuthenticationSuccess(exchange, authentication))
                .onErrorResume(exception -> failureHandler.onAuthenticationFailure(exchange, exception));


    }

}

退出登录的示例代码这里就不在写了直接删除redis的登录用户信息即可,并让前端也删除保存的token

HttpWeb使用示例

下面的代码只写基础框架,不写具体逻辑

JwtTokenFilter
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

   

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
         //从请求头获取token信息并进行校验
         //...
         //校验逻辑
         //...
         //校验结束并获取当前登录人的信息
         //当前登录用户信息及权限封装SpringSecurity的上下文中方便后面获取登录人的信息
          UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
         authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
         log.debug("authenticated user:{}", account);
         SecurityContextHolder.getContext().setAuthentication(authentication);
         // 放行到下一个过滤器
        filterChain.doFilter(request, response);
    }
}
返回对校验异常信息返回进行拦截并返回准确的异常信息,以便前端进行处理
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setHeader("content-type","application/json;charset=UTF-8");
        String msg = "请求访问:"+request.getRequestURI()+",认证失败,无法访问系统资源";
        ResultBean<String> error = ResultBean.error(401,"fail",msg);
        response.getWriter().write(JSON.toJSONString(error));
    }
}
SpringSecurity配置
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Lazy
    @Autowired
    private UserServiceImpl userServiceImpl;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        if(enabled) {
            //定义哪些URL需要被保护、定义哪些不需要被保护
            http.authorizeRequests()
                    //设置未登录用户可以访问的页面
                    .antMatchers("/auth/login").anonymous()
                    // 任何请求,登录后可以访问
                    .anyRequest().authenticated()
                    //禁止session认证
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    //设置登录异常处理类
                    .and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler())
                    .and().csrf().disable();    // 关闭csrf防护
            http.cors(); // 设置允许跨域
            http.logout().logoutUrl("/api/power/v4/user/logout").logoutSuccessHandler(customerLogoutSuccessHandler());
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
         
        }
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceImpl).passwordEncoder(passwordEncoder);
    }

    @Bean
    public AuthenticationEntryPointImpl unauthorizedHandler() {
        return new AuthenticationEntryPointImpl();
    }

}
登录接口代码
  @PostMapping("/login")
    @ApiOperation("用户登录接口")
    public ResultBean<String> login(@RequestBody UserForm userForm,  HttpServletResponse response){
        return ResultBean.success(userService.login(userForm,response));
    }
@Lazy
@Autowired
private AuthenticationManager authenticationManager;


public String login(UserForm userForm,  HttpServletResponse response) {
        String username = userForm.getUsername();
        String password = userForm.getPassword();
        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        redisTemplate.opsForHash().put("LOGIN_USER_INFO",loginUser.getUser().getIuid(),loginUser);
        User user = loginUser.getUser();
        DefaultClaims claims = new DefaultClaims();
        claims.setSubject(user.getAccount());
        String token = jwtTokenUtil.generateToken(claims);
        response.setHeader(tokenHeader,token);
        return loginUser.getUser().getIuid();
    }

签名算法介绍及使用示例

HS签名算法

HS256(HMAC using SHA-256)是一种基于哈希函数SHA-256和密钥的消息认证码(HMAC)算法。使用相同的秘钥来进行签名和验证。所以它b不是一种对称加密算法,而是一种用于验证消息完整性和身份验证的算法。

特点

  • 基于哈希:HS256使用SHA256哈希算法来生成签名

  • 对称秘钥:HS256使用相同的秘钥来签名和验证JWT。这意味着双方必须共享相同的秘钥

  • 安全性:由于秘钥的私密性,只有拥有相同密钥的一方才能验证JWT的真实性。

  • 应用场景:通常用于客户端和服务器之间的认证和授权过程,特别是无状态的应用程序。

使用示例

@Slf4j
public class JwtUtil {
    // 配置签名验证秘钥
    private static final String SECRET = "c2VjcdmV0";

    private static final Long EXPIRATION_TIME = 1800000L;

    public static String generateToken(String key) {
        return createToken(key);
    }

    /**
     * 私有方法,用于实际生成 JWT
     *
     * @param subject
     * @return
     */
    private static String createToken(String subject) {
        Claims claims = Jwts.claims()
                .setId(UUID.randomUUID().toString())
                .setSubject(subject)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .setIssuer("gateway-auth")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setNotBefore(new Date(System.currentTimeMillis()));
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.HS256,SECRET)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    private static String createToken(Claims claims) {
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.HS256,SECRET)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    /**
     * 判断令牌是否是系统签发,且是否在使用时间范围内
     * @param token 令牌
     * @return 校验正常 返回 true,校验失败返回false
     */
    public static boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
            if (!isTokenTimeValidate(token)) {
                return false;
            }
        } catch (Exception e) {
            log.error("令牌校验失败,失败令牌信息为:{}", token);
            return false;
        }
        return true;
    }

    //测试
    public static void main(String[] args) {
        String token = JwtUtil.generateToken("123456");
        System.out.println(token);
        System.out.println(JwtUtil.validateToken(token));
    }
//结果输出
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJhNGViMGRjYy0xZDdkLTQ3MDMtODJiMC1kZDY2NGQ1MDYyOTUiLCJzdWIiOiIxMjM0NTYiLCJleHAiOjE3MzU4OTIyODksImlzcyI6ImdhdGV3YXktYXV0aCIsImlhdCI6MTczNTg5MDQ4OSwibmJmIjoxNzM1ODkwNDg5fQ.dE4GG1NIzLYsC7gov02JvqTzhQClwWAJ6Ti458AQnM8
true

}

HS256‌、HS384、HS512的区别

‌HS384、HS512与HS256的主要区别在于它们使用的哈希算法不同,具体如下‌:

‌哈希算法不同‌:

  • HS256:使用SHA-256算法进行HMAC签名。

  • HS384:使用SHA-384算法进行HMAC签名。

  • HS512:使用SHA-512算法进行HMAC签名。

‌安全性‌:

‌ HS256‌:虽然使用较短的哈希值,但在大多数情况下足够安全,适用于信任的系统内部,如单体应用或集中式认证服务。

‌ HS384‌和‌HS512‌:由于使用更长的哈希值,提供了更高的安全性,适用于需要更高安全性的场景。

‌性能‌:

‌ HS256‌:由于使用较短的哈希值,计算速度较快。

HS384‌和‌HS512‌:由于哈希值较长,计算速度相对较慢,但提供了更高的安全性。

‌应用场景‌:

‌ HS256‌:适用于信任的系统内部,如单体应用或集中式认证服务。

‌ HS384‌和‌HS512‌:适用于需要更高安全性的分布式系统和多服务环境。

RS签名算法

特点:这些算法使用非对称加密,即有一对公钥和私钥。私钥用于签名,公钥用于验证。即使公钥被泄露,只要私钥保持安全,Token依然是安全的。适用于分布式系统和多服务环境。

public class RsaKeyUtil {

    /**
     * 生成RSA密钥对
     *
     * @param keySize 密钥长度(例如2048)
     * @return 生成的KeyPair对象
     * @throws NoSuchAlgorithmException 如果指定的算法不存在
     */
    public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(keySize);
        return keyGen.generateKeyPair();
    }

    /**
     * 将私钥保存到文件
     *
     * @param privateKey 私钥
     * @param filename   文件路径
     * @throws Exception 如果发生错误
     */
    public static void savePrivateKey(PrivateKey privateKey, String filename) throws Exception {
        Path path = Paths.get(filename);
        if (Files.notExists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        if(Files.notExists(path)){
            Files.createFile(path);
        }
        byte[] encodedKey = privateKey.getEncoded();
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(encodedKey);
        String pemString = "-----BEGIN PRIVATE KEY-----\n" +
                Base64.getEncoder().encodeToString(pkcs8KeySpec.getEncoded()) +
                "\n-----END PRIVATE KEY-----";
        Files.write(path, pemString.getBytes());
    }

    /**
     * 将公钥保存到文件
     *
     * @param publicKey 公钥
     * @param filename  文件路径
     * @throws Exception 如果发生错误
     */
    public static void savePublicKey(PublicKey publicKey, String filename) throws Exception {
        Path path = Paths.get(filename);
        // 确保父目录存在
        if (Files.notExists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        if(Files.notExists(path)){
            Files.createFile(path);
        }
        byte[] encodedKey = publicKey.getEncoded();
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(encodedKey);
        String pemString = "-----BEGIN PUBLIC KEY-----\n" +
                Base64.getEncoder().encodeToString(x509KeySpec.getEncoded()) +
                "\n-----END PUBLIC KEY-----";
        Files.write(path, pemString.getBytes());
    }
}
@Slf4j
public class JwtRsaUtil {
    private static final String RSA_PRIVATE_KEY_PATH="security/jwt/private.key";

    private static final String RSA_PUBLIC_KEY_PATH="security/jwt/public.key";

    private static final Long EXPIRATION_TIME = 1800000L;

    public static String generateToken(String key) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        return createToken(key);
    }


    private static PrivateKey getRsaPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Path path = Paths.get(RSA_PRIVATE_KEY_PATH);
        if(Files.notExists(path)){
            throw new FileNotFoundException("私钥文件不存在");
        }
        byte[] bytes = Files.readAllBytes(path);
        String privateKeyString = new String(bytes, StandardCharsets.UTF_8);
        String privateKeyPem = privateKeyString.substring(28, privateKeyString.length() - 26);
        byte[] decoded = Base64.getDecoder().decode(privateKeyPem);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    private static PublicKey getRsaPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Path path = Paths.get(RSA_PUBLIC_KEY_PATH);
        if(Files.notExists(path)){
            throw new FileNotFoundException("公钥文件不存在");
        }
        byte[] bytes = Files.readAllBytes(path);
        String publicKeyString = new String(bytes, StandardCharsets.UTF_8);
        String publicKeyPem = publicKeyString.substring(27, publicKeyString.length() - 25);
        byte[] decoded = Base64.getDecoder().decode(publicKeyPem);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 私有方法,用于实际生成 JWT
     *
     * @param subject
     * @return
     */
    private static String createToken(String subject) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.claims()
                .setId(UUID.randomUUID().toString())
                .setSubject(subject)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .setIssuer("gateway-auth")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setNotBefore(new Date(System.currentTimeMillis()));
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.RS256, privateKey)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    private static String createToken(Claims claims) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.RS256,privateKey)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串.
    }



    /**
     * 判断令牌是否是系统签发,且是否在使用时间范围内
     * @param token 令牌
     * @return 校验正常 返回 true,校验失败返回false
     */
    public static boolean validateToken(String token) {
        try {
            PrivateKey privateKey = getRsaPrivateKey();
            Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token);
            if (!isTokenTimeValidate(token)) {
                return false;
            }
        } catch (Exception e) {
            log.error("令牌校验失败,失败令牌信息为:{}", token);
            return false;
        }
        return true;
    }

    /**
     * 判断令牌是否到了刷新时间,如果到了刷新时间返回新令牌,否则返回旧令牌
     * @param token
     * @return
     */
    public static String refreshToken(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        Date expiration = claims.getExpiration();
        long difference = expiration.getTime()-System.currentTimeMillis();
        if((double)difference/EXPIRATION_TIME<0.3d){
            claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
            return createToken(claims);
        }
        return token;
    }

    public static <T> T getClaimParam(String token, String paraName, Class<T> type) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        return claims.get(paraName, type);
    }

    
    private static Boolean isTokenTimeValidate(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        if (claims.getNotBefore() != null && !claims.getNotBefore().before(new Date())) {
            return false;
        }
        if (claims.getExpiration() != null && new Date().after(claims.getExpiration())) {
            return false;
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        if(Files.notExists(Paths.get(RSA_PRIVATE_KEY_PATH))){
            KeyPair keyPair = RsaKeyUtil.generateKeyPair(2048);
            RsaKeyUtil.savePublicKey(keyPair.getPublic(), RSA_PUBLIC_KEY_PATH);
            RsaKeyUtil.savePrivateKey(keyPair.getPrivate(), RSA_PRIVATE_KEY_PATH);
        }
        String token = createToken("test");
        System.out.println("私钥加密:"+token);
        PublicKey rsaPublicKey = getRsaPublicKey();
        Claims claims = Jwts.parser().setSigningKey(rsaPublicKey).parseClaimsJws(token).getBody();
        System.out.println("公钥解密:"+claims.getSubject());
    }
}

RS256‌、RS384、RS512的区别

  • RS256:使用RSA和SHA-256算法进行签名。

  • RS384:使用RSA和SHA-384算法进行签名。

  • RS512:使用RSA和SHA-512算法进行签名。

ES签名算法

特点:与RS算法类似,ECDSA也是非对称加密算法,但使用的是椭圆曲线加密,通常比RSA算法更快,签名和验证过程更高效。同时,ECDSA的密钥长度较短,有助于减少Token的大小。

public class EsKeyUtil {

    /**
     * 生成RSA密钥对
     *
     * @param keySize 密钥长度(例如2048)
     * @return 生成的KeyPair对象
     * @throws NoSuchAlgorithmException 如果指定的算法不存在
     */
    public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
        keyGen.initialize(keySize);
        return keyGen.generateKeyPair();
    }

    /**
     * 将私钥保存到文件
     *
     * @param privateKey 私钥
     * @param filename   文件名
     * @throws Exception 如果发生错误
     */
    public static void savePrivateKey(PrivateKey privateKey, String filename) throws Exception {
        Path path = Paths.get(filename);
        if (Files.notExists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        if(Files.notExists(path)){
            Files.createFile(path);
        }
        byte[] encodedKey = privateKey.getEncoded();
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(encodedKey);
        String pemString = "-----BEGIN PRIVATE KEY-----\n" +
                Base64.getEncoder().encodeToString(pkcs8KeySpec.getEncoded()) +
                "\n-----END PRIVATE KEY-----";
        Files.write(path, pemString.getBytes());
    }

    /**
     * 将公钥保存到文件
     *
     * @param publicKey 公钥
     * @param filename  文件名
     * @throws Exception 如果发生错误
     */
    public static void savePublicKey(PublicKey publicKey, String filename) throws Exception {
        Path path = Paths.get(filename);
        // 确保父目录存在
        if (Files.notExists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        if(Files.notExists(path)){
            Files.createFile(path);
        }
        byte[] encodedKey = publicKey.getEncoded();
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(encodedKey);
        String pemString = "-----BEGIN PUBLIC KEY-----\n" +
                Base64.getEncoder().encodeToString(x509KeySpec.getEncoded()) +
                "\n-----END PUBLIC KEY-----";
        Files.write(path, pemString.getBytes());
    }
}
@Slf4j
public class JwtEsUtil {
    private static final String ES_PRIVATE_KEY_PATH="security/jwt/private_es.key";

    private static final String ES_PUBLIC_KEY_PATH="security/jwt/public_es.key";

    private static final Long EXPIRATION_TIME = 1800000L;

    public static String generateToken(String key) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        return createToken(key);
    }


    private static PrivateKey getEsPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Path path = Paths.get(ES_PRIVATE_KEY_PATH);
        if(Files.notExists(path)){
            throw new FileNotFoundException("私钥文件不存在");
        }
        byte[] bytes = Files.readAllBytes(path);
        String privateKeyString = new String(bytes, StandardCharsets.UTF_8);
        String privateKeyPem = privateKeyString.substring(28, privateKeyString.length() - 26);
        byte[] decoded = Base64.getDecoder().decode(privateKeyPem);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        return keyFactory.generatePrivate(keySpec);
    }

    private static PublicKey getEsPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Path path = Paths.get(ES_PUBLIC_KEY_PATH);
        if(Files.notExists(path)){
            throw new FileNotFoundException("公钥文件不存在");
        }
        byte[] bytes = Files.readAllBytes(path);
        String publicKeyString = new String(bytes, StandardCharsets.UTF_8);
        String publicKeyPem = publicKeyString.substring(27, publicKeyString.length() - 25);
        byte[] decoded = Base64.getDecoder().decode(publicKeyPem);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 私有方法,用于实际生成 JWT
     *
     * @param subject
     * @return
     */
    private static String createToken(String subject) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getEsPrivateKey();
        Claims claims = Jwts.claims()
                .setId(UUID.randomUUID().toString())
                .setSubject(subject)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .setIssuer("gateway-auth")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setNotBefore(new Date(System.currentTimeMillis()));
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.ES256, privateKey)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    private static String createToken(Claims claims) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getEsPrivateKey();
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.ES256,privateKey)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串.
    }



    /**
     * 判断令牌是否是系统签发,且是否在使用时间范围内
     * @param token 令牌
     * @return 校验正常 返回 true,校验失败返回false
     */
    public static boolean validateToken(String token) {
        try {
            PrivateKey privateKey = getEsPrivateKey();
            Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token);
            if (!isTokenTimeValidate(token)) {
                return false;
            }
        } catch (Exception e) {
            log.error("令牌校验失败,失败令牌信息为:{}", token);
            return false;
        }
        return true;
    }

    /**
     * 判断令牌是否到了刷新时间,如果到了刷新时间返回新令牌,否则返回旧令牌
     * @param token
     * @return
     */
    public static String refreshToken(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getEsPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        Date expiration = claims.getExpiration();
        long difference = expiration.getTime()-System.currentTimeMillis();
        if((double)difference/EXPIRATION_TIME<0.3d){
            claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
            return createToken(claims);
        }
        return token;
    }

    public static <T> T getClaimParam(String token, String paraName, Class<T> type) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getEsPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        return claims.get(paraName, type);
    }


    private static Boolean isTokenTimeValidate(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getEsPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        if (claims.getNotBefore() != null && !claims.getNotBefore().before(new Date())) {
            return false;
        }
        if (claims.getExpiration() != null && new Date().after(claims.getExpiration())) {
            return false;
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        if(Files.notExists(Paths.get(ES_PRIVATE_KEY_PATH))){
            KeyPair keyPair = RsaKeyUtil.generateKeyPair(2048);
            RsaKeyUtil.savePublicKey(keyPair.getPublic(), ES_PUBLIC_KEY_PATH);
            RsaKeyUtil.savePrivateKey(keyPair.getPrivate(), ES_PRIVATE_KEY_PATH);
        }
        String token = createToken("test");
        System.out.println("私钥加密:"+token);
        PublicKey rsaPublicKey = getEsPublicKey();
        Claims claims = Jwts.parser().setSigningKey(rsaPublicKey).parseClaimsJws(token).getBody();
        System.out.println("公钥解密:"+claims.getSubject());
    }
}

ES256‌、ES384、ES512的区别

  • ES256:使用椭圆曲线加密算法ECDSA和SHA-256进行签名。

  • ES384:使用ECDSA和SHA-384进行签名。

  • ES512:使用ECDSA和SHA-512进行签名

PS签名算法

特点:PS算法是RSA变种,使用填充方案来增加安全性,适用于需要更高安全性的场景

public class RsaKeyUtil {

    /**
     * 生成RSA密钥对
     *
     * @param keySize 密钥长度(例如2048)
     * @return 生成的KeyPair对象
     * @throws NoSuchAlgorithmException 如果指定的算法不存在
     */
    public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(keySize);
        return keyGen.generateKeyPair();
    }

    /**
     * 将私钥保存到文件
     *
     * @param privateKey 私钥
     * @param filename   文件路径
     * @throws Exception 如果发生错误
     */
    public static void savePrivateKey(PrivateKey privateKey, String filename) throws Exception {
        Path path = Paths.get(filename);
        if (Files.notExists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        if(Files.notExists(path)){
            Files.createFile(path);
        }
        byte[] encodedKey = privateKey.getEncoded();
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(encodedKey);
        String pemString = "-----BEGIN PRIVATE KEY-----\n" +
                Base64.getEncoder().encodeToString(pkcs8KeySpec.getEncoded()) +
                "\n-----END PRIVATE KEY-----";
        Files.write(path, pemString.getBytes());
    }

    /**
     * 将公钥保存到文件
     *
     * @param publicKey 公钥
     * @param filename  文件路径
     * @throws Exception 如果发生错误
     */
    public static void savePublicKey(PublicKey publicKey, String filename) throws Exception {
        Path path = Paths.get(filename);
        // 确保父目录存在
        if (Files.notExists(path.getParent())) {
            Files.createDirectories(path.getParent());
        }
        if(Files.notExists(path)){
            Files.createFile(path);
        }
        byte[] encodedKey = publicKey.getEncoded();
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(encodedKey);
        String pemString = "-----BEGIN PUBLIC KEY-----\n" +
                Base64.getEncoder().encodeToString(x509KeySpec.getEncoded()) +
                "\n-----END PUBLIC KEY-----";
        Files.write(path, pemString.getBytes());
    }
}
@Slf4j
public class JwtPsUtil {
    private static final String RSA_PRIVATE_KEY_PATH="security/jwt/private.key";

    private static final String RSA_PUBLIC_KEY_PATH="security/jwt/public.key";

    private static final Long EXPIRATION_TIME = 1800000L;

    public static String generateToken(String key) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        return createToken(key);
    }


    private static PrivateKey getRsaPrivateKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Path path = Paths.get(RSA_PRIVATE_KEY_PATH);
        if(Files.notExists(path)){
            throw new FileNotFoundException("私钥文件不存在");
        }
        byte[] bytes = Files.readAllBytes(path);
        String privateKeyString = new String(bytes, StandardCharsets.UTF_8);
        String privateKeyPem = privateKeyString.substring(28, privateKeyString.length() - 26);
        byte[] decoded = Base64.getDecoder().decode(privateKeyPem);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(keySpec);
    }

    private static PublicKey getRsaPublicKey() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Path path = Paths.get(RSA_PUBLIC_KEY_PATH);
        if(Files.notExists(path)){
            throw new FileNotFoundException("公钥文件不存在");
        }
        byte[] bytes = Files.readAllBytes(path);
        String publicKeyString = new String(bytes, StandardCharsets.UTF_8);
        String publicKeyPem = publicKeyString.substring(27, publicKeyString.length() - 25);
        byte[] decoded = Base64.getDecoder().decode(publicKeyPem);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decoded);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 私有方法,用于实际生成 JWT
     *
     * @param subject
     * @return
     */
    private static String createToken(String subject) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.claims()
                .setId(UUID.randomUUID().toString())
                .setSubject(subject)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .setIssuer("gateway-auth")
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setNotBefore(new Date(System.currentTimeMillis()));
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.PS256, privateKey)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串
    }

    private static String createToken(Claims claims) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        return Jwts.builder()//开始构建 JWT 的构建器
                .setClaims(claims)//设置 JWT 中的声明
                .signWith(SignatureAlgorithm.PS256,privateKey)//使用指定的算法(HS256)和密钥对 JWT 进行签名
                .compact();//生成最终的 JWT 字符串.
    }



    /**
     * 判断令牌是否是系统签发,且是否在使用时间范围内
     * @param token 令牌
     * @return 校验正常 返回 true,校验失败返回false
     */
    public static boolean validateToken(String token) {
        try {
            PrivateKey privateKey = getRsaPrivateKey();
            Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token);
            if (!isTokenTimeValidate(token)) {
                return false;
            }
        } catch (Exception e) {
            log.error("令牌校验失败,失败令牌信息为:{}", token);
            return false;
        }
        return true;
    }

    /**
     * 判断令牌是否到了刷新时间,如果到了刷新时间返回新令牌,否则返回旧令牌
     * @param token
     * @return
     */
    public static String refreshToken(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        Date expiration = claims.getExpiration();
        long difference = expiration.getTime()-System.currentTimeMillis();
        if((double)difference/EXPIRATION_TIME<0.3d){
            claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME));
            return createToken(claims);
        }
        return token;
    }

    public static <T> T getClaimParam(String token, String paraName, Class<T> type) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        return claims.get(paraName, type);
    }


    private static Boolean isTokenTimeValidate(String token) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        PrivateKey privateKey = getRsaPrivateKey();
        Claims claims = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token).getBody();
        if (claims.getNotBefore() != null && !claims.getNotBefore().before(new Date())) {
            return false;
        }
        if (claims.getExpiration() != null && new Date().after(claims.getExpiration())) {
            return false;
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        if(Files.notExists(Paths.get(RSA_PRIVATE_KEY_PATH))){
            KeyPair keyPair = RsaKeyUtil.generateKeyPair(2048);
            RsaKeyUtil.savePublicKey(keyPair.getPublic(), RSA_PUBLIC_KEY_PATH);
            RsaKeyUtil.savePrivateKey(keyPair.getPrivate(), RSA_PRIVATE_KEY_PATH);
        }
        String token = createToken("test");
        System.out.println("私钥加密:"+token);
        PublicKey rsaPublicKey = getRsaPublicKey();
        Claims claims = Jwts.parser().setSigningKey(rsaPublicKey).parseClaimsJws(token).getBody();
        System.out.println("公钥解密:"+claims.getSubject());
    }
}

PS256‌、PS384、PS512的区别

  • PS256:使用RSASSA-PSS和SHA-256进行签名。

  • PS384:使用RSASSA-PSS和SHA-384进行签名。

  • PS512:使用RSASSA-PSS和SHA-512进行签名。