Spring Cloud Gateway "invalid csrf token" - spring-boot

Spring Cloud Gateway keeps rejecting my csrf token even though request header "X-XSRF-TOKEN" and "XSRF-TOKEN" cookie are correctly set as you can see here:
This is the Spring Cloud Gateway Security configuration:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Autowired
private ReactiveClientRegistrationRepository clientRegistrationRepository;
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
CorsConfiguration cors_config = new CorsConfiguration(); //Setting cors config
cors_config.setAllowCredentials(true);
cors_config.applyPermitDefaultValues();
cors_config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "null"));
cors_config.setAllowedMethods(List.of("GET", "POST", "OPTIONS", "DELETE"));
cors_config.setAllowedHeaders(List.of("*"));
http.cors().configurationSource(source -> cors_config)
.and()
.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
.oauth2Login()//Setting Oauth2Login
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("http://localhost:3000/")).and()
.logout(logout -> logout //Setting Oauth2Logout
.logoutHandler(logoutHandler())
.logoutSuccessHandler(oidcLogoutSuccessHandler()))
.csrf(csrf -> csrf.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())); //Enabling csrf (all post/patch/put/... requests will need a csrf token in X-XSRF-TOKEN header
return http.build();
}
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(this.clientRegistrationRepository);
// Sets the location that the End-User's User Agent will be redirected to
// after the logout has been performed at the Provider
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("http://localhost:8090/oauth2/authorization/spring-gateway-client");
return oidcLogoutSuccessHandler;
}
private DelegatingServerLogoutHandler logoutHandler() {
//Invalidate session on logout
return new DelegatingServerLogoutHandler(
new SecurityContextServerLogoutHandler(), new WebSessionServerLogoutHandler());
}
}
Filter:
#Component
public class C {
#Bean
public WebFilter addCsrfTokenFilter() {
return (exchange, next) -> Mono.just(exchange)
.flatMap(ex -> ex.<Mono<CsrfToken>>getAttribute(CsrfToken.class.getName()))
.doOnNext(ex -> {
})
.then(next.filter(exchange));
}
}
I don't really know how to solve this.

Related

Spring Security with OAuth2(Keycloak) disable default login page

I have successfully configured Spring Boot Spring Security with Keycloak. Everything works fine. In order to login, I use the following URL: http://localhost:8081/realms/MY_REALM_NAME
But when I try to access the following page: http://localhost:8080/login I see the following page:
I'd like to disable/remove this page. How to properly configure it with Spring Security?
UPDATED
My SpringSecurity configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {
private final ClientRegistrationRepository clientRegistrationRepository;
private final GrantedAuthoritiesMapper authoritiesMapper;
private final ProfileService profileService;
SecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository,
GrantedAuthoritiesMapper authoritiesMapper, ProfileService profileService) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.authoritiesMapper = authoritiesMapper;
this.profileService = profileService;
SecurityContextHolder.setStrategyName(VaadinAwareSecurityContextHolderStrategy.class.getName());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
// Enable OAuth2 login
.oauth2Login(oauth2Login ->
oauth2Login
.clientRegistrationRepository(clientRegistrationRepository)
.userInfoEndpoint(userInfoEndpoint ->
userInfoEndpoint
// Use a custom authorities mapper to get the roles from the identity provider into the Authentication token
.userAuthoritiesMapper(authoritiesMapper)
)
// Use a Vaadin aware authentication success handler
.successHandler(new KeycloakVaadinAuthenticationSuccessHandler(profileService))
)
// Configure logout
.logout(logout ->
logout
// Enable OIDC logout (requires that we use the 'openid' scope when authenticating)
.logoutSuccessHandler(logoutSuccessHandler())
// When CSRF is enabled, the logout URL normally requires a POST request with the CSRF
// token attached. This makes it difficult to perform a logout from within a Vaadin
// application (since Vaadin uses its own CSRF tokens). By changing the logout endpoint
// to accept GET requests, we can redirect to the logout URL from within Vaadin.
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
);
}
#Bean
#Primary
public SpringViewAccessChecker springViewAccessChecker(AccessAnnotationChecker accessAnnotationChecker) {
return new KeycloakSpringViewAccessChecker(accessAnnotationChecker, "/oauth2/authorization/keycloak");
}
private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler() {
var logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");
return logoutSuccessHandler;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
// Don't apply security rules on our static pages
web.ignoring().antMatchers("/session-expired");
}
#Bean
public PolicyFactory htmlSanitizer() {
// This is the policy we will be using to sanitize HTML input
return Sanitizers.FORMATTING.and(Sanitizers.BLOCKS).and(Sanitizers.STYLES).and(Sanitizers.LINKS);
}
}
Have tried formLogin().disable() method?
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
//your config here
.and().formLogin().disable();
}

