"springcloud 2021 series" Spring Cloud Gateway + OAuth2 + JWT realize unified authentication and authentication

Posted by chrisranjana on Tue, 15 Feb 2022 01:35:40 +0100

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

Spring Cloud Gateway + Spring Security OAuth2 + JWT realize unified authentication and authorization of microservices

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!

Topics: Spring Spring Cloud Microservices gateway