当前位置: 首页 > news >正文

Spring Security6 快速实战

Spring Security 介绍

官网:https://spring.io/projects/spring-security

image.png

Spring Security 定义

Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。Spring Security 主要实现了认证和授权,SpringSecurity 在架构上将认证与授权分离,并提供了扩展点。

  • 认证(Authentication)

用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录、二维码登录、手机短信登录、指纹认证等方式。

  • 授权(Authorization)

授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。

Spring Security 和 Shiro 比较

在 Java 生态中,目前有 Spring Security 和 Apache Shiro 两个安全框架,可以完成认证和授权的功能。
Apache Shiro:一个功能强大且易于使用的 Java 安全框架,提供了认证、授权、加密和会话管理。

相同点:

  1. 认证功能
  2. 授权功能
  3. 加密功能
  4. 会话管理
  5. 缓存支持
  6. rememberMe 功能

优点:

  1. Spring Security 基于 Spring 开发,项目中如果使用 Spring 作为基础,配合 Spring Security 做权限更加方便,而 Shiro 需要和 Spring 进行整合开发
  2. Spring Security 功能比 Shiro 更加丰富些,例如安全防护
  3. Spring Security 社区资源比Shiro丰富

缺点:

  1. Shiro 的配置和使用比较简单,Spring Security 上手复杂
  2. Shiro 依赖性低,不需要任何框架和容器,可以独立运行,而 Spring Security 依赖于 Spring 容器

一般来说,常见的安全管理技术栈的组合是这样的:

  • SSM + Shiro
  • Spring Boot/Spring Cloud + Spring Security

Spring Security 使用

用户认证

快速开始

创建一个 SpringBoot 项目
1)引入依赖

<!-- 接入spring security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

2)编写测试的 Controller

@RestController
@RequestMapping("/admin")
public class AdminController {@GetMapping("/demo")public String demo() {return "spring security demo";}
}

