I'm looking forward to Spring Security integration in Spring Cloud Gateway. A common pattern of an API gateway is that the gateway takes responsibilities of user authentication and authorization. In current 2.0.0.M5 version, I see the warning
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.
when I attempted to achieve that by integrate Spring Security into my gateway.
So, I'm wondering whether we are able to do user authentication and authorization in a Spring Cloud gateway or not.
In a spike we used the following Security configuration in conjunction with the gateway:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig{
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http
.authorizeExchange()
.anyExchange().authenticated()
.and().authenticationManager(reactiveAuthenticationManager())
.httpBasic().and()
.build();
}
@Bean
ReactiveAuthenticationManager reactiveAuthenticationManager(){
return new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsRepository());
}
@Bean
public MapReactiveUserDetailsService userDetailsRepository() {
User.UserBuilder userBuilder = User.withDefaultPasswordEncoder();
UserDetails hubert = userBuilder.username("hubert").password("hubert").roles("USER").build();
UserDetails admin = userBuilder.username("admin").password("admin").roles("USER", "ADMIN").build();
return new MapReactiveUserDetailsService(hubert, admin);
}
}
We did define routes using a RouteLocator:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ConcatPrincipalAtPathGatewayFilterFactory addPrincipal) {
//@formatter:off
return builder.routes()
.route(r -> r.path("/image/png")
.addResponseHeader("X-TestHeader", "foobar")
.uri("http://httpbin.org:80")
)
.route(r -> r.path("/image/webp")
.addResponseHeader("X-AnotherHeader", "baz")
.addRequestHeader("Y-AnotherHeader", "foo")
.uri("http://httpbin.org:80")
)
.route(r -> r.order(-1)
.path("/anything")
.filter(addPrincipal.apply())
.uri("http://httpbin.org:80")
)
.build();
////@formatter:on
}
Actually the routes were secured as expected, access was given only when basic auth was provided.
Unfortunately in the GatewayFilters, the provided ServerWebExchange had an empty Principal, although the actual class of the ServerWebExchange was SecurityContextServerWebExchange.class.
We like to strongly support this request for an spring security integration, and hope that authorization via oauth2 is supported soon.
@hubert-wagener Thank you for sharing your code. I'm glad to see that Basic auth works with the gateway, although basic auth is a decent authentication method in REST API. Personally, I hope to see username/password login in Json format.
I've successfully used Spring Security with the gateway. After that it's just passing headers
A good news! I can't believe that. So, is the following message supposed to be removed?
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.
No, spring MVC is not needed for Spring Security
See https://github.com/spring-cloud-samples/spring-cloud-gateway-sample needs to be updated to the latest, but it works
@spencergibb could you please give an example on how to access Authentication from a custom GatewayFilter?
This is how my project looks like:
@SpringBootApplication
@EnableWebFluxSecurity
public class ApiGatewayExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayExampleApplication.class, args);
}
@Bean
MapReactiveUserDetailsService authentication() {
return new MapReactiveUserDetailsService(
User.withDefaultPasswordEncoder().username("user").password("user123").roles("USER").build()
);
}
@Bean
SecurityWebFilterChain authorization(ServerHttpSecurity security) {
return security.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic()
.and()
.build();
}
@Component
public class CustomGatewayFilterFactory implements GatewayFilterFactory {
@Override
public GatewayFilter apply(Tuple args) {
return (exchange, chain) -> {
exchange.getPrincipal().log().subscribe();
return chain.filter(exchange);
};
}
}
spring:
cloud:
gateway:
routes:
- id: httpbin
uri: http://httpbin.org:80
predicates:
- Path=/bin/**
filters:
- RewritePath=/bin/(?<segment>.*), /$\{segment}
- Custom
On curl -i -u user:user123 http://localhost:8080/bin/get the log says:
2018-02-02 10:39:25.782 INFO 9064 --- [ parallel-2] reactor.Mono.MapFuseable.4 : | onSubscribe([Fuseable] FluxMapFuseable.MapFuseableSubscriber)
2018-02-02 10:39:25.782 INFO 9064 --- [ parallel-2] reactor.Mono.MapFuseable.4 : | request(unbounded)
2018-02-02 10:39:25.782 INFO 9064 --- [ parallel-2] reactor.Mono.MapFuseable.4 : | onComplete()
As you can see, there is no onNext. exchange is (wrapped by delegates) of type SecurityContextServerWebExchange.
@stphngrtz I don't understand the problem.
@spencergibb I think the principal of exchange is empty, but it should not. Please have a look at this example. I would expect to see the name of the user to get printed to the console. Am I doing something wrong or is it a bug?
git clone https://github.com/stphngrtz/spring-cloud-gateway-issue-144.git
mvn clean package -f spring-cloud-gateway-issue-144
java -jar spring-cloud-gateway-issue-144/target/issue144-1.0-SNAPSHOT.jar
curl -u user:pw http://localhost:8080/get
an empty principal doesn't have anything to do with the gateway. It's all spring security.
From my point this issue could be closed, because Spring Security works well with Spring Cloud Gateway. My example has been updated to demonstrate how to access the principal from a custom filter.
How do you integrate Spring-Security-Oauth2 with Spring Cloud Gateway? The integration breaks Spring Cloud Gateway because Spring-security-oauth2 requires spring mvc.
Closing in favor of https://github.com/spring-cloud/spring-cloud-security/issues/141
Most helpful comment
See https://github.com/spring-cloud-samples/spring-cloud-gateway-sample needs to be updated to the latest, but it works