I implemented JWT with Spring Security.
Spring Security /login url returns a JWT which contains a role but when I try to access a URL which requires a role, it returns 403.
"timestamp": 1507840896561,
"status": 403,
"error": "Forbidden",
"message": "Access is denied",
"path": "/teacher/dashboard"
I defined a role for /teacher/** like this in WebSecurityConfig which extends WebSecurityConfigurerAdapter:
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/teacher/**").hasRole("TEACHER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
http
.csrf().disable();
http
.formLogin()
.defaultSuccessUrl("/", true)
.and()
.addFilterBefore(new SimpleCorsFilter(), ChannelProcessingFilter.class)
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
I tried to set the parameter for hasRole() as ROLE_TEACHER and Spring Security warned me not to use ROLE_prefix. I also tried hasAuthority("TEACHER") and again got 403`.
Payload of my JWT:
{
"sub": "org.springframework.security.core.userdetails.User#394ca6ef: Username: teacher#postman.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_TEACHER",
"exp": 1508704641
}
Token has Granted Authorities: ROLE_TEACHER but I keep getting access denied error. Am I missing something or is there any other implementation for defining roles for urls?
Change your http config like that and try again,
.antMatchers("/teacher/**").access("hasAnyRole('TEACHER')")
hope this will work fine.
You can also check role in method level security using
#PreAuthorize("hasRole('ROLE_TEACHER')")
To use #PreAuthorize you need to enabled it first. To do that extends your MethodSecurityConfig class with GolbalMethodSecurityConfiguration and add a annotation
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
prePostEnabled = true enables your application to authorize before (pre) and after (post) basis. Its your turn which one is suitable for you.
Related
My Spring Webflux application provides multiple authentication methods for the APIs, the user either presents a JWT token or he presents a userid and password. I understand that each authentication method is a separate SecurityWebFilterChain. In my security config I defined 2 Beans, one for basic auth and one for JWT. Setting up each one for different endpoints works fine using a SecurityMatcher, but how do I setup both for the same endpoint. I want either basic auth or JWT token to authenticate for a specific endpoint. All my attempts result in the first authentication method failing and returning a 401 unauthorized without attempting to try the second method. How do I get it not to fail but to try the second SecurityWebFilterChain bean?
Here is the code from my security config
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
#Autowired private SecurityContextRepository securityContextRepository;
#Bean
SecurityWebFilterChain webHttpSecurity(
ServerHttpSecurity http, BasicAuthenticationManager authenticationManager) {
http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/something/**"))
.authenticationManager(authenticationManager)
.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated())
.httpBasic()
.and()
.csrf()
.disable();
return http.build();
}
#Bean
SecurityWebFilterChain springWebFilterChain(
ServerHttpSecurity http, AuthenticationManager authenticationManager) {
String[] patterns =
new String[] {
"/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/v3/api-docs/**",
"/webjars/**",
};
return http.cors()
.disable()
.exceptionHandling()
.authenticationEntryPoint(
(swe, e) ->
Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)))
.accessDeniedHandler(
(swe, e) ->
Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
.and()
.csrf()
.disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(patterns)
.permitAll()
.pathMatchers(HttpMethod.OPTIONS)
.permitAll()
.anyExchange()
.authenticated()
.and()
.build();
}
The first Bean sets up basic auth for one specific endpoint using a custom authentication manager which veruifies the userid and password, the second bean sets up JWT auth for all other endpoints (with a custom AuthenticationManager that verifies the token etc.) except those that are excluded. Lets say I have the following endpoints
/api/something
/api/whatever
.....
endpoint 1 I want to authenticate with either basic auth or JWT
endpoint 2,3 ,n I want only JWT
As I have it now endpoint 1 is using only basicAuth and all other endpoints use JWT. How can I add JWT to endpoint 1 as well?
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
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;
}
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();
}