3)启动项目后测试接口调用
引入 Spring Security 之后 ,访问 API 接口时,需要首先进行登录,才能进行访问。
测试 [http://localhost:8080/admin/demo](http://localhost:8080/admin/demo) ,会跳转到登录界面:
image.png
说明:
页面生成源码:DefaultLoginPageGeneratingFilter#generateLoginPageHtml
用户名密码认证 Filter:UsernamePasswordAuthenticationFilter
因为要加载远程的 css,故很慢,一般自己指定登录页面即可。

需要登录,默认用户名:user,密码可以查看控制台日志获取:
image.png
登录之后跳转回请求接口:
image.png
4)退出登录
Spring Security 默认实现了 logout 退出,用户只需要向 Spring Security 项目中发送 [http://localhost:8080/logout](http://localhost:8080/logout) 退出请求即可。
image.png

设置用户名密码

(1)基于 application.yml 方式
可以在 application.yml 中自定义用户名密码:

spring:# Spring Security 配置项,对应 SecurityProperties 配置类security:user:name: user # 用户名password: 123456 # 密码roles: # 拥有角色- admin

原理:
默认情况下,UserDetailsServiceAutoConfiguration 自动化配置类,会创建一个内存级别的 InMemoryUserDetailsManager 对象,提供认证的用户信息。

  • 添加 spring.security.user 配置项,UserDetailsServiceAutoConfiguration 会基于配置的信息在内存中创建一个用户User
  • 未添加 spring.security.user 配置项,UserDetailsServiceAutoConfiguration 会自动在内存中创建一个用户名为 user,密码为 UUID 随机的用户 User

(2)基于Java Bean 配置方式

@Configuration
@EnableWebSecurity  // 开启spring sercurity支持
public class SecurityConfig {/*** 配置用户信息* @return*/@Beanpublic UserDetailsService userDetailsService() {// 使用默认加密方式bcrypt对密码进行加密,添加用户信息UserDetails user = User.withDefaultPasswordEncoder().username("jay").password("123456").roles("user").build();UserDetails admin = User.withUsername("admin").password("{noop}123456") // 对密码不加密.roles("admin", "user").build();return new InMemoryUserDetailsManager(user, admin);}}

设置加密方式

(1)方式1
Spring Security 密码加密格式为:{id}encodedPassword

UserDetails user = User.withUsername("user").password("{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG").roles("USER").build();
UserDetails admin = User.withUsername("admin").password("{noop}123456") // noop表示对密码不加密.roles("admin", "user").build();

如果密码不指定 {id} 会抛异常:
image.png
Spring Security 支持的加密方式可以通过 PasswordEncoderFactories 查看:
image.png

(2)方式2
也可以通过增加 PasswordEncoder 配置指定加密方式

UserDetails admin = User.withUsername("admin")// 指定加密算法对密码加密.password(passwordEncoder().encode("123456")) .roles("admin", "user").build();@Bean
public PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();  // 不加密//return new BCryptPasswordEncoder(); // 加密方式bcrypt
}

自定义用户信息加载方式

需要自定义从数据库获取用户信息,可以实现UserDetailsService接口。

@Service
public class DBUserDetailService implements UserDetailsService {@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// TODO 根据用户名可以从数据库获取用户信息,角色以及权限信息// 模拟从数据库获取了用户信息,并封装成UserDetails对象UserDetails user = User.withUsername("jay").password(passwordEncoder.encode("123456")).roles("user").build();return user;}
}

自定义登录页面

Spring Security 默认登录页面通过 DefaultLoginPageGeneratingFilter#generateLoginPageHtml 生成。
1)编写登录页面

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><form action="/user/login" method="post">用户名:<input type="text" name="username"/><br/>密码:<input type="password" name="password"/><br/><input type="submit" value="提交"/></form></body>
</html>

2)配置 Spring Security 的过滤器链

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 表单提交http.formLogin((formLogin) -> formLogin.loginPage("/login.html") // 指定自定义登录页面地址.loginProcessingUrl("/user/login") // 登录访问路径:前台界面提交表单之后跳转到这个路径进行UserDetailsService的验证,必须和表单提交接口一样.defaultSuccessUrl("/admin/demo") // 认证成功之后跳转的路径);// 对请求进行访问控制设置http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests// 设置哪些路径可以直接访问,不需要认证.requestMatchers("/login.html","/user/login").permitAll().anyRequest().authenticated() // 其他路径的请求都需要认证);// 关闭跨站点请求伪造csrf防护http.csrf((csrf) -> csrf.disable());return http.build();
}

测试 [http://localhost:8080/admin/demo](http://localhost:8080/admin/demo) ,会跳转到自定义登录界面:
image.png

前后端分离认证

表单登录配置模块提供了 successHandler() 和 failureHandler() 两个方法,分别处理登录成功和登录失败的逻辑。其中,successHandler() 方法带有一个 Authentication 参数,携带当前登录用户名及其角色等信息;而failureHandler() 方法携带一个 AuthenticationException 异常参数。

// 前后端分离认证逻辑
http.formLogin((formLogin) -> formLogin.loginProcessingUrl("/login") // 登录访问接口.successHandler(new LoginSuccessHandler()) // 登录成功处理逻辑.failureHandler(new LoginFailureHandler()) // 登录失败处理逻辑
);/*** 认证成功处理逻辑*/
public class LoginSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {response.setContentType("text/html;charset=utf-8");response.getWriter().write("登录成功");}
}/*** 认证失败处理逻辑*/
public class LoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {// TODOresponse.setContentType("text/html;charset=utf-8");response.getWriter().write("登录失败");exception.printStackTrace();}
}

认证流程

image.png

用户授权

授权的方式包括 web 授权和方法授权,web 授权是通过 url 拦截进行授权,方法授权是通过方法拦截进行授权。

web 授权

基于 url 的访问控制,Spring Security 可以通过 http.authorizeRequests() 对 web 请求进行授权保护 ,Spring Security 使用标准 Filter 建立了对 web 请求的拦截,最终实现对资源的授权访问。配置顺序会影响之后授权的效果,越是具体的应该放在前面,越是笼统的应该放到后面。

