The official Spring Github Repo's Readme reads:
The application is almost finished functionally. The last thing we
need to do is implement the logout feature that we sketched in the
home page. If the user is authenticated then we show a "logout" link
and hook it to a logout() function in the AppComponent. Remember, it
sends an HTTP POST to "/logout" which we now need to implement on the
server. This is straightforward because it is added for us already by
Spring Security (i.e. we don’t need to do anything for this simple use
case). For more control over the behaviour of logout you could use the
HttpSecurity callbacks in your WebSecurityAdapter to, for instance
execute some business logic after logout.
Taken from: https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/single
However, I am using basic authentication and testing it with Postman app. The POST on '/logout' gives me a 403 Forbidden like so:
{
"timestamp": "2018-07-30T07:42:48.172+0000",
"status": 403,
"error": "Forbidden",
"message": "Forbidden",
"path": "/logout"
}
My Security Configurations are:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/save")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/user/**")
.hasRole("USER")
.and()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.and()
.httpBasic()
.and()
.logout()
.permitAll()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
I want the session to be invalidated, all the cookies to be deleted, such that when I query again on the endpoint /user with wrong credentials, I should get a 403. However, even after POST on /logout (which gives 403 anyway), the application accepts the GET on /user from the previous session and shows me the details of the user.
The endpoint is:
#GetMapping
public Principal user(Principal user){
return user;
}
Related
I am studying some code of spring-security. I would like to understand this example that I found on internet
1:
http.requestMatchers()
.antMatchers("/management/**") // (1)
.and()
.authorizeRequests() // (2)
.antMatchers("/management/health")
.permitAll()
.antMatchers("/management/info")
.permitAll()
.antMatchers("/management/**")
.hasRole("ACTUATOR")
.anyRequest().permitAll()
.and()
.httpBasic(); (3)
}
I can not understand this configuration, why this code:
http.requestMatchers()
.antMatchers("/management/**")
.and()
Is before the .authorizeRequests() ? (1)
What does that mean?
Can you explanation this example?
2: In the second case, what is the difference?
http.requestMatchers().antMatchers("/rest2/**")
.and()
.authorizeRequests()
.antMatchers("/rest/v1/test/hello").permitAll()
.antMatchers("/rest/v1/test/**").denyAll()
.and()
.requestMatchers().antMatchers("/rest/**")
.and()
.authorizeRequests()
.antMatchers("/rest/v1/test/hello").permitAll();
What is the impact using requestMatchers()?
If I send a request to "/rest/v1/test/hello2" I received a 401 Why if the rule that deny a request does not match with the antMatchers("/rest2/**") ?
The purpose of requestMatchers() is to specify which requests the spring security configuration will be applied to.
For example if you have 2 endpoints "/public" and "/private" and you only want security (specifically csrf protection) applied to "/private", then you could add the following configuration:
http
.requestMatchers()
.antMatchers("/private/**")
.and()
.csrf();
Then, if you POST to "/private" you will get a 403 response.
But if you POST to "/public" you will get a 200, because there has been no security applied.
This is separate from authorizeRequests which indicates the type of access that is required for that endpoint, as opposed to whether security is applied at all.
In example 1 that you mention
http
.requestMatchers()
.antMatchers("/management/**")
.and()
...
the security configuration is only applied to "/management/**", so if you were to make a request to "/foo", it would not be secured.
In example 2 that you mention,
http
.requestMatchers()
.antMatchers("/rest2/**")
.and()
.authorizeRequests()
.antMatchers("/rest/v1/test/hello").permitAll()
.antMatchers("/rest/v1/test/**").denyAll()
.and()
.requestMatchers()
.antMatchers("/rest/**")
.and()
.authorizeRequests()
.antMatchers("/rest/v1/test/hello").permitAll();
the reason why "/rest/v1/test/hello2" responds with a 401 is because "/rest/**" is in a request matcher, so your security rule .antMatchers("/rest/v1/test/hello").permitAll() will apply.
If you were to make a request to "/rest3/v1/test/hello2", then it would respond with a 200 because "/rest3/**" is not part of any request matcher.
Spring security API:
public final class HttpSecurity.RequestMatcherConfigurer
extends AbstractRequestMatcherRegistry
Allows mapping HTTP requests that this HttpSecurity will be used for
The following spring security config gives some unexpected behavior.
When making a request to some (non-health-check) endpoint (/user), in the browser and when using curl (via git bash on windows), an unauthenticated request returns an idp redirect as expected.
However, when using the WebTestClient, it returns 401 Unauthorized with www-authenticate: [Basic ...].
The request for basic authn in this context (and the password generated at startup) are unexpected because I've declared to disable basic authn via http.httpBasic().disable().
Why would this response come? Is there a better way to override the default basic auth configs? Is there an ordering on these configurations as suggested in this post? Where is this documented?
...env values
#Bean
public SecurityWebFilterChain webFilterChain(ServerHttpSecurity http) {
http.oauth2Client()
.and()
.oauth2Login()
.and()
.httpBasic()
.disable()
.formLogin()
.disable()
.csrf()
.disable()
.authorizeExchange()
.pathMatchers("/actuator/health").permitAll()
.anyExchange().authenticated();
return http.build();
}
#Bean
ReactiveClientRegistrationRepository getClientRegistrationRepository() {
ClientRegistration google =
ClientRegistration.withRegistrationId("google")
.scope("openid", "profile", "email")
.clientId(clientId)
.clientSecret(clientSecret)
.authorizationUri(authUri)
.tokenUri(tokenUri)
.userInfoUri(userInfoUri)
.redirectUri(redirectUri)
.jwkSetUri(jwksUri)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.userNameAttributeName("name")
.build();
return new InMemoryReactiveClientRegistrationRepository(google);
}
Project on github: https://github.com/segevmalool/spring-samples/blob/main/spring-security-webflux-postgres
httpBasic().authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
Solution
I need Spring to return unauthorized (status 401) instead of redirecting user to login page. Reason is that I'm doing a SPA application backend that already has built in mechanism for redirecting when http status code 401 recieved. Version of Spring Boot I'm working with is 1.3.0 but I've also tried it with 1.4.1 and still getting the same problem.
My security configuration is:
#Configuration
#EnableWebSecurity
#EnableRedisHttpSession
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/loginPath").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.formLogin().loginPage("/loginPath")
.successHandler(loginSuccessHandler()).permitAll()
.and()
.logout().deleteCookies("SESSION").permitAll()
.and()
.authenticationProvider(authenticationProvider)
.csrf().disable()
.httpBasic().disable();
}
...
}
Thanks for helping.
EDIT
There was a view controller mapping for "/error" to redirect user to login page with highest priority assigned. When that was removed, everything worked as expected.
It seems like Spring Security would return 401 and /error as a view but due to redirect config and higher priority it would get overriden.
I'm using Spring Security with Thymeleaf and want to create a login and a register form on different sites that make both use of CSRF protection. Protecting the login site is easy, as with the folloing WebSecurity configuration
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
Spring supports adding CSRF protection in general by the Security Filter Chain that is build in the configure method. This Filter Chain contains a CSRFFilter that adds/evaluates the CSRF token. This Filter Chain is then used for all matches defined in the above configuration. The mechanism of getting the Filters that are applied to a request can be found here in the method
doFilterInternal(ServletRequest, ServletResponse, FilterChain)
The problem is, if I add the "/register" site to this configuration, the user is redirected to the "/login" site first. If I don't add it to the above config, the mentioned FilterChain is not applied (and so not the CsrfFilter).
So what I want is to reuse the CsrfFilter in the Filter Chain of the "/register" site, but I don't know how to do that.
I'd prefer this approach to other ideas like writing a custom CSRF filter as suggested here or here.
From all of this i understood the problem is that you want people to access /register without needing to login first. Which is a simple fix:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.requestMatchers()
// add this line
.antMatchers("/register").permitAll().and
//
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access")
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
Turned out that the Spring Security Filter chain is applied to all endpoints mentioned in the list provided to requestMatchers().antMatchers().
So to use CSRF protection for a site that is not the login site, I just had to add it to this list and then permit all access to it, so there is no redirect to the login page. My final config looks like this
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.requestMatchers()
// consider these patterns for this config and the Security Filter Chain
.antMatchers("/login", "/register", "/oauth/authorize", "/oauth/confirm_access", "/oauth/token_key",
"/oauth/check_token", "/oauth/error")
.and()
// define authorization behaviour
.authorizeRequests()
// /register is allowed by anyone
.antMatchers("/register").permitAll()
// /oauth/authorize needs authentication; enables redirect to /login
.antMatchers("/oauth/authorize").authenticated()
// any other request in the patterns above are forbidden
.anyRequest().denyAll()
.and()
.formLogin()
// we have a custom login page at /login
// that is permitted to everyone
.loginPage("/login").permitAll();
}
I am experimenting with Spring Security 3.2.0.RC2 using javaconfig and it appears that the logout url is POST only. Is this by design and is there any way to make it logout a user with a GET request?
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about", "/password").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated().and()
.formLogin()
.loginPage("/login")
.permitAll();
}
This is intentional and is documented within the CSRF documentation. The reason is to prevent CSRF attacks that forcibly log users out of your application. If you would like to support non-POST requests you can do so with the following Java Configuration:
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
You can also find information about configuring log out on the Javadoc of the LogoutConfigurer (i.e. the object returned by the http.logout() method).