Logout at Spring Authorization Server doesn’t terminate user session in Keycloak - spring-security

I am trying to implement an end-user initiated logout mechanism.
I have 4 main entities involved.
Client: an Angular app registered as a public OIDC client on Keycloak
Keycloak: behaves as an identity broker
Spring Authorization Server: identity provider registered on Keycloak
Resource Server: a Spring Boot application with a secure REST endpoint
When the end-user initiates a logout, a call is made by the Angular application to Keycloak’s end_session_endpoint. I have configured the logout URL for my identity provider (Spring Authorization Server) in Keycloak as http://localhost:9000/logout which is the default Spring Security logout endpoint.
FYI: I haven't enabled BackChannel or FrontChannel logout.
In the Network tab of Developer Console, the sequence of calls happen as below:
http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/protocol/openid-connect/logout?client_id=hello-world&post_logout_redirect_uri=http%3A%2F%2Flocalhost%3A4200%2F&id_token_hint=...
http://localhost:9000/logout?state=ff57bb5b-4a79-4e68-bd39-fe7a33ec8788&id_token_hint=...&post_logout_redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Fauth%2Frealms%2FSpringBootKeycloak%2Fbroker%2Foidc%2Fendpoint%2Flogout_response
While inspecting the DEBUG logs in the Spring Authorization Server, I am able to see the logout happen for that particular end-user including invalidating the JSESSIONID, however the session doesn’t terminate in Keycloak which causes the user to stay logged in and access the secure REST endpoint.
Is the Spring Authorization Server expected to return a specific response back to Keycloak to convey that the logout process is complete at it’s end and that Keycloak can end the session now?
This is my logic for logout at Spring Authorization Server.
#Bean
#Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
authorize -> authorize.mvcMatchers("/hello/**").permitAll().anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll())
.addFilterAfter(cookieFilter, ChannelProcessingFilter.class)
.logout().logoutSuccessUrl("http://localhost:4200");
return http.build();
}
I am totally clueless about what I am missing to make Keycloak end the session at it’s end.
Any leads will be greatly appreciated.
Thank you!
Update: A post_logout_redirect_uri parameter is sent in the logout request to the Spring Authorization Server. I am not able to see a redirection happen to this URI which may be the reason that Keycloak session doesn't terminate.
post_logout_redirect_uri=http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/broker/oidc/endpoint/logout_response