// 对请求进行访问控制设置
http.authorizeHttpRequests((authorizeHttpRequests) -> authorizeHttpRequests// 设置哪些路径可以直接访问,不需要认证.requestMatchers("/login").permitAll()  // 不需要认证.requestMatchers("/index").hasRole("user")  // 需要user角色,底层会判断是否有ROLE_admin权限.requestMatchers("/index2").hasRole("admin").requestMatchers("/user/**").hasAuthority("user:api") // 需要user:api权限.requestMatchers("/order/**").hasAuthority("order:api").anyRequest().authenticated()  // 其他路径的请求都需要认证);@Bean
public UserDetailsService userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("jay").password("123456").roles("user").build();UserDetails admin = User.withDefaultPasswordEncoder().username("admin").password("123456")// 注意:roles和authorities不能同时配置,同时配置后者会覆盖前者的权限.authorities("ROLE_admin","ROLE_user","user:api","order:api").build();return new InMemoryUserDetailsManager(user,admin);
}

自定义授权失败异常处理

使用 Spring Security 时经常会看见 403(无权限)。Spring Security 支持自定义权限受限处理,需要实现 AccessDeniedHandler 接口。

public class BussinessAccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setContentType("text/html;charset=utf-8");response.getWriter().write("没有访问权限");accessDeniedException.printStackTrace();}
}

在配置类中设置访问受限后交给 BussinessAccessDeniedHandler 处理。

// 访问受限后的异常处理
http.exceptionHandling((exceptionHandling) ->exceptionHandling.accessDeniedHandler(new BussinessAccessDeniedHandler())
);

方法授权

基于注解的访问控制,Spring Security 在方法的权限控制上支持三种类型的注解,JSR-250注解、@Secured注解和支持表达式的注解。这三种注解默认都是没有启用的,需要通过 @EnableGlobalMethodSecurity 来进行启用。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true, securedEnabled = true, prePostEnabled = true)
public class SecurityConfig {
}// Controller
@RolesAllowed({"ROLE_user","ROLE_admin"}) // 配置访问此方法时应该具有的角色
@GetMapping("/index5")
public String index5(){return "index5";
}@Secured("ROLE_admin") // 配置访问此方法时应该具有的角色
@GetMapping("/index6")
public String index6(){return "index6";
}

Spring Security 中定义了四个支持使用表达式的注解,分别是 @PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。其中前两者可以用来在方法调用前或者调用后进行权限检查,后两者可以用来对集合类型的参数或者返回值进行过滤。

@PreAuthorize("hasRole('ROLE_admin') and #id<10 ") // 访问此方法需要具有admin角色,同时限制只能查询id小于10的用户
@GetMapping("/findUserById")
public String findById(long id) {// TODO 查询数据库获取用户信息return "success";
}

过滤器实现动态权限控制

Spring Security 从 5.5 之后动态权限控制方式已经改变。
5.5 之前需要实现接口:

  • FilterInvocationSecurityMetadataSource:获取访问URL所需要的角色信息
  • AccessDecisionManager:用于权限校验,失败抛出 AccessDeniedException 异常

5.5 之后,利用过滤器动态控制权限,在 AuthorizationFilter 中,只需要实现接口 AuthorizationManager,如果没有权限,抛出 AccessDeniedException 异常。

权限校验核心逻辑:
org.springframework.security.web.access.intercept.AuthorizationFilter#doFilter
》org.springframework.security.authorization.AuthorityAuthorizationManager#check
》org.springframework.security.authorization.AuthoritiesAuthorizationManager#isAuthorized

Spring Security 整合 JWT 实现自定义登录认证

自定义登录认证的业务需求

某企业要做前后端分离的项目,决定要用 spring boot + spring security + JWT 框架实现登录认证授权功能,用户登录成功后,服务端利用 JWT 生成 token,之后客户端每次访问接口,都需要在请求头上添加 Authorization:Bearer token 的方式传值到服务器端,服务器端再从 token 中解析和校验 token 的合法性,如果合法,则取出用户数据,保存用户信息,不需要在校验登录,否则就需要重新登录。

流程:
1、用户输入用户名和密码,客户端(例如 web 页面)调用登录接口获取 token;
2、服务端验证用户名和密码,验证通过返回 token 到客户端;
3、客户端带上 token 请求服务端其他接口,服务端验证 token 合法性,成功返回请求结果;
4、客户端展示结果;

