Why isn't my Spring WebFilter being applied to my webclient API requests? - spring

I am trying to create a WebFilter for my Spring App's web client requests so that a token will be added within the headers of the request. It seems that my WebFilter isn't ever called since the println I've added in the filter is never printed out.
This is my WebFilter
#Component
public class Auth0RequestFilter implements WebFilter {
#Value("${auth0.audiences}")
private Set<String> auth0Audiences;
#Autowired
Auth0Client auth0Client;
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
String audience = serverWebExchange.getRequest().getURI().getHost();
System.out.println("We've reached this piece of code");
if(auth0Audiences.contains(audience)) {
String accessToken = auth0Client.getAccessToken(audience);
serverWebExchange.getRequest().getHeaders().add("authorization", "Bearer ".concat(accessToken));
}
return webFilterChain.filter(serverWebExchange);
}
}
And this is my API request:
#Component
public class TestAPICall {
final Auth0RequestFilter auth0RequestFilter;
public TestAPICall(Auth0RequestFilter auth0RequestFilter) {
this.auth0RequestFilter = auth0RequestFilter;
}
WebClient client = WebClient.create();
#Scheduled(fixedRate = 10000)
public void scheduleFixedRateTask() {
client.get()
.uri("https://google.com")
.retrieve()
.bodyToMono(String.class)
.block();
}
}

WebFilter is a contract for interception-style, chained processing of server Web requests, not client.
To intercept client requests you need to use ExchangeFilterFunction
ExchangeFilterFunction filterFunction = (clientRequest, nextFilter) -> {
…
return nextFilter.exchange(clientRequest);
};
and then add it to the WebClient instance
WebClient webClient = WebClient.builder()
.filter(filterFunction)
.build();

Related

How can I forward request using HandlerFilterFunction?

A server environment requires an endpoint for /some/health.
I already configured actuator.
Rather changing the actuator's function, I'm thinking forwarding /some/health to the /actuator/health.
And I'm trying to do with HandlerFilterFunction.
#Configuration
public class SomeHealthFilterFunction
implements HandlerFilterFunction<ServerResponse, ServerResponse> {
private static final String PATTERN = "/some/health";
#Override
public Mono<ServerResponse> filter(ServerRequest request,
HandlerFunction<ServerResponse> next) {
if (PATTERN.equals(request.requestPath().pathWithinApplication().value())) {
RequestPath requestPath
= request.requestPath().modifyContextPath("/actuator/health");
// How can I call next.handle, here?
}
}
}
How can I change the origin request and do next.handle(...)?
Here's an example WebFilter that will reroute all calls from /some/health to /actuator/health
#Component
public class RerouteWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
ServerHttpRequest request = serverWebExchange.getRequest();
if ("/some/health".equals(request.getPath().pathWithinApplication().value())) {
ServerHttpRequest mutatedServerRequest = request.mutate().path("/actuator/health").build();
serverWebExchange = serverWebExchange.mutate().request(mutatedServerRequest).build();
}
return webFilterChain.filter(serverWebExchange);
}
}

OAuth2FeignRequestInterceptor class deprecated in Spring Boot 2.3

In our last feign client security configuration we have this Bean:
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(
ClientCredentialsResourceDetails oauth2RemoteResource) {
return new OAuth2FeignRequestInterceptor(
new DefaultOAuth2ClientContext(),
oauth2RemoteResource
);
}
In 2.3 spring version OAuth2FeignRequestInterceptor is deprecated! But we cannot found the new one.
Anyone knows something about that?
You can create your own RequestInterceptor to add the Authorization header.
There's an example here:
https://developer.okta.com/blog/2018/02/13/secure-spring-microservices-with-oauth
I had the same problem, I needed a request interceptor to call through a Feign client to a another microservice.
The idea is very easy, The only thing that I needed to implement was a custom RequestInterceptor annonted with #Component that inject the current JWT from the security context to the Authorization Header.
You can view this component as follows:
#Component
#Slf4j
public class FeignClientInterceptor implements RequestInterceptor {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String TOKEN_TYPE = "Bearer";
#Override
public void apply(RequestTemplate requestTemplate) {
log.debug("FeignClientInterceptor -> apply CALLED");
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication instanceof JwtAuthenticationToken) {
final JwtAuthenticationToken jwtAuthToken = (JwtAuthenticationToken) authentication;
requestTemplate.header(AUTHORIZATION_HEADER, String.format("%s %s", TOKEN_TYPE, jwtAuthToken.getToken().getTokenValue()));
}
}
}
Next, I can use the feign client successfully
final APIResponse<ProcessedFileDTO> response = filesMetadataClient.getProcessedFileByName(uploadFile.getOriginalFilename());
if (response.getStatus() == ResponseStatusEnum.ERROR
&& response.getHttpStatusCode() == HttpStatus.NOT_FOUND) {
sendFileToSftp(uploadFile);
} else {
throw new FileAlreadyProcessedException();
}

Spring Cloud gateway send response in filter

