Implementing Spring Security Password Flow in a Project

The overall framework is spring-cloud-alibaba-nacos + spring-security + jwt + redis.

  1. Authorization Server

a. pom.xml for the Authorization Server

<!-- Spring Security, OAuth2, and JWT -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Nacos Client -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- Nacos Configuration Center -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- Feign to call service interfaces -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- Web Support -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Validation support for Spring Boot 2.3.x -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<!-- MyBatis Plus Starter -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>

<!-- Druid Connection Pool -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<!-- MySQL Driver -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- Configuration Processor -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

<!-- Lombok for Setter/Getter -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<!-- Swagger -->
<dependency>
    <groupId>com.spring4all</groupId>
    <artifactId>swagger-spring-boot-starter</artifactId>
</dependency>

<!-- Aliyun SDKs -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

<!-- HTTP Request Utility -->
<dependency>
    <groupId>com.arronlong</groupId>
    <artifactId>httpclientutil</artifactId>
</dependency>

<!-- Utility Dependencies -->
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
</dependency>

b. bootstrap.yml and application.yml

spring:
  application:
    name: auth-server # The application name for this service, matching the prefix in Nacos's dataid
  cloud:
    nacos:
      discovery:
        server-addr: 172.21.25.56:8848 # Address of the Nacos server for registration
      config:
        server-addr: 172.21.25.56:8848 # Address of the Nacos configuration center
        file-extension: yml # File extension for configuration files in Nacos
  profiles:
    active: dev # Activate the development environment, which reads the auth-server-dev.yml configuration

server:
  port: 7001
  servlet:
    context-path: /auth # Context path for requests, e.g., ip:port/auth

spring:
  redis:
    host: 172.21.25.56
    port: 6379
    password: # No username needed for Redis
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://172.21.25.56:3306/dcy_blog_auth?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialSize: 8
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL

c. Startup Class

@EnableFeignClients // Scan Feign interfaces
@EnableDiscoveryClient // Mark as a Nacos client
@SpringBootApplication
public class AuthApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthApplication.class, args);
    }
}

d. Security Configuration Class

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

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

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

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler)
                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .and()
                .csrf().disable();
    }
}
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private IFeignSystemController feignSystemController;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StringUtils.isEmpty(username)) {
            throw new BadCredentialsException("Username cannot be empty");
        }
        SysUser sysUser = feignSystemController.findUserByUsername(username);
        if (sysUser == null) {
            throw new BadCredentialsException("Invalid username or password");
        }

        List<sysmenu> menuList = feignSystemController.findMenuListByUserId(sysUser.getId());

        List<grantedauthority> authorities = null;
        if (CollectionUtils.isNotEmpty(menuList)) {
            authorities = new ArrayList<>();
            for (SysMenu menu : menuList) {
                String code = menu.getCode();
                authorities.add(new SimpleGrantedAuthority(code));
            }
        }

        JwtUser jwtUser = new JwtUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(),
                sysUser.getNickName(), sysUser.getImageUrl(), sysUser.getMobile(), sysUser.getEmail(),
                sysUser.getIsAccountNonExpired(), sysUser.getIsAccountNonLocked(),
                sysUser.getIsCredentialsNonExpired(), sysUser.getIsEnabled(),
                authorities);

        return jwtUser;
    }
}</grantedauthority></sysmenu>
@Data
public class JwtUser implements UserDetails {
    @ApiModelProperty(value = "User ID")
    private String uid;

    @ApiModelProperty(value = "Username")
    private String username;

    @JSONField(serialize = false)
    @ApiModelProperty(value = "Password, encrypted storage, admin/1234")
    private String password;

    @ApiModelProperty(value = "Nickname")
    private String nickName;

    @ApiModelProperty(value = "Avatar URL")
    private String imageUrl;

    @ApiModelProperty(value = "Registered phone number")
    private String mobile;

    @ApiModelProperty(value = "Registered email")
    private String email;

    @JSONField(serialize = false)
    @ApiModelProperty(value = "Account not expired (1 not expired, 0 expired)")
    private boolean isAccountNonExpired;

    @JSONField(serialize = false)
    @ApiModelProperty(value = "Account not locked (1 not locked, 0 locked)")
    private boolean isAccountNonLocked;