Got this to work finally!
The logoutSuccessUrl("http://localhost:4200") is incorrect.
As I mentioned in the Update part of my question that Keycloak sends a post_logout_redirect_uri parameter (inspected the logout endpoint in the Network calls in Chrome Developer Console), the logoutSuccessUrl or the redirectUri should be set to
http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/broker/oidc/endpoint/logout_response
Also, note that the above URL takes in state as a request parameter. Initially, it gave me a 400 Bad Request as I didn't send the state.
Since I was required to intercept the state parameter when logout is initiated at the Spring Authorization Server, I added a custom logout handler to do the job.
#Component
public class IdpLogoutHandler implements LogoutHandler {
private static final String STATE = "state";
#Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String state = request.getParameter(STATE);
try {
response.sendRedirect(
"http://127.0.0.1:8080/auth/realms/SpringBootKeycloak/broker/oidc/endpoint/logout_response?state="
+ state);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Updated WebSecurityConfig:
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(
authorize -> authorize.mvcMatchers("/hello/**").permitAll().anyRequest().authenticated())
.formLogin(form -> form.loginPage("/login").permitAll())
.addFilterAfter(cookieFilter, ChannelProcessingFilter.class).logout()
.addLogoutHandler(customLogoutHandler);

Related

Spring Authorization Server: how to continue authorization after login

I am trying to integrate Authorization Server into my app with form login. I'm using my own login page.
Samples suggest to use the following configuration:
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
//...
http
// Redirect to the login page when not authenticated from the
// authorization endpoint
.exceptionHandling((exceptions) ->
exceptions
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"));
//...
)
Now when I try to authorize (/oauth2/authorize?...) I got redirection to my login page. If I logged in before I see OAuth consent page and able to submit consent. But 99% of times I see my /login page, able to log in and stuck here. How to continue to consent page there? Should I write my own logic for that?
Solved issue myself by removing custom .successHandler(...) from my custom form login configuration. Default SavedRequestAwareAuthenticationSuccessHandler correctly handle all redirects as expected.

Spring Security SAML2 and Username Password Login in the same API

I am working on implementing a login feature to secure a REST API in Spring Boot and I am struggling to get both the Username/Password JWT authentication and SAML2 authentication to work at the same time. The configuration I give highest priority to is the one that works no matter what. The goal is to use the SAML Provider as an alternative Identity provider for the service that has an internal authentication flow, then grant a user a JWT regardless if they use SAML or the app's login.
I'm following this example for the SAML, and have built out a username/password JWT grant that is working as well. But they only work independently or when given highest priority.
When I do something with multiple configs like:
#EnableWebSecurity
class MultiConfig {
#Config
#Order(1)
static class JWTConfig extends WebSecurityConfigurerAdapter {
// working JWT configs
}
#Config
#Order(2)
static class SAMLConfig extends WebSecurityConfigurerAdapter {
// working SAML Configs
}
}
The configs work separately, but when combined, Spring Security is only working on whatever has highest priority.
I am working with the new(ish) SAML2 libraries in Spring Security Core as of 5.6.3.
The specific error I'm getting is on a sample endpoint:
#RequestMapping(value = "/valid/saml/landing", method = POST).
String testSaml(Model model, #AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
// Supposed to get user info here
}
When combined and priority is given to Username/Password JWT, the above endpoint says that "principal is null". The ultimate goal is to treat a SAML assertion as an alternative Identity Authentication and grant the user an application JWT.
It turns out, the configs were correct all along and it's a SAML Response redirect issue.
The SAML Response was redirecting to "/" when it should have been redirecting to "/my/saml/endpoint".

AccesDenied request is not forwarded to AuthenticationEntryPoint if multiple AuthenticationProvider exist

In a Spring Security project I have configured Keycloak via the regular KeycloakWebSecurityConfigurerAdapter method. Everything works fine with Keycloak itself.
I now have added one specific URL that needs to be authorized via basic-auth. For that, I have configured a second configuration, with an antMatcher to that URL that has higher priority than the Keycloak-configuration. This works fine, when I access that URL, basic auth is triggered and I can authenticate for it.
But after authenticating for this URL, I can no longer authenticate via my Keycloak-specific URL. The current logic is as follows: I have a Keycloak-login URL (/auth/login), which is handled by a controller in my project. The handling method is annotated with #Secured, requiring the user to have a role that he only gets after authenticating with Keycloak. So for the first login, the request to the /auth/login URL is denied, an AccessDeniedException is thrown.
What Spring then automatically does in ExceptionTranslationFilter: It delegates the request to the authentication entrypoint, which is a Keycloak specific implementation which does all the magic.
The problem in this specific case is, however, that in ExceptionTranslationFilter#handleSpringSecurityException Spring will not delegate to the authentication entry point, if the user is not "anonymous". Because the user has previously authenticated for a basic-auth URL, Spring finds this authentication object in the context and then does not consider the user as anonymous. I guess the logic here is that Spring considered this a failed login-attempt or something.
What can I do so that Spring also delegates the request to the configured authentication entry point if it finds some unrelated authentication in the context?

Why Resource Server has to know client_id in Spring OAuth2?

I'm implementing OAuth2 authorization using Spring Boot. I have already Authorization Server and Resource Server, now I want to access resources from Resource Server using client_credentials grant type.
I'm little confused about it, because in Resource Server I have to add client_id and client_secret. But why Resource Server really need it?
As I understand this concept client should get from Authorization Server using client credentials his access token. And then send this access token to Resource Server without any client credentials.
So why Resource Server also need some client credentials? Resource Server and client are two separeted entities, I don't understand why Resource Server has to know about client_id and client_secret.
Why access token is not enough to authenticate? check_token endpoint can return list of resources that can be accessed with this token and if client has this token, this means that he is already authenticated with client credentials to get this token.
What if I want to access from multiple different clients to this Resource Server?
Resource Server config:
#Configuration
#RestController
#EnableWebSecurity
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and()
.httpBasic().disable();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId("translate-service");
}
}
Resource server properties:
security.oauth2.resource.user-info-uri=http://localhost:8090/user
security.oauth2.resource.token-info-uri=http://localhost:8090/oauth/check_token
security.oauth2.client.client-id=XXXX
security.oauth2.client.client-secret=XXXX
If I wont set client properties Spring will log warning:
Null Client ID or Client Secret detected. Endpoint that requires authentication will reject request with 401 error.
And authentication will not work.
Maybe I doing something wrong and there is some solution to not provide client_id in Resource Server?
If you use RemoteTokenServices your Resource Server is also an additional client of the Authorization Server, see OAuth 2 Developers Guide:
An alternative is the RemoteTokenServices which is a Spring OAuth features (not part of the spec) allowing Resource Servers to decode tokens through an HTTP resource on the Authorization Server (/oauth/check_token). RemoteTokenServices are convenient if there is not a huge volume of traffic in the Resource Servers (every request has to be verified with the Authorization Server), or if you can afford to cache the results. To use the /oauth/check_token endpoint you need to expose it by changing its access rule (default is "denyAll()") in the AuthorizationServerSecurityConfigurer, e.g.
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess(
"hasAuthority('ROLE_TRUSTED_CLIENT')");
}
In this example we are configuring both the /oauth/check_token endpoint and the /oauth/token_key endpoint (so trusted resources can obtain the public key for JWT verification). These two endpoints are protected by HTTP Basic authentication using client credentials.
and OAuth2 Boot:
2.4 How to Configure the Token Info Endpoint
The token info endpoint, also sometimes called the introspection endpoint, likely requires some kind of client authentication, either Basic or Bearer. Generally speaking, the bearer token in the SecurityContext won’t suffice since that is tied to the user. Instead, you’ll need to specify credentials that represent this client, like so:
spring:
security:
oauth2:
client:
clientId: client-id
clientSecret: client-secret
resource:
tokenInfoUri: https://issuer/oauth2/check_token
By default, this will use Basic authentication, using the configured credentials, to authenticate against the token info endpoint.

Spring oauth2 password flow, how use AuthenticationSuccessHandler

I am trying to add handler on user login. In basic spring security it could be implemented by this code:
protected void configure(HttpSecurity http) throws Exception {
http.
//some configurations
.formLogin().successHandler(authenticationSuccessHandler);
}
Unfortunalty, this way do not work with oauth2 password flow because user do not authenticate using login form(there is direct request to oauth/token endpoint).
I have found this answer, and wonder is there cleaner way?
UPD: If I use Authentica‌​tionSuccessEvent - I receive this event on every request, Perhaps because I use JWT authentication.

Resources