Spring Security CookieBasedCSrf not working - spring

I am developing a RESTful Spring backend with an Angular2 front end. I store my access token (JWT implementation) in a httpOnly Cookie. To protect myself from XSRF attacks on post requests, I need to enable XSRF protection on all pages, except the login page. Per the Spring Security guide here, I have enabled CookieCsrfTokenRepository.
However, when I hit a public API (GET), the XSRF-TOKEN is not set. Also, when I submit my login form data from Angular2, the system thows a 'invalid csrf token' error. Below is my WebSecurityConfig:
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.authenticationEntryPoint(this.authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
.antMatchers(TOKEN_CSRF_ENTRY).permitAll()
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() // Protected API End-points
.and()
.cors()
.and()
.addFilterBefore(buildAjaxLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);

You should not get a CSRF token by HTTP GET, see Spring Security Reference:
Use proper HTTP verbs
The first step to protecting against CSRF attacks is to ensure your website uses proper HTTP verbs. Specifically, before Spring Security’s CSRF support can be of use, you need to be certain that your application is using PATCH, POST, PUT, and/or DELETE for anything that modifies state.
This is not a limitation of Spring Security’s support, but instead a general requirement for proper CSRF prevention. The reason is that including private information in an HTTP GET can cause the information to be leaked. See RFC 2616 Section 15.1.3 Encoding Sensitive Information in URI’s for general guidance on using POST instead of GET for sensitive information.
You should not exclude login from CSRF protection, see Spring Security Reference:
Logging In
In order to protect against forging log in requests the log in form should be protected against CSRF attacks too.
But you can exclude some URLs from and include some HTTP verbs to CSRF protection, see Spring Security Reference:
You can also specify a custom RequestMatcher to determine which requests are protected by CSRF (i.e. perhaps you don’t care if log out is exploited). In short, if Spring Security’s CSRF protection doesn’t behave exactly as you want it, you are able to customize the behavior. Refer to the Section 41.1.18, “<csrf>” documentation for details on how to make these customizations with XML and the CsrfConfigurer javadoc for details on how to make these customizations when using Java configuration.

Have you tried to enable withCredentials: true?
This is how I write my post server in Angular2.
this.http.request(
path,
{
method: RequestMethod.Post,
body: body,
headers: customHeaders,
withCredentials: true
}
)
.map...
.catch...
I have the same config in WebSecurityConfig.
I do have a working example using Angular 2 and Spring Boot that uses CookieCsrfTokenRepository.
My Angular 2 API service
My WebSecurityConfig
Github repo: angular-spring-starter

Related

How to make Postman show CSRF token from get request using postman interceptor

I'm having specific problem that I would like to find solution to. I'm working with my spring security and I should use Postman Interceptor to retrieve X-CSRF-TOKEN in Cookies section. I installed postman interceptor, and this is my spring security code with enabled csrf
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
Now I'm not getting any cookies beside some SESSIONID and I've not idea how I should set up interceptor on this postman that will retrieve me cookies after enabling postmang interceptor, so if you could be kind and lead me? I've been struggling with this for days, and I also tried few approaches from here and ask on different groups, but no luck. Thanks in advance. Also I have user with pw for basic auth if needed and this is what I only get from postman https://prnt.sc/74GGlfA4TYYN
In my case I usually configure Spring Security to use a CookieCsrfTokenRepository, like so:
http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnly(false)
);
And in my Postman requests, I add this script in the Tests tab.
var xsrfCookie = postman.getResponseCookie("XSRF-TOKEN");
pm.environment.set("xsrf-token", decodeURIComponent(pm.cookies.get("XSRF-TOKEN")))
In the Headers section of your request in Postman, you should have a header like this:
Key
Value
X-XSRF-TOKEN
{{xsrf-token}}
With this setup, Postman will retrieve the Cookie with the CSRF token returned from your backend and add it as an environment variable so you can use it in the next requests.

How to setup ForwardedHeaderFilter for login using Spring Security without Spring Boot?

I am looking to setup the ForwardedHeaderFilter in spring security so I can let spring know which protocol to use after login. I have several app servers behind a load-balancer (using ssl termination) and spring security is redirecting the user using http (instead of https). Because of this, my users are now getting a obtrusive warning message. The only examples I can find online are with spring boot which I do not implement.
I thought of using "addFilterBefore()" method to my security configuration, but the filter is never called.
Any ideas?
// Apply sameOrigin policy for iframe embeddings
http.headers().frameOptions().sameOrigin();
// ********* Add filter here? *******
http.addFilterBefore(new ForwardedHeaderFilter(), ChannelProcessingFilter.class);
// Authorization filters
http.authorizeRequests().antMatchers("/sysAdmin/**", "/monitoring/**").access("isFullyAuthenticated() and hasRole('GOD')");
http.authorizeRequests().antMatchers("/app/**").authenticated();
http.authorizeRequests().antMatchers("/**").permitAll();
http.formLogin()
.loginPage("/public/login.jsp")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/app/Dashboard.action", false)
.failureHandler(customAuthenticationFailureHandler());
// Disable so that logout "get" url works (otherwise you have to do a html form)
http.csrf().disable();
http.logout().logoutSuccessUrl("/public/login.jsp");
http.sessionManagement()
.invalidSessionUrl("/public/expiredSession.jsp?expiredId=2")
.maximumSessions(2)
.sessionRegistry(sessionRegistry())
.expiredUrl("/public/expiredSession.jsp?expiredId=3");
I ended up adding the filter like this and everything seemed to work
// Added for load balancer headers (X-Forwarded-For, X-Forwarded-Proto, etc)
http.addFilterBefore(new ForwardedHeaderFilter(), WebAsyncManagerIntegrationFilter.class);