    @JSONField(serialize = false)
    @ApiModelProperty(value = "Credentials not expired (1 not expired, 0 expired)")
    private boolean isCredentialsNonExpired;

    @JSONField(serialize = false)
    @ApiModelProperty(value = "Account available (1 available, 0 deleted)")
    private boolean isEnabled;

    @JSONField(serialize = false)
    private List<grantedauthority> authorities;

    public JwtUser(String uid, String username, String password,
                   String nickName, String imageUrl, String mobile, String email,
                   Integer isAccountNonExpired, Integer isAccountNonLocked,
                   Integer isCredentialsNonExpired, Integer isEnabled,
                   List<grantedauthority> authorities) {
        this.uid = uid;
        this.username = username;
        this.password = password;
        this.nickName = nickName;
        this.imageUrl = imageUrl;
        this.mobile = mobile;
        this.email = email;
        this.isAccountNonExpired = isAccountNonExpired == 1 ? true : false;
        this.isAccountNonLocked = isAccountNonLocked == 1 ? true : false;
        this.isCredentialsNonExpired = isCredentialsNonExpired == 1 ? true : false;
        this.isEnabled = isEnabled == 1 ? true : false;
        this.authorities = authorities;
    }
}</grantedauthority></grantedauthority>
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException e) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        String result = objectMapper.writeValueAsString(Result.error(e.getMessage()));
        response.getWriter().write(result);
    }
}
@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    Logger logger = LoggerFactory.getLogger(getClass());

    private static final String HEADER_TYPE = "Basic ";

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        logger.info("Login successful {}", authentication.getPrincipal());
        String header = request.getHeader(HttpHeaders.AUTHORIZATION);

        logger.info("Header {}", header);

        Result result = null;

        try {
            if (header == null || !header.startsWith(HEADER_TYPE)) {
                throw new UnsupportedOperationException("No client information in the request header");
            }

            String[] tokens = RequestUtil.extractAndDecodeHeader(header);
            assert tokens.length == 2;

            String clientId = tokens[0];
            String clientSecret = tokens[1];

            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
            if (clientDetails == null) {
                throw new UnsupportedOperationException("Client ID configuration does not exist: " + clientId);
            }

            if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
                throw new UnsupportedOperationException("Invalid client secret");
            }

            TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

            OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

            result = Result.ok(accessToken);
        } catch (Exception e) {
            logger.error("Authentication success handler exception={}", e.getMessage(), e);
            result = Result.build(ResultEnum.AUTH_FAIL.getCode(), e.getMessage());
        }

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Bean
    public ClientDetailsService jdbcClientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(jdbcClientDetailsService());
    }

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;

    @Resource
    private TokenStore tokenStore;

    @Resource
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Resource
    private TokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.userDetailsService(userDetailsService);
        endpoints.tokenStore(tokenStore).accessTokenConverter(jwtAccessTokenConverter);

        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<tokenenhancer> enhancerList = new ArrayList<>();
        enhancerList.add(jwtTokenEnhancer);
        enhancerList.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(enhancerList);
        endpoints.tokenEnhancer(enhancerChain).accessTokenConverter(jwtAccessTokenConverter);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()");
    }
}</tokenenhancer>
@Configuration
public class PasswordEncoderConfig {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
public class RequestUtil {
    public static String[] extractAndDecodeHeader(String header) throws IOException {
        byte[] base64Token = header.trim().substring(6).getBytes(StandardCharsets.UTF_8);
        byte[] decoded;
        try {
            decoded = Base64.getDecoder().decode(base64Token);
        } catch (IllegalArgumentException var8) {
            throw new RuntimeException("Header parsing failed: " + header);
        }

        String token = new String(decoded, "UTF-8");
        int delim = token.indexOf(":");
        if (delim == -1) {
            throw new RuntimeException("Invalid header: " + token);
        } else {
            return new String[]{token.substring(0, delim), token.substring(delim + 1)};
        }
    }
}
@Component("customLogoutSuccessHandler")
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void onLogoutSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication) throws IOException, ServletException {
        String accessToken = request.getParameter("accessToken");
        if (StringUtils.isNotBlank(accessToken)) {
            OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessToken);
            if (oAuth2AccessToken != null) {
                tokenStore.removeAccessToken(oAuth2AccessToken);
            }
        }

        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(Result.ok().toJsonString());
    }
}
@Configuration
public class JwtTokenStoreConfig {

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory factory = new KeyStoreKeyFactory(
                new ClassPathResource("oauth2.jks"), "oauth2".toCharArray());
        converter.setKeyPair(factory.getKeyPair("oauth2"));

