This is about Spring
v.4 (MVC + Security). I have implemented UserDetailsServiceImpl
, where inside the loadUserByUsername
method a user is granted with its authorities. Let's say it's simply:
public UserDetails loadUserByUsername(String username) {
...
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ADMIN"));
return new org.springframework.security.core.userdetails.User(username, password, enabled, true, true, true, authorities);
...
}
And there is a security controller inside which there is an annotated method with the @Secured
annotation:
@Secured("ADMIN")
@RequestMapping(value = "/users", method = RequestMethod.GET)
public String users(Model model ...) { ... }
As you can see inside the loadUserByUsername
method I explicitly granted the ADMIN
role to the user.
But when I'm trying access the /users
I get an Access is denied
exception:
2016-04-19 10:25:16,899 DEBUG (http-nio-8080-exec-9) [org.springframework.security.web.access.ExceptionTranslationFilter] - Access is denied (user is not anonymous); delegating to AccessDeniedHandler org.springframework.security.access.AccessDeniedException: Access is denied at org.springframework.security.access.vote.AbstractAccessDecisionManager.checkAllowIfAllAbstainDecisions(AbstractAccessDecisionManager.java:70) at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:88) at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232) at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ...
(without the @Secured
annotation everything works fine).
What have I missed here?
Using @Secured Annotation. The @Secured annotation is used to specify a list of roles on a method. So, a user only can access that method if she has at least one of the specified roles.
Role as Authority Similarly, in Spring Security, we can think of each Role as a coarse-grained GrantedAuthority that is represented as a String and prefixed with “ROLE“. When using a Role directly, such as through an expression like hasRole(“ADMIN”), we are restricting access in a coarse-grained manner.
Method-level security is implemented by placing the @PreAuthorize annotation on controller methods (actually one of a set of annotations available, but the most commonly used). This annotation contains a Spring Expression Language (SpEL) snippet that is assessed to determine if the request should be authenticated.
@Secured and @RolesAllowed are the same the only difference is @RolesAllowed is a standard annotation (i.e. not only spring security) whereas @Secured is spring security only. @PreAuthorize is different in a way that it is more powerful then the other 2. It allows for SpEL expression for a more fine-grained control.
Surprisingly, but the problem was with the roles names. Due to the defaultRolePrefix
set to the ROLE_
(see org.springframework.security.access.vote.RoleVoter
class) all roles should have names starting with the ROLE_
prefix. In other words, when I've changed
authorities.add(new SimpleGrantedAuthority("ADMIN"));
to
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
and @Secured("ADMIN")
to @Secured("ROLE_ADMIN")
- everything became fine.
I also faced this issue. Spring Security 4 automatically prefixes any role with ROLE_
. Refer to the Spring Security 3.x to 4.x migration guide for more details.
ROLE_
prefixingAs suggested in the migration guide, the following BeanPostProcessor
can be used to disable the automatic ROLE_
prefixing:
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;
}
}
Then define it as a bean:
@Bean
public static DefaultRolesPrefixPostProcessor defaultRolesPrefixPostProcessor() {
return new DefaultRolesPrefixPostProcessor();
}
Alternatively you can check the authorities using the @PreAuthorize
annotation:
@PreAuthorize("hasAuthority('ADMIN')")
Ensure that prePostEnabled
is enabled:
@EnableGlobalMethodSecurity(prePostEnabled = true)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With