The spring cloud gateway carries a Token for the whitelist interface, and the direct authentication of the gateway fails. The problem is solved
1. Problem description
Previously, when using SpringCloudGateway to integrate SpringSecurity for authentication and authorization of Oauth2, because the white list needs to be set in the gateway, the URL of the white list does not need authentication and authorization and is released directly. During the project development process, it is found that there is a problem that when the path interface of the white list does not carry a token for access, it can be accessed normally and released by the gateway, However, when the interface of the white list path carries an incorrect token in the request header for access, the gateway will directly report authentication failure.
Note: the following figure is only used to describe the problem and does not need to be tangled.
Figure 1: the white list does not carry token access, and the gateway is released normally
Figure 2: the white list request carries incorrect token access, and the gateway authentication fails
2. Problem solving
Since the white list request does not carry token access, and the gateway can release normally, can you directly remove the Authorization information in the request header and rewrite the request access during the white list access? Later, after consulting the relevant materials, it is found that this scheme is feasible. The specific operations are as follows for reference only!
2.1. Add a custom filter in the spring cloud Gateway project
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; import java.util.List; /** * The request header authentication information needs to be removed when accessing the whitelist path * * @author Starry sky fleeting year */ @Component public class WhiteListAuthorizationFilter implements WebFilter { @Resource private WhiteListProperties properties; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); PathMatcher pathMatcher = new AntPathMatcher(); //Whitelist path remove request header authentication information List<String> urls = properties.getUrls(); for (String url : urls) { if (pathMatcher.match(url, path)) { request = exchange.getRequest().mutate().header(HttpHeaders.AUTHORIZATION, "").build(); exchange = exchange.mutate().request(request).build(); return chain.filter(exchange); } } return chain.filter(exchange); } }
Note: the whitelist configuration information class is as follows
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; import java.util.List; /** * White list release path * * @author Starry fleeting year */ @ConfigurationProperties(prefix = "whitelist") @Component @RefreshScope public class WhiteListProperties { private List<String> urls; public List<String> getUrls() { return urls; } public void setUrls(List<String> urls) { this.urls = urls; } @Override public String toString() { return "WhiteListProperties{" + "urls=" + urls + '}'; } }
2.2. Add a custom filter before the default authentication filter
Before configuring the custom filter to the default authentication filter, configure it in ResourceServerConfig.
The work of ResourceServerConfig here is to configure the authentication manager AuthorizationManager to the resource server to request white list release, unauthorized access and user-defined exception response of invalid token.
Note: where to add a custom filter
import cn.hutool.core.util.ArrayUtil; import cn.hutool.json.JSONUtil; import lombok.AllArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.SecurityWebFiltersOrder; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; /** * Resource server configuration * * @author Starry fleeting year */ @AllArgsConstructor @Configuration @EnableWebFluxSecurity public class ResourceServerConfig { @Resource private AuthorizationManager authorizationManager; @Resource private WhiteListProperties properties; @Resource private WhiteListAuthorizationFilter authenticationFilter; private static final Logger log = LoggerFactory.getLogger(ResourceServerConfig.class); @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); http.oauth2ResourceServer().authenticationEntryPoint(authenticationEntryPoint()); http.addFilterBefore(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION); http.authorizeExchange().pathMatchers(ArrayUtil.toArray(properties.getUrls(), String.class)).permitAll().anyExchange().access(authorizationManager) .and().exceptionHandling().accessDeniedHandler(accessDeniedHandler()).authenticationEntryPoint(authenticationEntryPoint()) .and().csrf().disable(); return http.build(); } /** * Unauthorized * * @return */ @Bean ServerAccessDeniedHandler accessDeniedHandler() { return (exchange, denied) -> Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> { response = responseInfo(response); String body = JSONUtil.toJsonStr(Result.fail(RestStatus.INVALID_TOKEN.getCode(), "Access not authorized, Please confirm the validity of the token!")); log.error("Access not authorized, The response information is: {}", body); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer)); }); } /** * token Invalid or expired custom response * * @return */ @Bean ServerAuthenticationEntryPoint authenticationEntryPoint() { return (exchange, e) -> Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> { response = responseInfo(response); String body = JSONUtil.toJsonStr(Result.fail(RestStatus.INVALID_TOKEN.getCode(), "The token is missing, invalid or expired, please confirm!")); log.error("The token is missing or invalid or has expired, header:{},The response information is: {}", exchange.getRequest().getHeaders(), body); DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)).doOnError(error -> DataBufferUtils.release(buffer)); }); } /** * Redefine R rights manager * <p> * explain: * ServerHttpSecurity The payload part of the authorities in jwt is not treated as Authentication * You need to add the authorities in jwt's Claim * Scheme: redefine the R permission manager, and the default converter is JwtGrantedAuthoritiesConverter * * @return */ @Bean public Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() { JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstants.AUTHORITY_PREFIX); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstants.AUTHORITY_CLAIM_NAME); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } /** * Set response information * * @param response * @return */ private ServerHttpResponse responseInfo(ServerHttpResponse response) { response.setStatusCode(HttpStatus.OK); response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); response.getHeaders().set(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); response.getHeaders().set(HttpHeaders.CACHE_CONTROL, "no-cache"); return response; } }
3. Carry error token Test
reference material:
1,https://www.cnblogs.com/summerday152/p/13635948.html
2,https://juejin.cn/post/7036297405326688287