Getting 403 with CSRF token in spring security - spring

I am encountering an issue with multiple tabs. If i logout from first tab and open another tab and after logging in and logging out if i go back to first tab and login i get 403. For example, the logout page of first tab had following added to the form by spring security and thymeleaf:
<input type="hidden" name="_csrf" value="7b9639ba-aaae-4ed2-aad2-bb4c5930458e">
where as the login form of second tab added a different csrf token.
<input type="hidden" name="_csrf" value="659324d5-ec5c-4c57-9984-dab740746285">
Now when i go to first tab and login from there i get 403 forbidden. Which makes sense since csrf token is now stale. But how do i get around this? I am also getting 403 forbidden if the user was logged out from inactivity and redirected to login page but tried logging in again only after a while, say half an hour.

As of Spring Security 3.2, we have the CsrfTokenRepository interface, which allows you to store the synchronizer token however you see fit, such as in a database. This gives you the option to expire those tokens however you want in order to avoid stale tokens in your use case.
If you want to provide a nicer error message when something does go awry, you can supply a custom AccessDeniedHandler implementation that manages the MissingCsrfTokenException and InvalidCsrfTokenException exceptions in order to produce a more informative message.
UPDATE:
I have an interceptor that handles all my uncaught exceptions, so I just built a little AccessDeniedHandler that rethrows the CSRF-related exceptions:
public class CustomAccessDeniedHandler extends AccessDeniedHandlerImpl {
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if(accessDeniedException instanceof MissingCsrfTokenException
|| accessDeniedException instanceof InvalidCsrfTokenException) {
throw new ServletException(accessDeniedException);
}
super.handle(request, response, accessDeniedException);
}
}

The simplest approach to handling access denied errors I've used has been setting the access denied handler within your security config to redirect to your login page.
<http ...>
<access-denied-handler error-page="/login.html" />
...

Related

Is there any implementation of OIDC Session Management and Logout mechanism in Spring Authorization Server for implementing Single Sing On?

I am trying to implement Single Sing On using Spring Authorization Server which is based on oAuth2 and OIDC for Authorization and Authentication respectively, but as per the Spring Authorization Server feature list OIDC support is not fully integrated. Mainly the session management and logout part, I couldn't find. Also if there's any workaround for implementing sso and logout?
Feature List URL: https://docs.spring.io/spring-authorization-server/docs/current/reference/html/overview.html
These features are still on the backlog but are not scheduled yet (as of the time of this writing). See #58 and #266 respectively to follow progress on these features.
Given that there are a number of pieces to the specifications for both of these features, I imagine it would be a bit of a hassle to attempt a fully spec-compliant implementation of them as extensions to SAS (though I'm sure it's possible). Instead, you might pick a minimal subset of the logout feature as a way to get started.
Using Spring Security configuration, you can configure a logout endpoint in a custom way. Here's some pseudocode to get you started:
#Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.logout((logout) -> logout
.logoutRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/logout", "GET"),
new AntPathRequestMatcher("/logout", "POST")
))
.addLogoutHandler((request, response, authentication) -> {
// Logic to validate an id_token_hint, client_id, etc.
// Throw exception in case of invalid request
})
.logoutSuccessHandler((request, response, authentication) -> {
// Get state and calculate redirect for logout success back to client
// http://127.0.0.1:8080/logout?success&state=...
// new SimpleUrlLogoutSuccessHandler()...
})
);
return http.build();
}
This assumes validation of some kind is implemented to prevent CSRF, denial of service, etc. You might also choose to add a logout confirmation page as a separate endpoint of the auth server that redirects to the logout endpoint when the user clicks a "Confirm" button, etc.
To use this, you simply redirect from your client application to the logout confirmation page, which redirects to the logout endpoint on confirm, which then redirects back to the client (which can then log out of the client automatically).

Spring Security loginPage Vs loginProcessingURL

