Spring Security denies logout CSRF token - java

I'm trying to implement an Angular app using this tutorial: https://spring.io/guides/tutorials/spring-security-and-angular-js/
Logging in works and performing subsequent HTTP calls works, too. Angular successfully appends the CSRF token and Spring successfully parses it. Assuming the token is foo, the requests will contain these headers:
Cookie: JSESSIONID=...; XSRF-TOKEN=foo
X-XSRF-TOKEN: foo
Now, when trying to log out with
$http.post('logout', {}), Angular will use exactly the same headers. However, Spring answers with a 403:
Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.
This is what my security configuration looks like:
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated().and()
.logout().and()
.addFilterBefore(new CsrfHeaderFilter(), CsrfFilter.class);
}
CsrfHeaderFilter is the class explained in the tutorial (which apparently works for every other request).

I realize it's 2 months late, but I was following the exact same guide today and this unanswered post keeps on popping up so here's the solution.
Basically, you were missing the csrfTokenRepository() configuration in the HttpSecurity configurer.
Spring CsrfTokenRepository expects the header "X-CSRF-TOKEN" but Angular sends the token in a header called "X-XSRF-TOKEN" so the guide recommended you setup an instance of CsrfTokenRepository which expects the Angular default header "X-XSRF-TOKEN":
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated().and()
.logout()
.and()
//This is the first part you were missing
.csrf()
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterBefore(new CsrfHeaderFilter(), CsrfFilter.class);
}
#Bean
public CsrfTokenRepository csrfTokenRepository(){
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
// This is the second part you were missing
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}

Related

zuul proxy always giving 401 unauthorized