        return converter;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter()) {
            @Override
            public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
                if (token.getAdditionalInformation().containsKey("jti")) {
                    String jti = token.getAdditionalInformation().get("jti").toString();
                    redisTemplate.opsForValue()
                            .set(jti, token.getValue(), token.getExpiresIn(), TimeUnit.SECONDS);
                }
                super.storeAccessToken(token, authentication);
            }

            @Override
            public void removeAccessToken(OAuth2AccessToken token) {
                if (token.getAdditionalInformation().containsKey("jti")) {
                    String jti = token.getAdditionalInformation().get("jti").toString();
                    redisTemplate.delete(jti);
                }
                super.removeAccessToken(token);
            }
        };
    }
}
@Component
public class JwtTokenEnhancer implements TokenEnhancer {

    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken,
                                     OAuth2Authentication oAuth2Authentication) {
        JwtUser user = (JwtUser) oAuth2Authentication.getPrincipal();
        Map<string object=""> map = new HashMap<>();
        map.put("userInfo", JSON.toJSON(user));

        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(map);

        return oAuth2AccessToken;
    }
}</string>

e. Chinese Authentication Messages

@Configuration
public class ReloadMessageConfig {

    @Bean
    public ReloadableResourceBundleMessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource =
                new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:messages_zh_CN");
        return messageSource;
    }
}

f. Refresh Token

@RestController
public class AuthController {
    Logger logger = LoggerFactory.getLogger(getClass());

    private static final String HEADER_TYPE = "Basic ";

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthService authService;

    @GetMapping("/user/refreshToken")
    public Result refreshToken(HttpServletRequest request) {
        try {
            String refreshToken = request.getParameter("refreshToken");

            Preconditions.checkArgument(StringUtils.isNotEmpty(refreshToken), "Refresh token cannot be empty");

            String header = request.getHeader(HttpHeaders.AUTHORIZATION);
            if (header == null || !header.startsWith(HEADER_TYPE)) {
                throw new UnsupportedOperationException("No client information in the request header");
            }

            String[] tokens = RequestUtil.extractAndDecodeHeader(header);
            assert tokens.length == 2;

            String clientId = tokens[0];
            String clientSecret = tokens[1];

            ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
            if (clientDetails == null) {
                throw new UnsupportedOperationException("Client ID configuration does not exist: " + clientId);
            }

            if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
                throw new UnsupportedOperationException("Invalid client secret");
            }

            return authService.refreshToken(header, refreshToken);
        } catch (Exception e) {
            logger.error("refreshToken={}", e.getMessage(), e);
            return Result.error("Failed to get new token: " + e.getMessage());
        }
    }
}
@Service
public class AuthService {
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    public Result refreshToken(String header, String refreshToken) throws HttpProcessException {
        ServiceInstance serviceInstance = loadBalancerClient.choose("auth-server");
        if (serviceInstance == null) {
            return Result.error("No valid authentication server found, please try again later");
        }

        String refreshTokenUrl = serviceInstance.getUri().toString() + "/auth/oauth/token";

        Map<string object=""> map = new HashMap<>();
        map.put("grant_type", "refresh_token");
        map.put("refresh_token", refreshToken);

        Header[] headers = HttpHeader.custom()
                .contentType(HttpHeader.Headers.APP_FORM_URLENCODED)
                .authorization(header)
                .build();

        HttpConfig config = HttpConfig.custom().headers(headers).url(refreshTokenUrl).map(map);

        String token = HttpClientUtil.post(config);

        JSONObject jsonToken = JSON.parseObject(token);
        if (StringUtils.isNotEmpty(jsonToken.getString("error"))) {
            return Result.build(ResultEnum.TOKEN_PAST);
        }

        return Result.ok(jsonToken);
    }
}</string>
  1. Resource Server