what is the difference between loginPage vs loginProcessingURL.
.formLogin().loginPage("/login").usernameParameter("phone-number").passwordParameter("password")
Seems to be loginProcessingURL is like post method once user submits the data in the login page but when I remove also it is working fine. What is the significance of loginProcessingURL and how does it differ from loginPage?
The line loginPage("/login") instructs Spring Security
when authentication is required, redirect the browser to /login
we are in charge of rendering the login page when /login is requested
when authentication attempt fails, redirect the browser to
/login?error (since we have not specified otherwise)
we are in charge of rendering a failure page when /login?error is
requested
when we successfully logout, redirect the browser to /login?logout
(since we have not specified otherwise)
we are in charge of rendering a logout confirmation page when
/login?logout is requested
AND
.loginProcessingUrl("/login/process")
tells Spring Security to process the submitted credentials when sent the specified path and, by default, redirect user back to the page user came from. It will not pass the request to Spring MVC and your controller.
Refer documentation
Purpose of loginPage()
The loginPage() tells the framework where the user will be redirected when login is required. For example when you are not authorized to the page, you get redirected to this page. This page performs the login activity, for example when you implement a loginForm() or oauth2Login() like in my code using Google OAuth2,this page redirects to google login.
http.anonymous().and()
.authorizeRequests().antMatchers("/images**").permitAll().and()
.authorizeRequests().anyRequest().authenticated().and()
.oauth2Login()
.successHandler((request, response, authentication) -> {
request.authenticate(response);
})
.loginPage("/oauth2/authorization/google")
.loginProcessingUrl("/login")
Purpose of loginProcessingUrl()
The loginProcessingUrl() is the method that automatically set the rule antMatchers("/thisUrl").permitAll() to this URL so that when the response is returned (code, state, token, etc.) will be allowed to be GETed and this response is processed as you can see in the authenticate method of the request. Something more important is that this loginProcessingUrl() tells that the response should be processed to this URL. Without this the request.authenticate(response) will not be executed and authentication will not be returned or otherwise you implement another algorithm.
May the following code segment from spring security source code will help you:
loginPage the login page to redirect to if authentication is required
loginProcessingUrl the URL to validate username and password
DEFAULT_LOGIN_PAGE_URL = "/login"
/**
* Updates the default values for authentication.
*
* #throws Exception
*/
protected final void updateAuthenticationDefaults() {
if (loginProcessingUrl == null) {
loginProcessingUrl(loginPage);
}
//...
}

CSRF token expires during login

I'm working on Spring web application and I need to avoid problem with expire csrf token on login page, because if user is waiting too long and try to login only one way to resolve problem with csrf is to reload page and try to login again. But it's not user friendly and I want to avoid this situation.
First question: Is it possible in general(by spring security 3.2.4)? Without disable csrf.
I tried to use security="none" for login page and spring seciruty "login_check", but it's not working, i got infinity redirect or I got error that no mapping for url "myhost/login_check".
Second question: How can i do it?
Recommended solution
I would say that you should not disable csrf tokens on a production site. You may make session (and thus the csrf token) last longer (but it usually should not last longer than a day, especially for not-logged-in users as it is a DOS vector), but the real solution may be to automatically refresh the login page when the csrf token expires. You may use a
<META HTTP-EQUIV="REFRESH" CONTENT="csrf_timeout_in_seconds">
in your login page header. If the user lets the login page sit for hours, it should not bother him that the page got refreshed.
Second solution
A possible solution which does not require you to actually store sessions but allows for infinite timeout is that you can generate your csrf tokens with hashing from the session id and a server-side secret:
csrf = hash(sessionid+secret)
Note however that you need to really dig and override spring-security internal mechanisms, namely:
re-creating anonymous sessions on the fly if a request arrives and no such session exists
re-creating the csrf token on the fly from the session id
And choose a very secure hashing algorithm, preferably sha-512.
Third solution
You could have a small javascript that calls a no-op page on your server regularly (just before the session timeout), thus extending your session. This results in infinite session timeout only if the browser is on all the time, so the DOS aspect is mitigated.
Ok, one last solution
You can alter the CSRF token checking code, and disable it for the login page. This is actually synonymous with the second solution, but is specific for the login page, not generally for all anonymous sessions.
You can do this e.g. by setting a custom RequestMatcher in HttpSecurity:
http.csrf().requireCsrfProtectionMatcher(new MyCsrfRequestMatcher());
...
class MyCsrfRequestMatcher implements RequestMatcher {
#Override
public boolean matches(HttpServletRequest request) {
return !request.getServletPath().equals("/login");
}
}
Another option would be set no timeout for the session by default and then, when the user is authenticated, change the timeout to whatever you want. You can see an example of how to do this here.
In one of the projects I worked on, I implemented the following:
Implement an exception handler which handles CsrfException (or AccessDeniedException in general in my case). Forward the request to a controller method.
#ExceptionHandler(AccessDeniedException.class)
#ResponseStatus(value = HttpStatus.FORBIDDEN)
public void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
request.getRequestDispatcher("/Access_Denied").forward(request, response);
}
In the controller method, check whether the original request is for the login page. If so, show an appropriate message within the login page.
if ("/login".equals(request.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH))) {
model.addAttribute("error", "An invalid security token has been detected. Please try again.");
return "login.jsp";
} else {
return "accessDenied.jsp";
}
With this approach, user will be able to retry the login without the need to refresh.
You can also make your CSRF protection rely on cookies and NOT server side session state. Spring Security has full support for this.
CookieCsrfTokenRepository
You will only receive a timeout if your cookie expires. This scales well since it's basically stateless (from the server's perspective).
#EnableWebSecurity
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
Andrew

