Carry out unified authentication through authentication service, and then verify authentication and authentication through gateway.
Nacos will be used as the registration center, Gateway as the Gateway, and nimbus Jose JWT JWT library will be used to operate JWT tokens
Theoretical introduction
Spring Security is powerful and easy to customize. It is based on the application security framework developed by spring to realize authentication login and resource authorization
The core functions of spring security:
- Authentication: identity authentication, user login authentication (solve the problem of who you are)
- Authorization: access authorization, authorizing the access rights of system resources (solving the problem of what you can do)
- Security protection to prevent cross site requests, session attacks, etc
SpringSecurity configuration class
-
configure(HttpSecurity httpSecurity)
It is used to configure the url path to be intercepted, jwt filter and processor after exception
-
configure(AuthenticationManagerBuilder auth)
Used to configure UserDetailsService and PasswordEncoder
-
RestfulAccessDeniedHandler
The processor when the user does not have access rights, which is used to return the processing results in JSON format
-
RestAuthenticationEntryPoint
When the format of token is not valid, the result of JSON is returned
-
UserDetailsService
The core interface defined by spring security is used to obtain user information according to the user name, which needs to be implemented by itself
-
UserDetails
Spring security defines classes used to encapsulate user information (mainly user information and permissions), which need to be implemented by itself
-
PasswordEncoder
The interface defined by spring security for encoding and comparing passwords currently uses BCryptPasswordEncoder
-
JwtAuthenticationTokenFilter
The filter added before user name and password verification will log in according to the token information if there is a jwt token
JWT single sign on
When the user logs in and authenticates on application A, application A will issue him A JWT Token (A string containing several user status information). When the user accesses the interface of application B, he will give this string to application B, which will authenticate according to the contents in the Token. Different applications issue JWT tokens according to unified standards, and verify JWT tokens according to unified standards. Thus, the Token you get on application A is also recognized on application B. of course, the underlying database between these applications must be the same set of user, role and authority data.
- Unified authentication Controller code
- The authentication Filter code is unified and the verification rules are the same
- Use the same set of authorization data
- The same secret for signing and countersigning
Problems of JWT
After all, JWT is not seamless. Some problems caused by the maintenance of login status by the client still exist here, for example:
-
Renewal is one of the problems criticized by many people. The traditional cookie+session scheme naturally supports renewal, but jwt is difficult to solve the renewal problem perfectly because the server does not save the user's state. If redis is introduced, it can solve the problem, but jwt becomes different
-
The problem of logout is that the server no longer saves user information, so logout can generally be realized by modifying the secret. After modifying the secret of the server, the issued unexpired token will fail to authenticate and then realize logout, but it is not convenient for traditional logout after all
-
After the password is reset, the original token can still access the system. At this time, it is also necessary to modify the secret forcibly
-
Based on points 2 and 3, it is generally recommended that different users take different secret s
OAuth2
All the above single point cluster login schemes have A premise: application A, application B, application 1, application 2 and application 3 are owned by your company, and single point login verification is carried out among internal applications of your company.
But we have all seen such a scene: log in to a website and then use the user data saved on QQ and wechat. In other words, if a third-party application wants to use the user data of an authoritative platform for login authentication, how can this authoritative platform provide authentication services for third-party applications? At present, the common method is OAuth2 (modern social media websites basically use OAuth2 for login)
- The Spring Security OAuth project has entered the maintenance state and will no longer develop new features. Only functional maintenance and secondary feature development
- All future Spring based oauth2 0 is based on Spring Security version 5.2. That is, the version of Spring security after 5.2 is oauth2 0 support library to replace Spring Security OAuth
OAuth2 demand scenario
Before explaining the OAuth2 requirements and usage scenarios, you need to first introduce the various roles in the OAuth2 authorization process:
- User - refers to the user of the application, usually the login user of the system
- Authorization Server - a server that provides login authentication interfaces, such as github login, QQ login, wechat login, etc
- Resources Server - a server that provides resource interfaces and services, such as user information interfaces. It is usually the same application as the authentication server.
- Third party Client - a third-party application that wants to use the resources provided by the resource server
- Service provider: the authentication service and resource service belong to one organization, which is the service provider
OAuth2 four authorization modes
- authorization code mode
- Simplified mode (implicit)
- resource owner password credentials
- client credentials
The biggest difference between password mode and authorization code mode is:
- In the authorization code mode, the process of applying for the authorization code is that the user directly interacts with the authentication server, and then the authentication server informs the third-party client of the authorization result, that is, the user password information of the service provider will not be exposed to the third-party client
- Password mode is that the user gives the user password information to the third-party client, and then the third party authenticates and requests resources from the service provider. The vast majority of service providers will choose to use the authorization code mode to avoid the disclosure of their user passwords to third parties. Therefore, the password mode can only be used when the service provider has high trust in the third-party manufacturer (third-party application), or this "third-party application" is actually the service provider's own application
Integrate JWT
Because Spring Security OAuth "authentication server" supports multiple authentication modes, I don't want to abandon it. But I want to change the last "resource access token" from AccessToken to JWT token. Because AccessToken does not carry any additional information, it is a string, and JWT can carry additional information.
Application Architecture
The ideal solution should be that the authentication service is responsible for authentication, the gateway is responsible for verification, authentication and authentication, and other API services are responsible for processing their own business logic. Security related logic only exists in authentication services and gateway services. Other services only provide services without any security related logic
Division of relevant services:
- Security oauth2 gateway: gateway service, responsible for request forwarding and authentication functions, integrating Spring Security+Oauth2
- Security Oauth2 auth: Oauth2 authentication service, which is responsible for authenticating login users and integrating Spring Security+Oauth2
- Security oauth2 API: protected API service. Users can access the service after authentication. Spring Security+Oauth2 is not integrated
Scheme realization
The following describes the specific implementation of this solution, and builds authentication service, gateway service and API service in turn
Security oauth2 auth authentication
First, build the authentication service, which will be used as the authentication service of Oauth2, and the authentication function of the gateway service also needs to rely on it
pom dependency
- In POM Add relevant dependencies to XML, mainly Spring Security, Oauth2, JWT and Redis
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-rsa</artifactId> </dependency> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> </dependency> </dependencies>
- In application Add relevant configurations to YML, mainly related to Nacos and Redis
server: port: 9401 spring: profiles: active: dev application: name: security-oauth2-auth cloud: nacos: discovery: server-addr: 192.168.123.22:8848 username: nacos password: nacos redis: port: 6379 host: localhost password: xxx management: endpoints: web: exposure: include: "*"
Authentication service configuration
Generate keystore
- Use keytool to generate RSA certificate JWT JKS, copy it to the resource directory, and use the following command in the bin directory of JDK
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
Load user information
- Create a UserServiceImpl class to implement the UserDetailsService interface of Spring Security, which is used to load user information
/** * User management business class */ @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UmsAdminService adminService; @Autowired private HttpServletRequest request; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String clientId = request.getParameter("client_id"); UserDto userDto = null; if (AuthConstant.ADMIN_CLIENT_ID.equals(clientId)) { userDto = adminService.loadUserByUsername(username); } if (null == userDto) { throw new UsernameNotFoundException(EC.ERROR_USER_PASSWORD_INCORRECT.getMsg()); } SecurityUserDetails securityUser = new SecurityUserDetails(userDto); if (!securityUser.isEnabled()) { throw new DisabledException(EC.ERROR_USER_ENABLED.getMsg()); } else if (!securityUser.isAccountNonLocked()) { throw new LockedException(EC.ERROR_USER_LOCKED.getMsg()); } else if (!securityUser.isAccountNonExpired()) { throw new AccountExpiredException(EC.ERROR_USER_EXPIRE.getMsg()); } else if (!securityUser.isCredentialsNonExpired()) { throw new CredentialsExpiredException(EC.ERROR_USER_UNAUTHORIZED.getMsg()); } return securityUser; } } @Component public class UmsAdminService { @Autowired private PasswordEncoder passwordEncoder; public UserDto loadUserByUsername(String username) { String password = passwordEncoder.encode("123456a"); if("admin".equals(username)) { return new UserDto("admin", password, 1, "", CollUtil.toList("ADMIN")); } else if("langya".equals(username)) { return new UserDto("langya", password, 1, "", CollUtil.toList("ADMIN", "TEST")); } return null; } }
Authentication service configuration
- Add the authentication service related configuration Oauth2ServerConfig. You need to configure the service UserServiceImpl that loads user information and the key pair KeyPair of RSA
/** * Authentication server configuration */ @AllArgsConstructor @Configuration @EnableAuthorizationServer public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter { private final PasswordEncoder passwordEncoder; private final UserDetailsServiceImpl userDetailsService; private final AuthenticationManager authenticationManager; private final JwtTokenEnhancer jwtTokenEnhancer; /** * Client information configuration */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient(AuthConstant.ADMIN_CLIENT_ID) .secret(passwordEncoder.encode("123456")) .scopes("all") .authorizedGrantTypes("password", "refresh_token") .accessTokenValiditySeconds(3600 * 24) .refreshTokenValiditySeconds(3600 * 24 * 7) .and() .withClient(AuthConstant.PORTAL_CLIENT_ID) .secret(passwordEncoder.encode("123456")) .scopes("all") .authorizedGrantTypes("password", "refresh_token") .accessTokenValiditySeconds(3600 * 24) .refreshTokenValiditySeconds(3600 * 24 * 7); } /** * Configure authorization and token */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { TokenEnhancerChain enhancerChain = new TokenEnhancerChain(); List<TokenEnhancer> delegates = new ArrayList<>(); delegates.add(jwtTokenEnhancer); delegates.add(accessTokenConverter()); //Configure content enhancer for JWT enhancerChain.setTokenEnhancers(delegates); endpoints.authenticationManager(authenticationManager) //Configure the service that loads user information .userDetailsService(userDetailsService) .accessTokenConverter(accessTokenConverter()) .tokenEnhancer(enhancerChain); } /** * Allow form authentication */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients(); } /** * Sign the token using asymmetric encryption algorithm */ @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); //or set symmetric signature //jwtAccessTokenConverter.setSigningKey("2430B31859314947BC84697E70B3D31F"); jwtAccessTokenConverter.setKeyPair(keyPair()); return jwtAccessTokenConverter; } /** * Get the key pair (public key + private key) from the keystore under classpath */ @Bean public KeyPair keyPair() { //Get the secret key pair from the certificate under classpath KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray()); return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray()); } }
jwt enhancement
- If you want to add custom information to JWT, such as the ID of the login user, you can implement the TokenEnhancer interface yourself
/** * JWT Content Enforcer */ @Component public class JwtTokenEnhancer implements TokenEnhancer { @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { SecurityUserDetails securityUser = (SecurityUserDetails) authentication.getPrincipal(); //Set the user name to JWT Map<String, Object> info = new HashMap<>(); info.put("user_name", securityUser.getUsername()); info.put("client_id", securityUser.getClientId()); ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info); return accessToken; } }
Public key acquisition interface
- Because the gateway service needs RSA's public key to verify whether the signature is legal, the authentication service needs an interface to expose the public key
/** * Get RSA public key interface */ @RestController public class KeyPairController { @Autowired private KeyPair keyPair; @GetMapping("/rsa/publicKey") public Map<String, Object> getKey() { RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAKey key = new RSAKey.Builder(publicKey).build(); return new JWKSet(key).toJSONObject(); } }
Security configuration
- Don't forget to configure Spring Security to allow access to the public key interface
/** * SpringSecurity Security configuration */ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .antMatchers("/rsa/publicKey").permitAll() .anyRequest().authenticated(); } /** * If you do not configure SpringBoot, you will automatically configure an authentication manager to overwrite the users in memory */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
Resource role mapping cache
- Create a resource service ResourceServiceImpl and cache the matching relationship between resources and roles in Redis during initialization to facilitate the gateway service to obtain it during authentication
/** * Resource and role matching relationship management business class * <p> * During initialization, the matching relationship between resources and roles is cached in Redis, which is convenient for the gateway service to obtain during authentication */ @Service public class ResourceServiceImpl { @Autowired private RedisTemplate<String, Object> redisTemplate; private Map<String, List<String>> resourceRolesMap; @PostConstruct public void initData() { resourceRolesMap = new TreeMap<>(); resourceRolesMap.put("/admin/hello", CollUtil.toList("ADMIN")); resourceRolesMap.put("/admin/user/currentUser", CollUtil.toList("ADMIN", "TEST")); redisTemplate.opsForHash().putAll(AuthConstant.RESOURCE_ROLES_MAP_KEY, resourceRolesMap); } }
If the resource permissions are stored in the database, you can also directly use SQL statements to form a result set, such as:
Security oauth2 gateway authentication
Next, you can build the gateway service, which will be used as the resource service and client service of Oauth2 to conduct unified verification, authentication and authentication for requests to access micro services
pom dependency
- In POM Add relevant dependencies to XML, mainly Gateway, Oauth2 and JWT
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--lb:// need--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-resource-server</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency> <dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> </dependency> </dependencies>
- In application Add relevant configurations to YML, mainly including the configuration of routing rules, RSA public key in Oauth2 and routing white list
server: port: 9201 spring: main: #Spring cloud gateway is implemented internally through netty + weblux #Weblux implementation and spring boot starter web dependency conflict web-application-type: reactive profiles: active: dev application: name: security-oauth2-gateway cloud: nacos: discovery: server-addr: localhost:8848 username: nacos password: nacos gateway: routes: #Configure routing path - id: oauth2-api-route uri: lb://security-oauth2-api predicates: - Path=/admin/** filters: - StripPrefix=1 - id: oauth2-auth-route uri: lb://security-oauth2-auth predicates: - Path=/auth/** filters: - StripPrefix=1 discovery: locator: #Enable the function of dynamically creating routes from the registry enabled: true #Use lowercase service name, which is uppercase by default lower-case-service-id: true security: oauth2: resourceserver: jwt: #Configure RSA's public key access address jwk-set-uri: 'http://localhost:9401/rsa/publicKey' redis: host: 192.168.123.22 port: 6379 password: Hacfin_Redis8 timeout: 6000ms secure: ignore: #Configure whitelist path urls: - "/actuator/**" - "/auth/oauth/token"
Resource server configuration
- Configure the security configuration of the Gateway service. Since the Gateway uses WebFlux, it needs to be enabled with the @ EnableWebFluxSecurity annotation
/** * Resource server configuration */ @AllArgsConstructor @Configuration @EnableWebFluxSecurity public class ResourceServerConfig { private final AuthorizationManager authorizationManager; private final IgnoreUrlsConfig ignoreUrlsConfig; private final RestfulAccessDeniedHandler restfulAccessDeniedHandler; private final RestAuthenticationEntryPoint restAuthenticationEntryPoint; private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter; @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.oauth2ResourceServer().jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()); //Customize the results of processing JWT request header expiration or signature error http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint); //For the whitelist path, directly remove the JWT request header http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION); http.authorizeExchange() //White list configuration .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(), String.class)).permitAll() //Authentication manager configuration .anyExchange().access(authorizationManager) .and() .exceptionHandling() //Processing unauthorized .accessDeniedHandler(restfulAccessDeniedHandler) //Handling unauthenticated .authenticationEntryPoint(restAuthenticationEntryPoint) .and() .csrf().disable(); return http.build(); } /** * @linkhttps://blog.csdn.net/qq_24230139/article/details/105091273 * ServerHttpSecurity The load part of the authorities in jwt is not regarded as Authentication * You need to add the authorities in jwt's Claim * Scheme: redefine the ReactiveAuthenticationManager permission manager, and the default converter is JwtGrantedAuthoritiesConverter */ @Bean public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } }
Authentication manager
- In WebFluxSecurity, the user-defined authentication operation needs to implement the ReactiveAuthorizationManager interface
@Component public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> { @Autowired private RedisTemplate<String,Object> redisTemplate; @Override public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) { //Obtain the list of accessible roles of the current path from Redis URI uri = authorizationContext.getExchange().getRequest().getURI(); Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath()); List<String> authorities = Convert.toList(String.class,obj); authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList()); //Users who have passed the authentication and matching roles can access the current path return mono .filter(Authentication::isAuthenticated) .flatMapIterable(Authentication::getAuthorities) .map(GrantedAuthority::getAuthority) .any(authorities::contains) .map(AuthorizationDecision::new) .defaultIfEmpty(new AuthorizationDecision(false)); } }
filter
- Here, we also need to implement a global filter AuthGlobalFilter. When the authentication is passed, the user information in the JWT token is parsed out and then stored in the request Header. In this way, the subsequent service does not need to parse the JWT token and can directly obtain the user information from the request Header
/** * A global filter that converts the JWT of the logged in user into user information */ @Component public class AuthGlobalFilter implements GlobalFilter, Ordered { @Autowired private RedisTemplate redisTemplate; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { //The authentication information is obtained from the Header or request parameters ServerHttpRequest serverHttpRequest = exchange.getRequest(); String token = serverHttpRequest.getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER); if (Objects.isNull(token)) { token = serverHttpRequest.getQueryParams().getFirst(AuthConstant.JWT_TOKEN_HEADER); } if (StrUtil.isEmpty(token)) { return chain.filter(exchange); } try { //Parse the user information from the token and set it to the Header String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, ""); JWSObject jwsObject = JWSObject.parse(realToken); String userStr = jwsObject.getPayload().toString(); // Blacklist token verification JSONObject jsonObject = JSONUtil.parseObj(userStr); String jti = jsonObject.getStr("jti"); Boolean isBlack = redisTemplate.hasKey(AuthConstant.TOKEN_BLACKLIST_PREFIX + jti); if (isBlack) { } // There is a token and it is not a blacklist. The request writes the carrier information of the JWT ServerHttpRequest request = serverHttpRequest.mutate().header(AuthConstant.USER_TOKEN_HEADER, userStr).build(); exchange = exchange.mutate().request(request).build(); } catch (ParseException e) { e.printStackTrace(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
security-oauth2-api
Finally, build an API service, which will not integrate and implement any security related logic. It depends on the gateway to protect it
pom dependency
- In POM Adding related dependencies to XML adds a web dependency
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
Login information interface
- Create a LoginUserHolder component to directly obtain the login user information from the request Header
/** * Get login user information */ @Component public class LoginUserHolder { public UserDto getCurrentUser(HttpServletRequest request) { String userStr = request.getHeader(AuthConstant.USER_TOKEN_HEADER); JSONObject userJsonObject = new JSONObject(userStr); UserDto userDTO = new UserDto(); userDTO.setUserName(userJsonObject.getStr("user_name")); userDTO.setClientId(userJsonObject.getStr("client_id")); userDTO.setRoles(Convert.toList(String.class, userJsonObject.get(AuthConstant.AUTHORITY_CLAIM_NAME))); return userDTO; } }
- Create an interface to get the current user information
@RestController @RequestMapping("/user") public class UserController{ @Autowired private LoginUserHolder loginUserHolder; @GetMapping("/currentUser") public UserDTO currentUser() { return loginUserHolder.getCurrentUser(); } }
Function demonstration
Next, let's demonstrate the unified authentication function in the next microservice system. All requests are accessed through the gateway
-
Start Nacos and Redis services
-
Start the security-oauth2-auth, security-oauth2-gateway and security-oauth2-api services
- Use password mode to obtain JWT token. POST access address: http://localhost:9201/auth/oauth/token
- Use the obtained JWT token to access the interface to obtain the current login user information. Access address: http://localhost:9201/api/user/currentUser
- When the JWT token expires, refresh is used_ Token to obtain a new JWT token. Access address: http://localhost:9201/auth/oauth/token
extend
reference resources
Spring Cloud Gateway + Oauth2 realize unified authentication and authentication
Oauth2 best solution for customizing processing results
It is said that your JWT library is very twisted to use. I recommend this thief to use!