a. pom.xml for the Resource Server

<!-- Spring Security, OAuth2, and JWT -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

b. Resource Server Configuartion Class

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableResourceServer
@Configuration
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private TokenStore tokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/v2/api-docs", "/v2/feign-docs",
                        "/swagger-resources/configuration/ui",
                        "/swagger-resources", "/swagger-resources/configuration/security",
                        "/swagger-ui.html", "/webjars/**").permitAll()
                .antMatchers("/api/**").permitAll()
                .antMatchers("/**").access("#oauth2.hasScope('all')")
                .anyRequest().authenticated();
    }
}
@Configuration
public class JwtTokenStoreConfig {
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        ClassPathResource resource = new ClassPathResource("public.txt");
        String publicKey = null;
        try {
            publicKey = IOUtils.toString(resource.getInputStream(), "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        converter.setVerifierKey(publicKey);
        converter.setAccessTokenConverter(new CustomAccessTokenConverter());
        return converter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    private class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
        @Override
        public OAuth2Authentication extractAuthentication(Map<string> map) {
            OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
            oAuth2Authentication.setDetails(map);
            return oAuth2Authentication;
        }
    }
}</string>

c. Solving Request Header Loss During Remote Calls

@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes =
                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String token = request.getHeader(HttpHeaders.AUTHORIZATION);
            if (StringUtils.isNotEmpty(token)) {
                requestTemplate.header(HttpHeaders.AUTHORIZATION, token);
            }
        }
    }
}

d. Retrieving Current User

public class AuthUtil {
    public static SysUser getUserInfo() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        Map<string object=""> map = (Map<string object="">) details.getDecodedDetails();
        Map<string string=""> userInfo = (Map<string string="">) map.get("userInfo");

        SysUser user = new SysUser();
        user.setId(userInfo.get("uid"));
        user.setNickName(userInfo.get("nickName"));
        user.setUsername(userInfo.get("username"));
        user.setEmail(userInfo.get("email"));
        user.setImageUrl(userInfo.get("imageUrl"));
        user.setMobile(userInfo.get("mobile"));

        return user;
    }
}</string></string></string></string>
  1. Gateway Server

a. pom.xml for the Gateway Server

<!-- Parse JWT -->
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>6.0</version>
</dependency>

b. Determine if Requests Are Allowed Based on the Presence of Authentication Information in the Request Header

@Component
public class AuthenticationFilter implements GlobalFilter, Ordered {

    private static final String[] white = {"/api/"};

    @Override
    public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getPath().pathWithinApplication().value();

        if (StringUtils.indexOfAny(path, white) != -1) {
            return chain.filter(exchange);
        }

        String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if (StringUtils.isEmpty(authorization)) {
            JSONObject message = new JSONObject();
            message.put("code", 1401);
            message.put("message", "Missing identity credential");

            byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}</void>

c. Determine if Requests Are Allowed Based on the Token Storage Status in Redis

@Component
public class AccessTokenFilter implements GlobalFilter, Ordered {
    Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private RedisTemplate<string object=""> redisTemplate;

    @Override
    public Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        String token = StringUtils.substringAfter(authorization, "Bearer ");

        if (StringUtils.isEmpty(token)) {
            return chain.filter(exchange);
        }

        String message = null;

        try {
            JWSObject jwsObject = JWSObject.parse(token);
            JSONObject jsonObject = jwsObject.getPayload().toJSONObject();
            String jti = jsonObject.get("jti").toString();
            Object value = redisTemplate.opsForValue().get(jti);
            if (value == null) {
                logger.info("Token has expired {}", token);
                message = "Your identity has expired, please re-authenticate!";
            }
        } catch (ParseException e) {
            logger.error("Parsing token failed {}", token);
            message = "Invalid token";
        }

        if (message == null) {
            return chain.filter(exchange);
        }

        JSONObject result = new JSONObject();
        result.put("code", 1401);
        result.put("message", message);

        byte[] bits = result.toJSONString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        return 10;
    }
}</void></string>

Tags: spring-security OAuth2 JWT Redis Nacos

Posted on Thu, 18 Jun 2026 17:49:25 +0000 by tachekent