Spring Security Customizing Authorization Endpoint URL

I am implementing a Spring MVC REST web service and attempting to security it with Spring Security Oauth2.
My authorization URL is at the following address (note that there is no registration id):
http://localhost:8080/myoauthserver/oauthservlet
So, in my Spring Security Config, I have this:
#Bean
#Profile("dev")
public ClientRegistration devClientRegistration() {
// set up variables to pass to ClientRegistration
ClientRegistration result = ClientRegistration
.withRegistrationId("") // this obviously doesn't work
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri(authorizationUri)
.tokenUri(clientSecret)
.userInfoUri(userInfoUri)
.userNameAttributeName(userNameAttribute)
.redirectUri(redirectUrl)
.providerConfigurationMetadata(providerDetails)
.build();
return result;
}
#Bean
public ClientRegistrationRepository clientRegistrationRepository() {
ClientRegistrationRepository repository = new InMemoryClientRegistrationRepository(devClientRegistration());
return repository;
}
#Bean
#Profile("dev")
public SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
// this prevents throwing an exception in the oauth2 lambda function
Map<String, String> loginProps = ...
http
.authorizeRequests()
// what we want is for a few urls to be accessible to all, but most to require
// oauth authentication/authorization
.antMatchers("/login/**","/error/**", "/oauth2/**").anonymous()
.anyRequest().authenticated()
.and()
.oauth2Login(oauth2 -> {
oauth2
.clientRegistrationRepository(clientRegistrationRepository(loginPropsCopy))
.authorizationEndpoint()
.baseUri("http://localhost:8080/myoauthserver/oauthservlet");
}
);
return http.build();
}
How do I customize the entire authorization endpoint to not try to tack the registration id onto it?

How to cache a JWT in Spring-Security from an OAuth2Client

In my case I have an application for SpringBootAdmin. SpringBootAdmin sends requests to a lot of applications all the time.
For these requests I set an access token (JWT) which I pull from Keycloak via the AuthorizedClientServiceOAuth2AuthorizedClientManager.
Now the problem is that this token is not cached, and Spring-Security sends about 100 requests per minute to Keycloak to get the access token.
So is there a way to cache this JWT ?
Here is my SecurityConfig:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
public void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.oauth2Client()
.and()
.csrf()
.ignoringAntMatchers("/**");
}
}
My ClientRegistration:
#Configuration
public class ClientRegistrationConfiguration
{
private static final String KEYCLOAK = "keycloak";
#Bean
public ClientRegistration clientRegistration(OAuth2ClientProperties properties)
{
return withRegistrationId(KEYCLOAK)
.tokenUri(properties.getProvider().get(KEYCLOAK).getTokenUri())
.clientId(properties.getRegistration().get(KEYCLOAK).getClientId())
.clientSecret(properties.getRegistration().get(KEYCLOAK).getClientSecret())
.authorizationGrantType(CLIENT_CREDENTIALS)
.build();
}
#Bean
public ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration)
{
return new InMemoryClientRegistrationRepository(clientRegistration);
}
#Bean
public OAuth2AuthorizedClientService oAuth2AuthorizedClientService(ClientRegistrationRepository clientRegistrationRepository)
{
return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
}
#Bean
public AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientServiceOAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService authorizedClientService)
{
var authorizedClientProvider = builder().clientCredentials().build();
var authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
}
And my RequestConfiguration:
#Configuration
public class HttpRequestConfiguration
{
#Bean
public HttpHeadersProvider customHttpHeadersProvider(AuthorizedClientServiceOAuth2AuthorizedClientManager clientManager)
{
return instance ->
{
var httpHeaders = new HttpHeaders();
var token = Objects.requireNonNull(clientManager.authorize(withClientRegistrationId("keycloak").principal("Keycloak").build())).getAccessToken();
httpHeaders.add("Authorization", "Bearer " + token.getTokenValue());
return httpHeaders;
};
}
}
Your headers provider is a bean, so you can simply cache the token there. If you write your provider like that:
#Bean
public HttpHeadersProvider customHttpHeadersProvider(AuthorizedClientServiceOAuth2AuthorizedClientManager clientManager)
{
var token = Objects.requireNonNull(clientManager.authorize(withClientRegistrationId("keycloak").principal("Keycloak").build())).getAccessToken();
return instance ->
{
var httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization", "Bearer " + token.getTokenValue());
return httpHeaders;
};
}
Then the token will be requested once, only when the bean is created. This will fix calling Keycloak on each request but has the usual problem of caching - at some point the access token will expire and you need a way to get a new token. One way to do it would be to catch 401 errors from your client and force recreation of the customHttpHeadersProvider bean when that happens.
Another way would be to create an object which will be a "token provider" and a bean itself. That object would keep the token in memory and have a method to refresh the token. Then you could create the authorization header near the request itself, instead of using a headers provider bean. You will have more control then over when do you want to refresh the access token.

