Using
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
my example works fine. For example for
http.authorizeRequests()
// ...
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
If I have changed inMemoryAuthentication to spring jdbc default - i got an role issue than.
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
I sure I configured db and schema using spring recommendations (to be able to use default jdbc authentication).
In debug mode I can see result of loading from db in the
org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl
#loadUserByUsername(username)[line 208]
return createUserDetails(username, user, dbAuths);
It returns similar result with in memory configuration:
org.springframework.security.core.userdetails.User@183a3:
Username: dba;
Password: [PROTECTED];
Enabled: true;
AccountNonExpired: true;
credentialsNonExpired: true;
AccountNonLocked: true;
Granted Authorities: ADMIN,DBA
As you can see it loads correspond Granted Authorities, but http request redirects me to .accessDeniedPage("/Access_Denied"). I confused because It should work for user like time before.
I do not use spring boot in my project.
My logs does not contain any configuration of jdbc errors.
I have spend a lot of time to investigate details and my ideas have just finished.
Do you think I need add to build some cache libraries or something else?
There are 2 gotchas in play here.
The first is that when using hasRole('ADMIN')
that first a check is done if it starts with the role prefix (for which the default is ROLE_
) if not the passed in role is prefix with it (see also the reference guide). So in this case the actual authority checked is ROLE_ADMIN
and not ADMIN
as you expect/assume.
The second is that when using the in memory option the roles
method does the same as mentioned here. It checks if the passed in roles start with the role prefix and if not adds it. So in your sample with the in memory one you end up with authorities ROLE_ADMIN
and ROLE_DBA
.
However in your JDBC option you have authorities ADMIN
and DBA
and hence the hasRole('ADMIN')
check fails because ROLE_ADMIN
isn't equal to ADMIN
.
To fix you have several options.
- Instead of
hasRole
use hasAuthority
the latter doesn't add the role prefix and for the in memory option use authorities
instead of roles
.
- In the JDBC option prefix the authorities in the database with
ROLE_
- Set the default role prefix to empty.
Using hasAuthority
First change the configuration of the in memory database to use authorities
instead of roles
.
auth.inMemoryAuthentication()
.withUser("dba").password("root123")
.authorities("ADMIN","DBA");
next change your expressions as well
.antMatchers("/db/**").access("hasAuthority('ADMIN') and hasAuthority('DBA')")
Prefix with ROLE_
In the script that inserts the authorities prefix the authorities with ROLE_
.
Remove the default role prefix
This is a bit tricky and is extensivly described in [the migration guide].
There is no easy configuration option and requires a BeanPostProcessor
.
public class DefaultRolesPrefixPostProcessor implements BeanPostProcessor, PriorityOrdered {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// remove this if you are not using JSR-250
if(bean instanceof Jsr250MethodSecurityMetadataSource) {
((Jsr250MethodSecurityMetadataSource) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultMethodSecurityExpressionHandler) {
((DefaultMethodSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof DefaultWebSecurityExpressionHandler) {
((DefaultWebSecurityExpressionHandler) bean).setDefaultRolePrefix(null);
}
if(bean instanceof SecurityContextHolderAwareRequestFilter) {
((SecurityContextHolderAwareRequestFilter)bean).setRolePrefix("");
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public int getOrder() {
return PriorityOrdered.HIGHEST_PRECEDENCE;
}
}
You can see see what happened enabling the logging. In your application.properties
add:
# ==============================================================
# = Logging springframework
# ==============================================================
logging.level.org.springframework.jdbc=DEBUG
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.http=DEBUG