JWT 详解

什么是 JWT

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递 json 对象,传递的信息经过数字签名可以被验证和信任。JWT 可以使用 HMAC 算法或使用 RSA 的公钥/私钥对来签名,防止被篡改。

官网: https://jwt.io/
标准: https://tools.ietf.org/html/rfc7519

JWT 令牌优点:

  1. jwt 基于 json,非常方便解析。
  2. 可以在令牌中自定义丰富的内容,易扩展。
  3. 通过非对称加密算法及数字签名技术,JWT 防止篡改,安全性高。
  4. 资源服务使用 JWT 可不依赖授权服务即可完成授权。

JWT 令牌缺点:

  1. JWT 令牌较长,占存储空间比较大。
  2. 安全性取决于密钥管理。

JWT 的安全性取决于密钥的管理。如果密钥被泄露或者被不当管理,那么 JWT 将会受到攻击。因此,在使用 JWT 时,一定要注意密钥的管理,包括生成、存储、更新、分发等等。

  1. 无法撤销

由于 JWT 是无状态的,一旦 JWT 被签发,就无法撤销。如果用户在使用 JWT 认证期间被注销或禁用,那么服务端就无法阻止该用户继续使用之前签发的 JWT。因此,开发人员需要设计额外的机制来撤销 JWT,例如使用黑名单或者设置短期有效期等等。

使用 JWT 主要用来做下面两点:

  • 认证(Authorization):这是使用 JWT 最常见的一种情况,一旦用户登录,后面每个请求都会包含 JWT,从而允许用户访问该令牌所允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小。
  • 信息交换(Information Exchange):JWT 是能够安全传输信息的一种方式。通过使用公钥/私钥对 JWT 进行签名认证。此外,由于签名是使用 head 和 payload 计算的,因此你还可以验证内容是否遭到篡改。

JWT 组成

一个 JWT 实际上就是一个字符串,它由三部分组成,头部(header)、载荷(payload)与签名(signature)。

https://jwt.io/

image.png

(1)头部(header):
头部用于描述关于该 JWT 的最基本的信息:类型(即 JWT)以及签名所用的算法(如 HMACSHA256 或 RSA)等。这也可以被表示成一个 JSON 对象:

{"alg": "HS256","typ": "JWT"
}

然后将头部进行 base64 加密(该加密是可以对称解密的),构成了第一部分:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

(2)载荷(payload):
第二部分是载荷,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

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

iss:jwt 签发者;
sub:jwt 所面向的用户;
aud:接收 jwt 的一方;
exp:jwt 的过期时间,这个过期时间必须要大于签发时间;
nbf:定义在什么时间之前,该 jwt 都是不可用的;
iat:jwt 的签发时间;
jti:jwt 的唯一身份标识,主要用来作为一次性 token,从而回避重放攻击;

  • 公共的声明

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

  • 私有的声明

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

定义一个 payload:

{"sub": "1234567890","name": "John Doe","iat": 1516239022
}

然后将其进行 base64 加密,得到 Jwt 的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

(3)签名(signature)
jwt 的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header(base64后的)
  • payload(base64后的)
  • secret(盐,一定要保密)

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

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);var signature = HMACSHA256(encodedString, 'jay'); // khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

将这三部分用“.”连接成一个完整的字符串,构成了最终的 jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

注意:secret 是保存在服务器端的,jwt 的签发生成也是在服务器端的,secret 就是用来进行 jwt 的签发和 jwt 的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个 secret, 那就意味着客户端是可以自我签发 jwt 了。

应用

一般是在请求头里加入 Authorization,并加上 Bearer 标注:

fetch('api/user/1', {headers: {'Authorization': 'Bearer ' + token}
})

服务端会验证 token,如果验证通过就会返回相应的资源。整个流程就是这样的:
1、浏览器发送用户名和密码给服务端;
2、服务端验证用户名和密码,通过 secret 生成 JWT;
3、将 JWT 返回给浏览器;
4、浏览器将 JWT 添加到请求 header;
5、服务端验签,并获取用户信息,进行资源授权;
6、返回浏览器响应信息;