Spring 4 MVC + Security login page redisplayed

I am implementing the security for my web application. The below are different event that takes place in authentication.
1) Successful login takes the users to the "dasboard" page.
2) Failure will take to the login page with the error.
3) The unauthenticated user access the secured page directly, redirected to the login, success takes to the dashboard, failure to the login with error.
4) The unauthenticated user access the secured page directly, redirected to the login, success takes to the dashboard, failure to the login with error.
The problem,
1) The user is successfully authenticated, views the dashboard page. Again he navigates directly to the login page, login is displayed. But, why should it? Should it not, by default, redirect to the dashboard? Should I need to check in my login controller if the user has already logged-in and redirect to dashboard or any configuration I am missing?
2) I have 3 role of users, super admin, admin and user. Can I redirect to different pages based on different role of users? If so how? If not how can this be implemented?
3) Based on the configuration I should be able to switch between LDAP auth or DB. If the app needs to be authenticated, in future, with ldap shall I inject the auth provider based on configuration? How do configure multiple authenticators?
Please share your wisdom/docs/links on "this is how it should be done" spring-security
My WebSecurityConfig.java
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin()
.loginPage("/login").loginProcessingUrl("/authenticate")
.defaultSuccessUrl("/dashboard").failureUrl("/login?error")
.usernameParameter("username").passwordParameter("password")
.permitAll().and().logout().logoutUrl("/logout").permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
AuthenticationProvider authenticationProvider = new CustomAuthenticationProvider();
auth.authenticationProvider(authenticationProvider);
}
1) As you said this can be easily done in your login controller. Just send a redirect instead of the login page if you know that the user is already logged in.
2) I think you mean the redirect after the user logged in, are you?
This can be achieved using a custom AuthenticationSuccessHandler. Maybe this question can give you more details.
3) You can configure multiple AuthenticationProviders (e.g. one for LDAP and one for DB) using an ProviderManager AuthenticationManager. An option would be to configure the AuthenticationProviders during startup in your java config based on the given environment or configuration values.
1) Create session when the user login.and always check that login session on you login page...if session is caught ,redirect to dashboard,....if session in null let it live on login page...
2) Create sessions according to the which user is login, for example if "super user" logins, create session of "superUser"....and vice-versa...
Now check that session,if session has value of superUser,redirect it to desired page for super user....and vice versa...
In both of these using session is good choice.

How to see the redirect status from an STS / IdP