I am using spring cloud gateway as edge server.
This is the flow
If request has a header named 'x-foo' then find the header value, get a string from another server and send that string as response instead of actually proxying the request.
Here is code for Filter DSL
#Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("foo-filter", r -> r.header('x-foo').and().header("x-intercepted").negate()
.filters(f -> f.filter(fooFilter))
.uri("http://localhost:8081")) // 8081 is self port, there are other proxy related configurations too
.build();
}
Code for Foo filter
#Component
#Slf4j
public class FooFilter implements GatewayFilter {
#Autowired
private ReactiveRedisOperations<String, String> redisOps;
#Value("${header-name}")
private String headerName;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
var foo = request.getHeaders().getFirst(headerName);
return redisOps.opsForHash()
.get("foo:" + foo, "response")
.doOnSuccess(s -> {
log.info("data on success");
log.info(s.toString()); // I am getting proper response here
if (s != null) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set("x-intercepted", "true");
byte[] bytes = s.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
response.writeWith(Mono.just(buffer));
response.setComplete();
}
})
.then(chain.filter(exchange));
}
}
The problem is, the response has the response is getting proper 200 code, the injected header is present on response but the data is not available in response.
This is how I got working.
Use flatMap instead of doOnSuccess
don't use then or switchIfEmpty instead use onErrorResume
Return the response.writeWith
#Component
#Slf4j
public class FooFilter implements GatewayFilter {
#Autowired
private ReactiveRedisOperations<String, String> redisOps;
#Value("${header-name}")
private String headerName;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
var foo = request.getHeaders().getFirst(headerName);
return redisOps.opsForHash()
.get("foo:" + foo, "response")
.flatMap(s -> {
log.info("data on success");
log.info(s.toString()); // I am getting proper response here
if (s != null) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set("x-intercepted", "true");
byte[] bytes = s.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
}else{ return chain.filter(exchange).then(Mono.fromRunnable(() -> {log.info("It was empty")} }
})
.onErrorResume(chain.filter(exchange));
}
}

Spring Webflux OAuth 2 resource server

I have a Spring OAuth 2 server based on Spring Boot 1.5 (Spring Security v4) which generates customized tokens and a few resource servers who communicate with this authorization server, making use of /oauth/check_token endpoint by configuration of RemoteTokenServices.
All the logic related to storing/retrieving tokens on Authorization server side is done with JdbcTokenStore.
I am building a new Spring Boot 2 application which is build with Spring webflux module and trying to implement client_credentials flow with existing Authorization Server using Spring Security 5.1.1.
I found that support for resource servers was added in 5.1.0.RC1 (https://spring.io/blog/2018/08/21/spring-security-5-1-0-rc1-released#oauth2-resource-servers) and updated in 5.1.0.RC2 (https://spring.io/blog/2018/09/10/spring-security-5-1-0-rc2-released#oauth2-resource-server) but looks like it's only possible to configure it with JWT support.
I might be messing up with concepts here but looking for more info and a way to configure all these components together.
I'm in same situation as you.I solve that problem in next way, maybe it can help you:
spring-boot-starter-parent.version: 2.1.1
spring-cloud-dependencies.version: Greenwich.R1
Security configuration:
#EnableWebFluxSecurity
public class SecurityConfig {
#Autowired
private ReactiveAuthenticationManager manager; //custom implementation
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/role").hasRole("ADMIN")
.pathMatchers("/test").access(new HasScope("server")) //custom implementation
.anyExchange().authenticated()
.and()
.httpBasic().disable()
.oauth2ResourceServer()
.jwt()
.authenticationManager(manager)
.and().and()
.build();
}
}
ReactiveAuthorizationManager (HasScope) implementation:
Helper which allow search for scopes in authentication object
public class HasScope implements ReactiveAuthorizationManager<AuthorizationContext> {
public HasScope(String...scopes) {
this.scopes = Arrays.asList(scopes);
}
private final Collection<String> scopes;
#Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext object) {
return authentication
.flatMap(it -> {
OAuth2Authentication auth = (OAuth2Authentication) it;
Set<String> requestScopes = auth.getOAuth2Request().getScope();
boolean allow = requestScopes.containsAll(scopes);
return Mono.just(new AuthorizationDecision(allow));
});
}
}
ReactiveAuthenticationManager implementation:
That is the main component in configuration which create OAuth2Authentication. There is a problem with response for wrong access_token, it returns only status code without body response.
#Component
public class ReactiveAuthenticationManagerImpl implements ReactiveAuthenticationManager {
private final ResourceServerProperties sso;
private final WebClient.Builder webClient;
private final ObjectMapper objectMapper;
private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
public ReactiveAuthenticationManagerImpl(ResourceServerProperties sso,
#Qualifier("loadBalancedWebClient") WebClient.Builder webClient, ObjectMapper objectMapper) {
this.sso = sso;
this.webClient = webClient;
this.objectMapper = objectMapper;
}
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.just(authentication)
.cast(BearerTokenAuthenticationToken.class)
.flatMap(it -> getMap(it.getToken()))
.flatMap(result -> Mono.just(extractAuthentication(result)));
}
private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
Object principal = getPrincipal(map);
OAuth2Request request = getRequest(map);
List<GrantedAuthority> authorities = authoritiesExtractor.extractAuthorities(map);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
token.setDetails(map);
return new OAuth2Authentication(request, token);
}
private Object getPrincipal(Map<String, Object> map) {
if (map.containsKey("principal")) {
try {
//that is the case for user authentication
return objectMapper.convertValue(map.get("principal"), UserPrincipal.class);
} catch (IllegalArgumentException ex) {
//that is the case for client authentication
return objectMapper.convertValue(map.get("principal"), String.class);
}
}
return null;
}
#SuppressWarnings({"unchecked"})
private OAuth2Request getRequest(Map<String, Object> map) {
Map<String, Object> request = (Map<String, Object>) map.get("oauth2Request");
String clientId = (String) request.get("clientId");
Set<String> scope = new LinkedHashSet<>(request.containsKey("scope") ?
(Collection<String>) request.get("scope") : Collections.emptySet());
return new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),
null, null, null, null);
}
private Mono<Map<String, Object>> getMap(String accessToken) {
String uri = sso.getUserInfoUri();
return webClient.build().get()
.uri(uri)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + accessToken)
.exchange()
.flatMap(it -> it.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}))
.onErrorMap(InvalidTokenException.class, mapper -> new InvalidTokenException("Invalid token: " + accessToken));
}