customize state parameter with Oauth2client using spring security

I want to customize state parameter with Oauth2client using spring security OIDC. I am using spring reactive. The issue is I am not able to add customize state in client registration. Below is my security configuration.
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/","/jwt").permitAll()
.anyExchange().authenticated().and()
.oauth2Client()
.build();
}
#Bean
public ReactiveClientRegistrationRepository reactiveClientRegistrationRepository(RegisteredClients clients) {
List<ClientRegistration> clientRegistrations = clients.getClients()
.entrySet().stream()
.map(clientRegistration -> {
String registrationId = clientRegistration.getKey();
RegisteredClients.OAuthClient client = clientRegistration.getValue();
return ClientRegistration.withRegistrationId(registrationId)
.clientId(client.getClientId())
.clientSecret(client.getClientSecret())
.redirectUriTemplate(client.getRedirectUri())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.tokenUri(client.getTokenUri())
.authorizationUri(client.getAuthorizationUri())
.scope("openid")
.build();
})
.collect(toList());
return new InMemoryReactiveClientRegistrationRepository(clientRegistrations);
}
To customize the authorization request, you should wire an ServerAuthorizationRequestResolver:
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
// ...
.oauth2Client((oauth2) -> oauth2
.authorizationEndpoint((authorization) -> authorization
.authorizationRequestResolver(authorizationRequestResolver)
)
)
.build();
}
#Bean
OAuth2ServerAuthorizationRequestResolver authorizationRequestResolver
(ReactiveClientRegistrationRepository registrations) {
DefaultServerOAuth2AuthorizationRequestResolver resolver =
new DefaultServerOAuth2AuthorizationRequestResolver(registrations);
resolver.setAuthorizationRequestCustomizer((request) -> request.state(...));
return resolver;
}
Spring Security has some related servlet documentation that may be useful as well.

Credentials propagation from Spring Cloud Gateway to underlying service

I use Spring Cloud Gateway as UI gateway. Security config:
#Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
return http.httpBasic().and()
.formLogin().loginPage("/login")
.and()
.authorizeExchange().anyExchange().permitAll()
.and()
.build();
}
How I can propagate current user credentials (username and roles) to underlying services? Do I need add some custom filters to routes config:
#Bean
RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("some-ui", r -> r.path("/some-ui-context-path/**")
.uri("lb://some-ui"))
.build();
}
? Is there a standard component for this purpose?
I created filter for adding username and user roles to headers of downstream services request (code on Kotlin):
#Component
class AddCredentialsGlobalFilter : GlobalFilter {
private val usernameHeader = "logged-in-user"
private val rolesHeader = "logged-in-user-roles"
override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain) = exchange.getPrincipal<Principal>()
.flatMap { p ->
val request = exchange.request.mutate()
.header(usernameHeader, p.name)
.header(rolesHeader, (p as Authentication).authorities?.joinToString(";") ?: "")
.build()
chain.filter(exchange.mutate().request(request).build())
}
}

Resources