Spring oauth2 password flow, how use AuthenticationSuccessHandler - spring-security

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.

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.

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

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);

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".

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.

Testing spring security with Postman

I have configured a form-base authentication using Spring security. It works fine when I log in using the login form in my web application.
It also works with cURL:
curl --data "j_username=myname&j_password=mypassword" http://localhost:8080/test/j_spring_security_check --verbose
However, I cannot make it works using Postman:
What is missing? I need to be authenticated in order to test other services.
Finally I managed making Postman aware of authentication by using the Postman Interceptor chrome extension
With the extension enabled (by clicking the satellite icon within the Postman App), you just need to log in using the login form of your web and then you can start using services that require authentication in Postman.
You can achieve authentication/authorization in postman through various authorization types given in postman dropdown under Authorization tab.
Below is the step to use Basic Auth which by default spring security provides.
In spring security you can customize your credentials in application.properties file as given below.
spring.security.user.name=yer
spring.security.user.password=galem
Using http.httpBasic worked for me.
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/api/home")
.hasRole("ADMIN")
.antMatchers("/api/product/*")
.hasRole("ADMIN")
.and()
.formLogin();
When using basic auth on postman, you will set the credentials on the authorization tab.
Click on Authorization, choose the type as Basic Auth, the credentials section will be displayed for you to key in the username and password.
For me the Postman Interceptor was not working, So I did the following and now I can login to the server.
Select POST request from dropdown and type login URL in request URL section.
Select Body from tabs
Enter username and password keys and values as shown in picture.
And click send this will create a temp cookie in postman and be there for a session.
You can also do GET request for logout URL to logout from session. Or delete the cookie from Cookies below Send button.
http.httpBasic is working for Testing spring security with Postman.
For example, Added the override configure method from the configuration class MyConfiguration.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").permitAll().anyRequest().authenticated()
.and().formLogin().permitAll().and().logout().permitAll().and().httpBasic();
http.cors().disable().csrf().disable();
}
Note: To access the POST, PUT, and DELETE request you need to disable the cors and csrf as per code above.

Resources