微服务-微服务Spring Security6实战

1. Spring Security介绍

1.1 Spring Security定义

Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框 架。 Spring Security 主要实现了 Authentication (认证,解决 who are you? ) 和 AccessControl (访问控制,也就是what are you allowed to do ?,也称为 Authorization )。 SpringSecurity 在架构上 将认证与授权分离,并提供了扩展点。
Spring Security is a powerful and highly customizable authentication and access-control
framework. It is the de-facto standard for securing Spring-based applications. Spring
Security 是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于 Spring
应用程序。
Spring Security is a framework that focuses on providing both authentication and
authorization to Java applications. Like all Spring projects, the real power of Spring Security
is found in how easily it can be extended to meet custom requirements
Spring Security 是一个框架,侧重于为 Java 应用程序提供身份验证和授权。与所有 Spring 项目一样, Spring
安全性
的真正强大之处,在于它很容易扩展以满足定制需求

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

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

1.2 Spring SecurityShiro比较

Java 生态中,目前有 Spring Security Apache Shiro 两个安全框架,可以完成认证和授权的功能。
Spring Security
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

2. Spring Security使用

2.1 用户身份认证

快速开始
创建一个 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 , 会跳转到登录界面
页面生成源码: DefaultLoginPageGeneratingFilter#generateLoginPageHtml
用户名密码认证 Filter: UsernamePasswordAuthenticationFilter
需要登录,默认用户名: user ,密码可以查看控制台日志获取
登录之后跳转回请求接口
4)退出登录
Spring security 默认实现了 logout 退出,用户只需要向 Spring Security 项目中发送 http://localhost:8080/logout 退出请求即可。

设置用户名密码

基于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
基于Java Bean配置方式
@Configuration
@EnableWebSecurity  //开启spring sercurity支持
public class SecurityConfig {


    /**
     * 配置用户信息
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        //使用默认加密方式bcrypt对密码进行加密,添加用户信息
        UserDetails user = User.withDefaultPasswordEncoder()
                .username("fox")
                .password("123456")
                .roles("user")
                .build();

        UserDetails admin = User.withUsername("admin")
                .password("{noop}123456") //对密码不加密
                .roles("admin", "user")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }

}
设置加密方式
方式1:{id}encodedPassword

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}会抛异常:

Spring Security支持的加密方式可以通过PasswordEncoderFactories查看

 

方式2: passwordEncoder().encode("123456")

也可以通过增加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接口

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

@Service
public class TulingUserDetailService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //TODO  根据用户名可以从数据库获取用户信息,角色以及权限信息
        // 模拟从数据库获取了用户信息,并封装成UserDetails对象
        UserDetails user = User
                .withUsername("fox")
                .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 ,会跳转到自定义登录界面

前后端分离认证

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

//前后端分离认证逻辑
http.formLogin((formLogin) -> formLogin
        .loginProcessingUrl("/login") //登录访问接口
        .successHandler(new LoginSuccessHandler()) //登录成功处理逻辑
        .failureHandler(new LoginFailureHandler()) //登录失败处理逻辑
);


//
/**
 * 认证成功处理逻辑
 */
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public 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 {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        // TODO
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("登录失败");
        exception.printStackTrace();
    }
}

认证流程

 

2.2 用户授权(访问控制)

授权的方式包括 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("fox")
            .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 {
    @Override
    public 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

 

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

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

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

3.2 JWT详解

什么是JWT

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

JWT令牌的优点:

  1. jwt基于json,非常方便解析。
  2. 可以在令牌中自定义丰富的内容,易扩展。
  3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
  4. 资源服务使用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)。

头部(header)

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

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

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

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(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

签名(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

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

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

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);

var signature = HMACSHA256(encodedString, 'fox'); // 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,如果验证通过就会返回相应的资源。整个流程就是这样的:

3.3 自定义登录核心实现

结合课堂代码理解

1)实现校验token的过滤器
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected 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信息访问接口,返回正常

3.4 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 token
        String 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 token
    Date expirationTime = extractExpirationTimeFromAccessToken(accessToken);
 
    // check if the expiration time is in the past
    return expirationTime.before(new Date());
}
 