自定义登录核心实现

1)实现校验 token 的过滤器

@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserDetailsService userDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 1.从请求头中取出token,进行判断,如果没有携带token,则继续往下走其他的其他的filter逻辑String tokenValue = request.getHeader(HttpHeaders.AUTHORIZATION);if (!StringUtils.hasText(tokenValue)) {filterChain.doFilter(request, response);return;}// 2. 校验token// 2.1 将token切割前缀“bearer ”,然后使用封装的JWT工具解析token,得到一个map对象String token = tokenValue.substring("bearer ".length());Map<String, Object> map = JWTUtils.parseToken(token);// 2.2 取出token中的过期时间,调用JWT工具中封装的过期时间校验,如果token已经过期,则删除登录的用户,继续往下走其他filter逻辑if (JWTUtils.isExpiresIn((long) map.get("expiresIn"))) {// token 已经过期SecurityContextHolder.getContext().setAuthentication(null);filterChain.doFilter(request, response);return;}String username = (String) map.get("username");if (StringUtils.hasText(username) && SecurityContextHolder.getContext().getAuthentication() == null) {// 获取用户信息UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (userDetails != null && userDetails.isEnabled()) {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));// 设置用户登录状态log.info("authenticated user {}, setting security context", username);SecurityContextHolder.getContext().setAuthentication(authentication);}}filterChain.doFilter(request, response);}
}

2)SpringSecurity 的过滤器链路中添加 JWT 登录过滤器

// 添加JWT登录过滤器,在登录之前获取token并校验
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

3)测试
启动应用后调用登录接口返回 token 信息;
不带 token 信息访问接口,返回401,没有权限;
带token信息访问接口,返回正常;

JWT 续期问题

JWT(JSON Web Token)通常是在用户登录后签发的,用于验证用户身份和授权。JWT 的有效期限(或称“过期时间”)通常是一段时间(例如 1 小时),过期后用户需要重新登录以获取新的 JWT。然而,在某些情况下,用户可能会在 JWT 到期之前使用应用程序,这可能会导致应用程序不可用或需要用户重新登录。为了避免这种情况,通常有两种解决方案来处理JWT续期问题:

刷新令牌(Refresh Token)

刷新令牌是一种机制,它允许应用程序获取一个新的 JWT,而无需用户进行身份验证。当 JWT 过期时,应用程序使用刷新令牌向身份验证服务器请求一个新的 JWT,而无需提示用户输入其凭据。这样,用户可以继续使用应用程序,而不必重新登录。
以下是一个示例 Java 代码,演示如何使用 Refresh Token 来更新 JWT。

public String refreshAccessToken(String refreshToken) {// validate the refresh token (check expiration, signature, etc.)boolean isValid = validateRefreshToken(refreshToken);if (isValid) {// retrieve the user information associated with the refresh token (e.g. user ID)String userId = getUserIdFromRefreshToken(refreshToken);// generate a new JWT access tokenString newAccessToken = generateAccessToken(userId);return newAccessToken;} else {throw new RuntimeException("Invalid refresh token.");}
}

在这个示例中,refreshAccessToken 方法接收一个刷新令牌作为参数,并使用 validateRefreshToken 方法验证该令牌是否有效。如果令牌有效,方法将使用 getUserIdFromRefreshToken 方法获取与令牌关联的用户信息,然后使用 generateAccessToken 方法生成一个新的 JWT 访问令牌,并将其返回。如果令牌无效,则抛出异常。

自动延长 JWT 有效期

在某些情况下,JWT 可以自动延长其有效期。例如,当用户在 JWT 过期前继续使用应用程序时,应用重新设置 token 过期时间。
要自动延长 JWT 有效期,您可以在每次请求时检查 JWT 的过期时间,并在必要时更新 JWT 的过期时间。以下是一个示例 Java 代码,演示如何自动延长 JWT 有效期。