I am using zuul proxy for routes and have added JWT authentication for the same. I have specified the APIs for with authorisation is to be skipped for example (/auth) but i am not able to call the same as I am getting 401 for the permitted URLs as well.
Following are the code snippet.
Class implementing WebSecurityConfigurerAdapter
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.and()
.addFilterAfter(new JwtTokenAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/auth/**").permitAll()
.antMatchers("/ping").permitAll()
.antMatchers("/login/**").permitAll()
.antMatchers("/signup/**").permitAll()
.anyRequest().authenticated();
}
And also my application.properties file looks as mentioned below
server.port=8762
spring.application.name=zuul-server
eureka.client.service-url.default-zone=http://localhost:8761/eureka/
# A prefix that can added to beginning of all requests.
zuul.prefix=/api
# Disable accessing services using service name (i.e. gallery-service).
# They should be only accessed through the path defined below.
zuul.ignored-services=*
# Map paths to services
zuul.routes.user-service.path=/users/**
zuul.routes.user-service.service-id=user-service
zuul.routes.user-service.sensitive-headers=Cookie,Set-Cookie
# Map path to auth service
zuul.routes.auth-service.path=/auth/**
zuul.routes.auth-service.service-id=auth-service
zuul.routes.auth-service.strip-prefix=false
# Exclude authorization from sensitive headers
zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie
But I am not able to hit /ping or /login or /auth APIs all are giving 401.
Could someone please help me regarding the same.
Thanks in advance !!!
try this
.antMatchers(HttpMethod.POST, "/api/auth/**").permitAll()

Spring security default form login page failing when antmatcher defined

I am having an issue with Spring 5 form based authentication. This is only an example at this point as I am trying to isolate the issue I'm having. My plan is to include this configuration in a larger project with multiple configurations. The below code (which can be found anywhere) works as expected:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user").password(passwordEncoder.encode("password")).roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
}
}
and following code does not work after adding antmatcher:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("user").password(passwordEncoder.encode("password")).roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.antMatcher("/test/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and().csrf().disable();
}
}
Notice that I've only added an antmatcher to this config. I only want the config to apply to /test/**. And, in this case, I'm not doing anything custom with regards to the form login and would like to get Spring default form. Of course in a real application I would not use the default but this is only for example purposes. My first config displays the default internal Spring form correctly when I GET the following URL: http://localhost:8080/webapp/test/anything. And I can authenticate successfully. For the second configuration I get 404 errors when issuing the same URL.
Spring security does in fact try to redirect to: http://localhost:8080/webapp/login however, the url does not exist. I did try the following change:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/test/**").authorizeRequests().anyRequest().authenticated()
.and().formLogin().permitAll().loginPage("/test/login").loginProcessingUrl("/test/login")
.and().logout().logoutUrl("/test/logout").invalidateHttpSession(true).logoutSuccessUrl("/");
}
This did not work either. I made several attempts, but have not been successful. Is it possible that the default form behavior is disabled when an antmatcher is defined?
This is ideal solution for your requirement
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.requestMatchers().antMatchers("/test/**", "/customLoginPage", "/logout").and()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/customLoginPage") //If you don't configure default is "/login"
.usernameParameter("userName").passwordParameter("password") //In case of custom form parameters
.defaultSuccessUrl("/app/user/dashboard") //If you don't configure default is "/"
.failureUrl("/login?error=true") //If you don't configure default is "/login?error"
.and().csrf().disable();
}
Let me explain Some cases how spring security deals with
Case 1:
http.authorizeRequests()
is equals to
http.antMatcher("/**").authorizeRequests()
A proxy filter will be defined and url-pattern for that filter will be "/**". With this type of configuration there will be no problem as it is a wild card. But in some cases we don't want to define wild card "/**" then we should configure requestMatchers correctly otherwise we will end up in lot of unguessable problems.
Case 2:
http.antMatcher("/test/**").authorizeRequests()
Here proxy filter will be added with URL pattern "/test/**" and requests with /login and /logout can't pass through the added filter. To overcome this .requestMatchers() should be used as given below
http
.requestMatchers().antMatchers("/test/**", "/customLoginPage", "/logout")
.and().authorizeRequests()
This means filter with filter mapping as given below
<filter-mapping>
<filter-name>/test/**</filter-name>
<url-pattern>/customLoginPage</url-pattern>
<url-pattern>/logout</url-pattern>
</filter-mapping>
You can try out some of basic examples(Working) from my github repository
After many tries, the following is working for me. Only showing the configure method:
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.antMatcher("/test/**")
.authorizeRequests()
.antMatchers("/test/login").permitAll()
.antMatchers("/test/logout").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.jsp").permitAll()
.loginProcessingUrl("/test/login").permitAll()
.failureUrl("/login.jsp?error=yes")
.and()
.logout().permitAll().logoutUrl("/test/logout").permitAll()
.and().csrf().disable();
}
I did need to create my own login form (login.jsp), however the processing of the login form (the POST) is handled by Spring default processing. Really, that was the important piece. Another key point to make is that the login form POST needs to point to /webapp/test/login. I suppose that the Spring configuration needs these additional "tips" in order to trigger the default form processing in cases where the antMatcher call must be present.

Spring Security ignore path filter still executed when Authorization header present

I have a fully working spring security process set up with some paths requiring authentication (via a token) and others I want to keep open and accessible without token. The issue I am running into is that when a request comes in to one of those open paths without the Authorization header, then the filters are ignored and the proper response is generated. However, when the Authorization header is present, even though on the ignored path, the request goes through the entire security filter chain when the ideal procedure would be to entirely skip the filter chain.
Below is my configuration.
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(DEFAULT_IGNORE_REQUESTS);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
authenticationTokenHeaderFilter.setAuthenticationManager(authenticationManager);
http.authorizeRequests()
.antMatchers("/example/**")
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler((request, response, accessDeniedException) -> {
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
})
.authenticationEntryPoint(new HttpAuthenticationEntryPoint())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors()
.and()
.csrf().disable()
.addFilter(authenticationTokenHeaderFilter)
.addFilterBefore(new ExceptionTranslationFilter(
new Http403ForbiddenEntryPoint()),
authenticationTokenHeaderFilter.getClass()
);
}
public class AuthenticationTokenHeaderFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest httpServletRequest) {
return httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest httpServletRequest) {
return "N/A";
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
I have additionally tried putting the ignored paths to the HttpSecurity with permitAll() with no success.
Clarification
String[] DEFAULT_IGNORE_REQUESTS = new String[]{ "/actuator" };
In what is described above, any requests going to /example/** should go through the security chain and through my filter to make sure the user is authenticated. Any requests going to /actuator should not go through the security filter chain. The /example/** works correctly and as expected. The /actuator however does not.
When I make a request without the Authorization header, the security chain is not invoked.
When I make a request with the Authorization header present, the security chain is invoked and the Authorization value (token) is verified. In the event that the token is invalid, a custom exception gets thrown inside the filter. Even though the error gets thrown, I get the expected response from /actuator with a 200. The thrown error in this case however gets logged and a stack trace gets generated, which I do not want as it's not an error in that case.
In Spring Boot, any #Bean of type Filter gets added as a servlet filter. What's most likely happening is that your filter is being added as a filter entirely separate from the filter chain.
Instead of declaring your filter as a #Bean, you could initialize AuthenticationTokenHeaderFilter in your WebSecurityConfigurerAdapter and set the AuthenticationManager directly (which you already do anyways). So you can remove the #Autowired annotation in the filter.

How to change Login URL in Spring Security

I created an API that provides User authentication and it's login operation is handled on default '/login' path by Spring Security.
I want to change this path to 'api/v1/login'.
this is my security config :
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/h2-console/**/**").permitAll()
.antMatchers(HttpMethod.POST,"/user/register").permitAll()
.antMatchers("/user/activate").permitAll()
.antMatchers("/user/reset-password").permitAll()
.antMatchers("/user/reset-password").permitAll()
.antMatchers("/admin/user").hasRole("ADMIN")
.antMatchers("/roles").permitAll()
.antMatchers("/user/**").hasRole("USER")
.and()
.formLogin().loginProcessingUrl("/api/v1/login")
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(new ExceptionHandlerFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUserDetailService));
I have added this line to change it :
.formLogin().loginProcessingUrl("/api/v1/login")
But it is still working under '/login' path.
"/api/v1/login" return 404.
Is there any way to change it ?
Spring Boot Version : 2.0.0.RELEASE
The function .loginProcessingUrl("/api/v1/login"), specifies the URL to validate the credentials, the URL to validate username and password.
It will only override url to /api/v1/login of POST type, not GET
It will not pass the request to Spring MVC and your controller
For additional customization you can have a look through FormLoginConfigurer
UPDATE v1
Can you also check if your urls under /api/** are all secured?
If yes then try removing the security from /api/v1/login and add permitAll() configuration to this url
Check this post - https://stackoverflow.com/a/27471722/2600196. if it helps your scenario
UPDATE v2 - this helped in the case here
you were not sending the username and password correctly and for that to work refer the things below, in your it was showing up BadCredentialsException. I enabled debug on the application and was able to figure that out.
you need to post the parameters to the url - http://localhost:8080/api/v1/login as below (have also attached the image, screenshot of postman):-
headers:
Content-Type=application/x-www-form-urlencoded
parameters in key value pairs(not in json format, please refer the image):
username=player3
password=pass3
Above you can the response coming up from the index.html like below:-
test static resource
Which you also need to customize.
For Sending the JSON request for username and password, the changes that will work easily for you will be:-
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/h2-console/**/**").permitAll()
.antMatchers(HttpMethod.POST,"/user/register").permitAll()
.antMatchers("/user/activate").permitAll()
.antMatchers("/user/reset-password").permitAll()
.antMatchers("/user/reset-password").permitAll()
.antMatchers("/admin/user").hasRole("ADMIN")
.antMatchers("/roles").permitAll()
.antMatchers("/user/**").hasRole("USER")
//.and()
//.formLogin().loginProcessingUrl("/api/v1/login") // not required any more
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(new ExceptionHandlerFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilter(jwtAuthorizationFilter())
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtUserDetailService));
http.headers().frameOptions().disable(); // its required for h2-console
}
public JwtAuthenticationFilter jwtAuthorizationFilter() throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/api/v1/login");
return jwtAuthenticationFilter;
}
And the code .formLogin().loginProcessingUrl("/api/v1/login") not required anymore
Further you need to add the success and the failure urls to the application, and to make your login url to fetch json based user credentials, you need to follow up and need to do some more stuff, some useful reference for that - https://stackoverflow.com/a/19501060/2600196
You are extending org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter which itself extendsorg.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter. In this last class, there is a setter called 
setFilterProcessesUrl
 which is intended to do just this:
setFilterProcessesUrl
public void setFilterProcessesUrl(String filterProcessesUrl)
Sets the URL that determines if authentication is required
Parameters: filterProcessesUrl
This is the link to that javadoc section
So in your WebSecurityConfigurerAdapter you could do just like this:
#Bean
public JWTAuthenticationFilter getJWTAuthenticationFilter() {
final JWTAuthenticationFilter filter = new JWTAuthenticationFilter(authenticationManager());
filter.setFilterProcessesUrl("/api/auth/login");
return filter;
}
And then in your configure method in the same class just reference it instead of creating new instance:
.addFilter(getJWTAuthenticationFilter

How to enable POST, PUT AND DELETE methods in spring security

I developed an application with spring boot, which was working fine. There is a restful controller. I tried to add spring security to some of the pages.
The rest controller's endpoint is
/api/greetings
I configured the security settings in the class below.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home","/api/greetings").permitAll()
//.antMatchers("/api/greetings","").permitAll()//can't do this
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
Now, when I tried accessing the Rest endpoint, from a Rest-client(Postman), only the GET method is accessible and i am getting 403 Forbidden response if I try to POST, PUT or DELETE.
{
"timestamp": 1467223888525,
"status": 403,
"error": "Forbidden",
"message": "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.",
"path": "/api/greetings/2"
}
How do i solve this issue. I am new to Spring Security things.
UPDATE Answer
If you're using Spring security 4, you can disable specific routes easily
http.csrf().ignoringAntMatchers("/nocsrf","/ignore/startswith/**")
If not, you can enable/disable CSRF on specific routes using requireCsrfProtectionMatcher
http.csrf().requireCsrfProtectionMatcher(new RequestMatcher() {
private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
private RegexRequestMatcher apiMatcher = new RegexRequestMatcher("/v[0-9]*/.*", null);
#Override
public boolean matches(HttpServletRequest request) {
// No CSRF due to allowedMethod
if(allowedMethods.matcher(request.getMethod()).matches())
return false;
// No CSRF due to api call
if(apiMatcher.matches(request))
return false;
// CSRF for everything else that is not an API call or an allowedMethod
return true;
}
});
ORIGINAL Answer
You got an error because CSRF handling is 'on' by default with Spring Security.
You can disabled it by adding http.csrf().disable();.
But really, would you leave your application unsecured? I invite you to read this article to protect your application against CSRF, even if your application is based on REST service and not form submission.

Categories