I try implementing the following: I want a distributed environment with one or more spring cloud gateway(s), behind these are several micro services (partly) exposed to the outside. For user authentication I want to use OIDC (just moved to Keycloak).
I actually just sticked to the standard configuration from the reference documentations of spring security, webflux and boot.
In detail, I have in the gateway:
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/login").permitAll()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2Login()
.and()
.logout(logout -> logout.logoutSuccessHandler(oidcLogoutSuccessHandler()));
return http.build();
}
private ServerLogoutSuccessHandler oidcLogoutSuccessHandler() {
OidcClientInitiatedServerLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedServerLogoutSuccessHandler(clientRegistrationRepository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri(URI.create("https://<host>/login?logout"));
return oidcLogoutSuccessHandler;
}
with application.yml (only essential part):
spring:
cloud:
gateway:
default-filters:
- TokenRelay=
- RemoveRequestHeader=Cookie
discovery:
locator:
enabled: true
consul:
host: 127.0.0.1
port: 8500
loadbalancer:
ribbon:
enabled: false
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: ${KEYCLOAK_ISSUER_URI}
registration:
keycloak:
client-id: ${KEYCLOAK_CLIENT_ID}
client-secret: ${KEYCLOAK_CLIENT_SECRET}
resourceserver:
jwt:
jwk-set-uri: ${KEYCLOAK_ISSUER_URI}/protocol/openid-connect/certs
server:
forward-headers-strategy: framework
For the proof of concept I have an "environment-test-application" with a controller just returning the claims as a json. It is configured like:
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt(Customizer.withDefaults());
return http.build();
}
The application.yml only contains the consul and security part from above.
Basically this works. If I try to access the environment-test-application, I get redirected to Keycloak, I have to enter my credentials and get redirected back to the environment-test-application and see the claims. I can logout by means of "/logout".
The problem: After 5 minutes the keycloak session expires, I get a 401 and chrome says "this page is not working". This happens even, if I am reloading all the time (user activity).
To my understanding the session should:
- be extended as long as there is activity (I thought this is what the refresh token is used for, and assumed spring security to handle this automatically)
- expire when the user is inactive for some time. When the user is active again, he/she should be redirected to the login and then back to the original resource.
- when hitting "remember me", the an expired session should be reinitiated without user activity (I think, this is what the offline token from Keycloak is meant for).
I guess all this is possible by adding just some simple lines of configuration, I just can't figure which. The reference documentation wasn't entirely clear to me, but I got the feeling this is all handled like so, by default. However, it is not.
Note: I am using spring reactive stack with the default Reactor Netty. Thus, neither the spring security, nor the spring boot plugin provided by Keycloak, can be used. Plus, it is not clear to me, how they interact with the standard configuration scheme.
Note: Before I was trying all this with okta (using their starter). The problem described above magically seemed to be working with okta. However, I had different issues and thus moved to open source now.