I have searched (probed ,even) for an answer to this but haven't come up with anything useful so far. I'm pretty new to ADFS, STS's in general and WIF so please excuse any obvious ignorance or inappropriate use of terminology. ;)
I'm currently integrating a custom MVC3 app with an external IdP, via ADFS. The ADFS to IdP setup is all done and working.
Some parts of the site are accessible to anon users - in the web.config authentication mode has been set to none. The other parts are protected by having their controllers/action methods decorated by a custom System.Web.Mvc.AuthorizeAttribute.
All the usual modifications to the web.config for using the WsFederationAuthenticationModule have been made and it works 95%; the user can browse to the anon accessible parts of the site. When they try and hit the protected parts, the authorize attribute checks if they have some custom information from our IdP in the IClaimsPrincipals associated with the HttpContext.Current.User and then sets the ActionResult to 401 if not; The WsFederationAuthenticationModule kicks in and redirects them to the IdP's login page. When they enter their details, they're then successfully redirected with some FedAuth cookies and the authorization then passes.
The problem starts when they get to the IdP's login page. This particular IdP has a link to return you directly to our site (to the same page the original request was made to), with this SAML response embedded somewhere (this is according to their documentation)
urn:oasis:names: tc:SAML:2.0:status: AuthnFailed
At this point, they are now "Unauthorized" and all the user will see (at least in dev) is a 401 page. You have to kill the session or otherwise get rid of that cookie to start again.
What I need to do is intercept that redirect request from the IdP, and essentially check for that particular SAML status, because the user should then be redirected to one of the unauthorized areas as if nothing has happened. I've tried something like this in the global.asax:
protected void Application_Start()
{
// mvc stuff here....
// add handler to intercept handling creation of security tokens by WsFederationAuthnticationModule
FederatedAuthentication.ServiceConfigurationCreated += OnServiceConfigurationCreated;
}
void OnServiceConfigurationCreated(object sender, ServiceConfigurationCreatedEventArgs e)
{
FederatedAuthentication
.WSFederationAuthenticationModule
.SessionSecurityTokenCreated += WSFederationAuthenticationModule_SecuityTokenCreated;
}
public void WSFederationAuthenticationModule_SecuityTokenCreated (Object sender, SessionSecurityTokenCreatedEventArgs args)
{
var token = args.SessionToken;
// do something with the session token here e.g. check for SAML status
}
.. but I cant see anything useful on that token; nothing to indicate a specific response status. The fact that there is a FedAuth cookie at all but no custom info from the Idp is a dead give away that the user has been there but somehow failed to authenticate, but in principle I want to be able to see that status. I might have to deal with timeouts at the IdP as well....
Maybe I'm doing this all wrong, or just plain old don't understand, but can somehow fill me in on how to determine those response statuses?
Phew. Thank you! :D
Ok, so I'm going to answer my own question.
The answer to whether I can get that custom status from my IdP is a no, at the moment. :(
But this is only because ADFS is not setup to capture it and pass it on. Apparently you need to do some custom coding for capturing information from the back channel that is opened between ADFS and the IdP.... well beyond the current scope of work.
As a work around for the moment:
If a request is made to the site and there is NO SAML token, its a new request by a user who has made no auth attempt at the Idp
If there is a SAML token but no ID from the IdP in the token (which is only present when they auth properly), then the user failed Auth for some reason
If there is a SAML token with the ID present, the user auth'd properly
Not great but acceptable. BTW, all credit goes to YMC in this SO post for the following code which lets you check for SAML tokens:
void WSFederationAuthenticationModule_SecurityTokenReceived(object sender, SecurityTokenReceivedEventArgs e)
{
var message = SignInResponseMessage.CreateFromFormPost(Request) as SignInResponseMessage;
var rstr = new WSFederationSerializer()
.CreateResponse(message,
new WSTrustSerializationContext(
SecurityTokenHandlerCollectionManager.CreateDefaultSecurityTokenHandlerCollectionManager()));
}
Pce!

Resources