The overall framework is spring-cloud-alibaba-nacos + spring-security + jwt + redis.
- 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>
- 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>
- 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>