private boolean shouldRefreshAccessToken(String accessToken) {
    // extract expiration time and current time
    Date expirationTime = extractExpirationTimeFromAccessToken(accessToken);
    Date currentTime = new Date();
 
    // calculate the remaining time until expiration
    long remainingTime = expirationTime.getTime() - currentTime.getTime();
 
    // refresh the token if it expires within the next 5 minutes
    return remainingTime < 5 * 60 * 1000;
}
 
private String generateNewAccessToken(String userId) {
    // generate a new access token with a new expiration time
    Date 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访问令牌。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/407148.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数字化转型导师坚鹏:政府数字化流程管理

政府数字化流程管理 课程背景&#xff1a; 很多政府存在以下问题&#xff1a; 不清楚数字化对流程有什么影响&#xff1f; 不知道政府业流程如何进行优化&#xff1f; 不知道政府业流程优化的具体案例&#xff1f; 课程特色&#xff1a; 有实战案例 有原创观点 …

小程序画布(二维地图线)

首先开始是想用小程序兼容openlayers的&#xff0c;但是了解到用不了&#xff0c;那就用画布来解决 实际效果如下 wxml中代码 <canvas id"trackDesignCanvas" //指定 id 的 Canvas 组件class"orbit-canvas-main" type"2d" …

鸿蒙LiteOS-M 内核初始化

目录 一、LiteOS-M 初始化内核二、LOS_KernelInit代码分析三、LOS_Start代码解析坚持就有收获 一、LiteOS-M 初始化内核 在LiteOS-M应用程序中&#xff0c;系统初始化如下&#xff1a; /*** brief This is the ohos entry, and you could call this in your main funciton af…

Android 内存优化内存泄漏处理

一:匿名内部类/非静态内部类 匿名内部类的泄漏原因&#xff1a;匿名内部类会隐式地持有外部类的引用.当外部类被销毁时&#xff0c;内部类并不会自动销毁&#xff0c;因为内部类并不是外部类的成员变量&#xff0c; 它们只是在外部类的作用域内创建的对象&#xff0c;所以内部…

LabVIEW变压器振动信号数据采集与分析

LabVIEW变压器振动信号数据采集与分析 随着电力系统的快速发展&#xff0c;对变压器的安全监控和故障诊断需求日益增加。设计了一套基于LabVIEW的变压器振动信号数据采集与分析系统&#xff0c;提高变压器的运行安全性和可靠性&#xff0c;实现对变压器振动信号的实时监测和故…

c语言---数组(超级详细)

数组 一.数组的概念二. 一维数组的创建和初始化2.1数组的创建2.2数组的初始化错误的初始化 2.3 数组的类型 三. 一维数组的使用3.1数组的下标3.2数组元素的打印3.2数组元素的输入 四. 一维数组在内存中的存储五. 二维数组的创建5.1二维数组的概念5.2如何创建二维数组 六.二维数…

Flutter Engine 编译

本地环境 Flutter 开发基本环境配置&#xff0c;SDK【】 MAC. M2芯片 git工具 python环境[MAC自带] xcode Chromium depot_tools depot_tools 是调试 Flutter 引擎的必备工具包&#xff0c;包含了 gclient、gn 和 ninja 等工具&#xff0c;这些在下面会用到&#xff01;…

信号系统之线性图像处理

1 卷积 图像卷积的工作原理与一维卷积相同。例如&#xff0c;图像可以被视为脉冲的总和&#xff0c;即缩放和移位的delta函数。同样&#xff0c;线性系统的特征在于它们如何响应脉冲。也就是说&#xff0c;通过它们的脉冲响应。系统的输出图像等于输入图像与系统脉冲响应的卷积…

Unity资源加密解决方案

据统计&#xff0c;全球范围内超过50%的游戏均使用Unity创作而成&#xff0c;作为游戏开发市场第一大游戏引擎占有者&#xff0c;Unity已经全面覆盖到各个游戏平台。 全球游戏引擎市场占有率 由于体量庞大&#xff0c;Unity游戏已成为受游戏黑灰产攻击的重灾区&#xff0c;因游…

Vue3自定义组件v-model双向绑定

无能吐槽一下&#xff0c;虽然用了很多遍v-model&#xff0c;但是还是不得要领&#xff0c;每次看官网都感觉说的不是很清晰&#xff0c;在写的时候还是要查看文档&#xff0c;可能就是不理解原理&#xff0c;这次特意好好写一篇文章&#xff0c;让自己好好理解一下。 自定义一…

网络编程、UDP、TCP

计算机网络 就是将地理位置不同的具有独立功能的多台计算及外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统、网络管理软件以及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统 目的 传播交流信息、数据交换、通信 如何做…

JVM(1)

JVM简介 JVM是Java Virtual Machine的简称,意为Java虚拟机. 在java中,它归属于jre(java运行时环境), 而jre归属于jdk(java开发工具包). 虚拟机是指通过软件模拟的具有完整硬件功能的,运行在一个完全隔离的环境中的完整计算机系统. 常见的虚拟机:JVM, VMwave, VirtualBox. J…

java——File类和字符集

目录 File类File类的常用操作&#xff1a;案例&#xff1a;文件搜索的实现案例&#xff1a;递归文件夹删除 字符集几种常见的字符集总结字符集的编码和解码 File类 File是java.io.包下的类&#xff0c;File类的对象&#xff0c;用于代表当前操作系统的文件&#xff08;可以是文…

聊聊JVM运行时数据区的堆内存

聊聊JVM运行时数据区的堆内存 内存模型变迁&#xff1a; Java堆在JVM启动时创建内存区域去实现对象、数组与运行时常量的内存分配&#xff0c;它是虚拟机管理最大的&#xff0c;也是垃圾回收的主要内存区域 。 内存模型变迁&#xff1a; 为什么要有年轻区和老年区&#xff1f;…

中兴助力低空经济发展,携山东移动完成5G-A通感一体商用验证

日前&#xff0c;中兴通讯在5G-A通感一体化技术研究和商用落地领域实现新突破。具体来说&#xff0c;中兴通讯联手山东移动&#xff0c;率先完成了5G-A&#xff08;5G-Advanced&#xff09;通感一体化技术试点&#xff0c;完成对低空无人机的通信感知融合测试。据悉&#xff0c…

java面试题之mysql篇

1、数据库索引 索引是对数据库表中一列或多列的值进行排序的一种结构&#xff0c;使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她&#xff0c;则与在表中搜索所有的行相比&#xff0c;索引有助于更快地获取信息。 索引的一个主要目的就是加快检索…

数据结构day4

实现创建单向循环链表、创建结点、判空、输出、头插、按位置插入、尾删、按位置删除 loop_list.c #include "loop_list.h" loop_p create_head() {loop_p L(loop_p)malloc(sizeof(loop_list));if(LNULL){printf("空间申请失败\n");return NULL;}L->le…

clip_as_service学习

参考&#xff1a;clip_as_service学习过程(一)——安装客户端与服务端_clip-as-service-CSDN博客 CLIP-as-service 0.8.3 documentation (jina.ai) pip3 install clip-client /usr/local/python3/bin/python3.7 -m pip install --upgrade pip pip3 install clip-server pyt…

boost搜索引擎

boost搜索引擎 1. 项目背景1.1 搜索引擎基本原理1.2 Boost库1.3 项目的目标 2. Boost搜索引擎宏观流程3. 技术栈与环境3.1 技术栈3.2 环境 4. 认识什么是索引4.1 正排索引4.2 倒排索引4.3 我们如何分词&#xff1f;4.4 模拟查找过程 5. 数据处理5.1 下载boost库到本地5.2 认识标…

IOS不使用默认的mainStroryboard作为首个controller的方法

步骤1&#xff1a; 删除info.plist文件下的一条配置&#xff0c;如图 步骤2&#xff1a; 编辑AppDelegate.m&#xff0c;参考以下代码 interface AppDelegate () //property (strong, nonatomic) UIWindow * window; property(nonatomic,strong) UIWindow * win; property(…
最新文章