public String getAccessToken(HttpServletRequest request) {String accessToken = extractAccessTokenFromRequest(request);if (isAccessTokenExpired(accessToken)) {String userId = extractUserIdFromAccessToken(accessToken);accessToken = generateNewAccessToken(userId);} else if (shouldRefreshAccessToken(accessToken)) {String userId = extractUserIdFromAccessToken(accessToken);accessToken = generateNewAccessToken(userId);}return accessToken;
}private boolean isAccessTokenExpired(String accessToken) {// extract expiration time from the access tokenDate expirationTime = extractExpirationTimeFromAccessToken(accessToken);// check if the expiration time is in the pastreturn expirationTime.before(new Date());
}private boolean shouldRefreshAccessToken(String accessToken) {// extract expiration time and current timeDate expirationTime = extractExpirationTimeFromAccessToken(accessToken);Date currentTime = new Date();// calculate the remaining time until expirationlong remainingTime = expirationTime.getTime() - currentTime.getTime();// refresh the token if it expires within the next 5 minutesreturn remainingTime < 5 * 60 * 1000;
}private String generateNewAccessToken(String userId) {// generate a new access token with a new expiration timeDate expirationTime = new Date(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TIME);String accessToken = generateAccessToken(userId, expirationTime);return accessToken;
}

在这个示例中,getAccessToken 方法接收 HttpServletRequest 对象作为参数,并使用 extractAccessTokenFromRequest 方法从请求中提取 JWT 访问令牌。然后,它使用 isAccessTokenExpired 方法检查 JWT 的过期时间是否已过期。如果过期,它使用 extractUserIdFromAccessToken 方法从 JWT 中提取用户 ID,并使用 generateNewAccessToken 方法生成一个新的 JWT 访问令牌。如果 JWT 尚未过期,但即将到期,则使用 shouldRefreshAccessToken 方法检查 JWT 是否需要更新。如果是这样,它使用相同的流程生成一个新的 JWT 访问令牌。

相关文章:

  • webrtc网之sip转webrtc
  • windows11 调整鼠标灵敏度方法
  • 在线html地址转html文本
  • 外置固态硬盘配置
  • C语言基础概念考查备忘 - 标识符、关键字、预定义标识符、语法检查、语义检查 ... 左值、右值、对象、副作用、未定义行为、sizeof是什么等等
  • 连接服务器的ssh终端自动断开解放方法
  • 商家门店小程序怎么做?门店小程序的优势和好处
  • 微服务的流量管理-服务网格
  • 说说React jsx转换成真实DOM的过程?
  • C++ vector基本操作
  • es6 语法 解构 拼接 扩展运算 数组降为 symbol 迭代器 生成器 定时器 map 映射 对象字面量 私有属性 构造函数继承
  • 【【FPGA的 MicroBlaze 的 介绍与使用 】】
  • MacBook续命,XCode硬盘占用问题
  • “分割“安卓用户,对标iOS,鸿蒙崛起~
  • HTTP常见响应码
  • Apache Spark Streaming 使用实例
  • Codepen 每日精选(2018-3-25)
  • Javascript 原型链
  • Javascript编码规范
  • js如何打印object对象
  • maya建模与骨骼动画快速实现人工鱼
  • unity如何实现一个固定宽度的orthagraphic相机
  • webgl (原生)基础入门指南【一】
  • 二维平面内的碰撞检测【一】
  • 计算机常识 - 收藏集 - 掘金
  • 老板让我十分钟上手nx-admin
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 时间复杂度与空间复杂度分析
  • 我看到的前端
  • 我与Jetbrains的这些年
  • 我这样减少了26.5M Java内存!
  • 学习使用ExpressJS 4.0中的新Router
  • 一起来学SpringBoot | 第十篇:使用Spring Cache集成Redis
  • 在GitHub多个账号上使用不同的SSH的配置方法
  • zabbix3.2监控linux磁盘IO
  • 通过调用文摘列表API获取文摘
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • $().each和$.each的区别
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (Forward) Music Player: From UI Proposal to Code
  • (ibm)Java 语言的 XPath API
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (三)终结任务
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (四)汇编语言——简单程序
  • (完整代码)R语言中利用SVM-RFE机器学习算法筛选关键因子
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)...
  • .net CHARTING图表控件下载地址
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .net 使用ajax控件后如何调用前端脚本