Spring SAML SSO with OKTA - InResponseTo when changing web app context

We're having a lot of trouble with OKTA SAML SSO integration with Spring Security. We're using the saml-dsl extension to Spring Security to configure the auth, and everything works fine on HTTP, however when we try to use HTTPS the authentication only works when the app is deployed on root (/) context. When we change the context to anything else, it stops working and starts throwing InResponseTo field errors and sometimes with different configurations it comes to a redirect loop.
Here's the configuration we're using:
http
.csrf()
.disable();
http
.sessionManagement().sessionFixation().none();
http
.logout()
.logoutSuccessUrl("/");
http
.authorizeRequests()
.antMatchers("/saml*").permitAll()
.anyRequest().authenticated()
.and()
.apply(samlConfigurer())
.serviceProvider()
.keyStore()
.storeFilePath(config.getKeystorePath())
.password(config.getKeystorePassword())
.keyname(config.getKeyAlias())
.keyPassword(config.getKeystorePassword())
.and()
.protocol("https")
.hostname(String.format("%s:%s", serverURL, config.getServerPort()))
.basePath("/"+contextRoot)
.and()
.identityProvider()
.metadataFilePath(config.getMetadataUrl());
And we should have our OKTA setup properly as well ( https://localhost:8443/ourappcontext/saml/SSO for testing, all the other stuff too )
We've tried most of the solutions proposed on here and the Spring documentation ( empty factory, spring session management and session fixation and so on ) and nothing seems to work. Are we doing something wrong? We're currently not generation any SP metadata, could it be that this is at fault and the request is somehow redirected to the wrong place or something? Really confused as of right now, first time using SAML and I'm not sure if it's the extension, the OKTA config or the Spring config...
We're deploying on Wildfly and you set the application context on there through a separate jboss-web.xml, if that matters at all.
By default the HttpSessionStorageFactory is used and this provides HttpSessionStorage SAML message store.
--> The HTTP session cookie is the browser side key to the server side SAML message store.
When the HTTP session cookie is not sent by the browser when the SAML response is delivered to Spring Security SAML SP, it can not find the related SAML AuthNRequest and fails to compare 'InResponseTo' value with 'ID' value of matching AuthNRequest.
If you can not assure HTTP session cookie is delivered you may implement your own SAMLMessageStorageFactory and SAMLMessageStorage (as I did).

Receive Authorization header on anonymous url using Spring Boot

How can an Authorization header be accessed on anonymous urls?
My security configuration looks like:
http
.authorizeRequests()
.antMatchers("/login", "/legacy-login").anonymous()
.antMatchers("/things/*").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic()
Authentication in general works fine. However, on /legacy-login I need to do some migration and need to access the authorization header without spring boot managing the authorization. Although /legacy-login is marked anonymous as soon as there are requests, spring intercepts the request and tries to authorize itself (what then results into 401).
How can I make Spring let the auth header through on that single url?
I foudn one solution myself. Instead of fiddleing around with .anonymous() and .permitAll() I added /legacy-login as ignore rule:
override fun configure(web: WebSecurity) {
super.configure(web)
web.ignoring()
.antMatchers("/legacy-login")
}

How to allow certain endpoint in spring security to be allowed without authentication?

I have Spring Boot Rest API web app in which I am using spring security to have most endpoints to require authentication.
This is part of code:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
....
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.requestMatchers()
.antMatchers("/oauth/token")
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
}
Can some one explain what each line begining with http.csrf means?
How can I modify above code so that enpoint /bars/pk can be allowed to be accessed without requiring authentication just as if there was no Spring Security ?
By default, Spring Boot activates protection against CSRF attack (Cross Site Request Forgery attack). The attack consists of a malicious site taking advantage of a user being alredy authenticated to a site (e.g. bank) in order to trick the user to do actions on that site (e.g. fund transfer).
The protection against the attack consists of the Spring Boot application sending a token with every response and expecting the token to be sent by the client on subsequent request. If the token is not received, Spring Boot returns an error.
Sometimes, you want to disable this behavior (at your own risks), so you use csrf.disable. You might find it convenient to disable csrf protection if you develop a Stateless API, and you have no way to link a POST request to any previous requests or session. But again, you need to consider this and reason about it carefully.
Please note that CSRF protection has not effect on GET requests. It only affects state chaning requests (e.g. POST, DELETE)
In order to allow your endoints to anyone, without requiring any authentication, you need to use
http.authorizeRequests().antMatchers("/**").permitAll();
EDIT
To specifically allow unauthorized requests to /bars/pk and keep the other elements unchanged, modify your code as follows :
http.csrf().disable()
.requestMatchers()
.antMatchers("/oauth/token")
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.antMatchers("/bars/pk").permitAll()
.anyRequest().authenticated();
Here is a complete example:
httpSecurity.authorizeRequests()
.antMatchers(HttpMethod.GET)
.permitAll() // Allow all GET requests to go unauthenticated
.antMatchers(allowedResources)
.permitAll() // Allow all requests to go unauthenticated for the specified paths
.antMatchers(protectedResources).hasRole(USER)
.antMatchers(adminResources).hasRole(ADMIN)
.anyRequest().authenticated(); // Authenticate all other request paths

Resources