Issue
I am not so into Spring Security and JWT token and I have the following doubt on a project which I am working on.
Basically I have this SecurityConfiguration class containing my Spring Security configuration, as you can see it is intended to handle JWT token:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
private UserDetailsService userDetailsService;
@Autowired
private JwtConfig jwtConfig;
@Autowired
private JwtTokenUtil jwtTokenUtil;
private static final String[] USER_MATCHER = { "/api/user/email/**"};
private static final String[] ADMIN_MATCHER = { "/api/users/email/**"};
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
/*
* NOTE:
* Using hasRole expects the authority names to start with 'ROLE_' prefix
* Instead, we use hasAuthority as we can use the names as it is
*/
http.csrf().disable()
.authorizeRequests()
//.antMatchers(USER_MATCHER).hasAnyAuthority("USER")
.antMatchers(USER_MATCHER).hasAnyAuthority("CLIENT")
.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")
.antMatchers("/api/users/test").authenticated()
.antMatchers(HttpMethod.POST, jwtConfig.getUri()).permitAll()
.anyRequest().denyAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(
new TokenVerificationFilter(authenticationManager(), jwtConfig, jwtTokenUtil),UsernamePasswordAuthenticationFilter.class);
}
/* To allow Pre-flight [OPTIONS] request from browser */
@Override
public void configure(WebSecurity web)
{
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
@Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
};
}
As you can see in the previous code I have the following two matcher lists:
private static final String[] USER_MATCHER = { "/api/user/email/**"};
private static final String[] ADMIN_MATCHER = { "/api/users/email/**"};
At the moment the both contains the same API path: /api/users/email/**. This because my original idea was that this API should be avaiable for simple users and admin users.
Then in the code you can find the following matcher definition based on the authorities contained into the generated token:
.antMatchers(USER_MATCHER).hasAnyAuthority("CLIENT")
.antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")
(the USER_MATCHER is related to the CLIENT authority that, at the moment, is the simplest type of operation that can be performed...please don't pay too much attention to the authority's name...these are mainly some example then I will better define my authority list).
So doing in this way I expected that this /api/users/email/ API must be enabled both for an user having the ADMIN authority but also for an user having the CLIENT authority.
But it seems not to be true, doing an example. I generate a token for an user having the ADMIN authority, something like this:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ4eHhAZ21haWwuY29tIiwibmFtZSI6IlJlbmF0byBudWxsIEdpYWxsaSIsInVzZXJfcHJvZmlsZXMiOlsiQURNSU4iXSwiZXhwIjoxNjQwMjg2NTY5LCJpYXQiOjE2NDAyMDAxNjksImF1dGhvcml0aWVzIjpbIkFETUlOIl19.WQGYKbo_ihrV2Nu1RlxrCweBpgU-Y-dNh9L6R9vrj3vhTyvbPlsyzWNPe8ljtP6WZ8_Vvv8FUDJIa6y5BLS1SA
using https://jwt.io/ website you can see that this token have the ADMIN authority:
{
"sub": "[email protected]",
"name": "Renato null Gialli",
"user_profiles": [
"ADMIN"
],
"exp": 1640286569,
"iat": 1640200169,
"authorities": [
"ADMIN"
]
}
So I use this token to call my target API (/api/users/email/) and I am obtaining what I expect:
Ok, now I generate a brand new JWT token for another user of my system having only the CLIENT authority. It generate something like this:
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ5eXlAZ21haWwuY29tIiwibmFtZSI6Ik1hcmlvIG51bGwgUm9zc2kiLCJ1c2VyX3Byb2ZpbGVzIjpbIkNMSUVOVCJdLCJleHAiOjE2NDAyODY5MzEsImlhdCI6MTY0MDIwMDUzMSwiYXV0aG9yaXRpZXMiOlsiQ0xJRU5UIl19.MYM6J3bGSK2PJvxPpi01BHjbcyOiONegYlbZ--lfEtg3p6Hw91acyKYC7KADC2KcJgcXnICJGmLTkPcrVIpfEw
As usual, using https://jwt.io/, I can check that it contains this authority and infact here it is:
{
"sub": "[email protected]",
"name": "Mario null Rossi",
"user_profiles": [
"CLIENT"
],
"exp": 1640286931,
"iat": 1640200531,
"authorities": [
"CLIENT"
]
}
So now I use this newtoken to call my target API (/api/users/email/) but the API is not accessible by this user:
As you can see using this token the API access seems to be forbidden.
Why if in my configuration I specified that the API defined into the USER_MATCHER list (so the previous target API) should be accessible also by the user having a token containing the CLIENT authority?
What is wrong? Or what am I missing in the authority definition logic?
Solution
it sounds like you would like the /api/users/email/
endpoint to be accessible by both CLIENT & ADMIN
instead of .antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN")
try .antMatchers(ADMIN_MATCHER).hasAnyAuthority("ADMIN", "CLIENT)
Answered By - indybee