How to intercept requests by handler method in Spring WebFlux

I've got following interceptor in Spring MVC that checks if user can access handler method:
class AccessInterceptor : HandlerInterceptorAdapter() {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any?): Boolean {
val auth: Auth =
(if (method.getAnnotation(Auth::class.java) != null) {
method.getAnnotation(Auth::class.java)
} else {
method.declaringClass.getAnnotation(Auth::class.java)
}) ?: return true
if (auth.value == AuthType.ALLOW) {
return true
}
val user = getUserFromRequest(request) // checks request for auth token
// and checking auth for out user in future.
return renderError(403, response)
In my Controller I do annotate methods, like this:
#GetMapping("/foo")
#Auth(AuthType.ALLOW)
fun doesntNeedAuth(...) { ... }
#GetMapping("/bar")
#Auth(AuthType.ADMIN)
fun adminMethod(...) { ... }
In case if user has wrong token or no permissions, error is being returned.
Is it possible to do this in Spring WebFlux with annotation-style controllers?
My implementation, w/o using toFuture().get() which is potentially blocking.
#Component
#ConditionalOnWebApplication(type = Type.REACTIVE)
public class QueryParameterValidationFilter implements WebFilter {
#Autowired
private RequestMappingHandlerMapping handlerMapping;
#NonNull
#Override
public Mono<Void> filter(#NonNull ServerWebExchange exchange, #NonNull WebFilterChain chain) {
return handlerMapping.getHandler(exchange)
.doOnNext(handler -> validateParameters(handler, exchange))
.then(chain.filter(exchange));
}
private void validateParameters(Object handler, ServerWebExchange exchange) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Set<String> expectedQueryParams = Arrays.stream(handlerMethod.getMethodParameters())
.map(param -> param.getParameterAnnotation(RequestParam.class))
.filter(Objects::nonNull)
.map(RequestParam::name)
.collect(Collectors.toSet());
Set<String> actualQueryParams = exchange.getRequest().getQueryParams().keySet();
actualQueryParams.forEach(actual -> {
if (!expectedQueryParams.contains(actual)) {
throw new InvalidParameterException(ERR_MSG, actual);
}
});
}
}
}
To solve that problem I would most probably use:
A Spring Reactive Web WebFilter from the WebHandler API to intercept the incoming request
The RequestMappingHandlerMapping to retrieve the method which handles the current request
#Autowired
RequestMappingHandlerMapping requestMappingHandlerMapping;
...
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
...
HandlerMethod handler = (HandlerMethod) requestMappingHandlerMapping.getHandler(exchange).toProcessor().peek();
//your logic
}
#Component
class AuditWebFilter(
private val requestMapping: RequestMappingHandlerMapping
): WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
// if not to call - then exchange.attributes will be empty
// so little early initializate exchange.attributes by calling next line
requestMapping.getHandler(exchange)
val handlerFunction = exchange.attributes.get(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE) as HandlerMethod
val annotationMethod = handlerFunction.method.getAnnotation(MyAnnotation::class.java)
// annotationMethod proccesing here
}
}
In newer versions of Spring the .toProcessor() call is deprecated. What worked for me is to use .toFuture().get() instead:
if(requestMappingHandlerMapping.getHandler(exchange).toFuture().get() instanceof HandlerMethod handlerMethod) { ... }
Unfortunately this requires handling of checked exceptions so the code will be a bit less readable but at least not deprecated anymore.

Resources