Reputation: 557
First of all, I am new to Java Spring Framework. So forgive me if I did not provide enough info. I have tried to add RoleHierarchy into my app but it did not work. Below are the codes I have tried.
SecurityConfig.java
// These config is try to set up a user Role Hierarchy
@Bean
public RoleHierarchy roleHierarchy() {
System.out.println("arrive public RoleHierarchy roleHierarchy()");
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy("ROLE_ADMIN > ROLE_STAFF");
r.setHierarchy("ROLE_STAFF > ROLE_USER");
r.setHierarchy("ROLE_DEVELOPER > ROLE_USER");
r.setHierarchy("ROLE_USER > ROLE_GUEST");
return r;
}
@Bean
public AffirmativeBased defaultAccessDecisionManager(RoleHierarchy roleHierarchy){
System.out.println("arrive public AffirmativeBased defaultAccessDecisionManager()");
List<AccessDecisionVoter> decisionVoters = new ArrayList<>();
// webExpressionVoter
WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
DefaultWebSecurityExpressionHandler
expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
webExpressionVoter.setExpressionHandler(expressionHandler);
decisionVoters.add(webExpressionVoter);
decisionVoters.add(roleHierarchyVoter(roleHierarchy));
// return new AffirmativeBased(Arrays.asList((AccessDecisionVoter) webExpressionVoter));
return new AffirmativeBased(decisionVoters);
}
@Bean
public RoleHierarchyVoter roleHierarchyVoter(RoleHierarchy roleHierarchy) {
System.out.println("arrive public RoleHierarchyVoter roleHierarchyVoter");
return new RoleHierarchyVoter(roleHierarchy);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// skipping some codes
http
// skipping some codes
.accessDecisionManager(defaultAccessDecisionManager(roleHierarchy()))
// skipping some codes
}
MethodSecurityConfig.java
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Inject
private SecurityConfig securityConfig;
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return securityConfig.authenticationManagerBean();
}
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
System.out.println("arrive protected MethodSecurityExpressionHandler createExpressionHandler()");
DefaultMethodSecurityExpressionHandler d = new DefaultMethodSecurityExpressionHandler();
d.setRoleHierarchy(securityConfig.roleHierarchy());
return d;
}
}
And I have a UserDetailsServiceImpl implements UserDetailsService
that provide the principal
, Authentication
and GrantedAuthority
Finally I have some APIs:
@PreAuthorize("hasRole('ROLE_STAFF')")
@RequestMapping(value = "/api/v1/contactUs", method = RequestMethod.GET)
@PreAuthorize("hasRole('ROLE_DEVELOPER')")
@RequestMapping(value = "/api/v1/system", method = RequestMethod.GET)
The problem is now if I login as ROLE_STAFF, ROLE_DEVELOPER, ROLE_ADMIN, I got the following result.
| API | ROLE_STAFF | ROLE_DEVELOPER | ROLE_ADMIN |
|-----------|------------|----------------|------------|
| contactUs | 200 | 403 | 403 |
| system | 403 | 200 | 403 |
As you can see ROLE_STAFF
and ROLE_DEVELOPER
work just fine. But I want ROLE_ADMIN
as a super role of both and it didn't work.
FYI, I am using spring-security 3.2.5.RELEASE
Upvotes: 12
Views: 18211
Reputation: 794
Correct format is: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
see doc.
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy(UserRole.ROLE_HIERARCHY);
return roleHierarchy;
}
Upvotes: 0
Reputation: 256
Note: The accepted answer won't work in the newest version of Spring security (I think since release 5.2.1). This is because the 'and' (ROLE_1 > ROLE_2 and ROLE_2 > ROLE_3) notation was never an official standard. You could have written every word instead of 'and' and it would still work the same in the past versions.
Instead, in the new version you should now use '\n' (new line), e.g. ROLE_1 > ROLE_2\nROLE2 > ROLE_3 ...
Upvotes: 13
Reputation: 1898
For me the solution was having proper bean name for the instance of DefaultWebSecurityExpressionHandler
. The name should be webSecurityExpressionHandler
.
@Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy(Roles.getRoleHierarchy());
return roleHierarchy;
}
@Bean
public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy());
return expressionHandler;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.expressionHandler(webSecurityExpressionHandler())
...
}
Upvotes: 4
Reputation: 2235
Override the createExpressionHandler
method so it returns a configured global expression handler
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private RoleHierarchy roleHierarchy;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler(){
return methodSecurityExpressionHandler();
}
private DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(){
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
@Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_OWNER > ROLE_USER");
return roleHierarchy;
}
@Bean
public RoleHierarchyVoter roleVoter() {
return new RoleHierarchyVoter(roleHierarchy);
}
@Configuration
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {}
}
}
Upvotes: 1
Reputation: 41
I don't use Spring RoleHierarchy - because it doea not work for me. But Ussualy I do like this: Define Role interface
public static interface Role {
String getName();
List<String> getHierarchy();
}
List of my roles (to store in DB):
public interface AuthStates {
// Spring security works fine only with ROLE_*** prefix
String ANONYMOUS = "ROLE_ANONYMOUS";
String AUTHENTICATED = "ROLE_AUTHENTICATED";
String ADMINISTRATOR = "ROLE_ADMINISTRATOR";
}
Define Anonymous role as basic role class:
public static class Anonymous implements Role {
private final String name;
private final List<String> hierarchy = Lists.newArrayList(ANONYMOUS);
public Anonymous() {
this(ANONYMOUS);
}
protected Anonymous(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public List<String> getHierarchy() {
return hierarchy;
}
protected void addHierarchy(String name) {
hierarchy.add(name);
}
}
Define Authenticated role (common user role):
public static class Authenticated extends Anonymous {
public Authenticated() {
this(AUTHENTICATED);
}
protected Authenticated(String name) {
super(name);
addHierarchy(AUTHENTICATED);
}
}
Define Administrator role (at the top of the evolution):
public static class Administrator extends Authenticated {
public Administrator() {
this(ADMINISTRATOR);
}
protected Administrator(String name) {
super(name);
addHierarchy(ADMINISTRATOR);
}
}
Optional - static factory class:
public static Role getRole(String authState) {
switch (authState) {
case ANONYMOUS: return new Anonymous();
case AUTHENTICATED: return new Authenticated();
case ADMINISTRATOR: return new Administrator();
default: throw new IllegalArgumentException("Wrong auth state");
}
}
In my CustomUserDetailsService (which implements UserDetailsService) I use role like this:
private Collection<GrantedAuthority> createAuthority(User user) {
final List<GrantedAuthority> authorities = new ArrayList<>();
AuthStates.Role userAuthState = AuthStates.getRole(user.getAuthState());
for (String role : userAuthState.getHierarchy()) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
In controllers:
@PreAuthorize("hasRole('ROLE_AUTHENTICATED')")
Will allow user logged in as ROLE_AUTHENTICATED and ROLE_ADMINISTRATOR both.
Upvotes: -2
Reputation: 1720
Everytime I want to implement a hierarchy of roles with Spring Security and Java config, I use the following approach:
We have to add a RoleHierarchyImpl bean into context (You see, that I use multiple roles to build a hierarchy):
@Bean
public RoleHierarchyImpl roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_DBA ROLE_DBA > ROLE_USER ");
return roleHierarchy;
}
Then we need to create web expression handler to pass obtained hierarchy to it:
private SecurityExpressionHandler<FilterInvocation> webExpressionHandler() {
DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy());
return defaultWebSecurityExpressionHandler;
}
The final step is to add expressionHandler into http.authorizeRequests():
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.expressionHandler(webExpressionHandler())
.antMatchers("/admin/**").access("(hasRole('ROLE_ADMIN') or hasRole('ROLE_DBA')) and isFullyAuthenticated()")
.antMatchers("/dba").access("hasRole('ROLE_DBA') and isFullyAuthenticated()")
.antMatchers("/dba/**").access("hasRole('ROLE_USER')")
.and()
.requiresChannel()
.antMatchers("/security/**").requiresSecure()
.anyRequest().requiresInsecure()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?auth=fail")
.usernameParameter("username")
.passwordParameter("password")
.defaultSuccessUrl("/admin")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.invalidateHttpSession(true)
.logoutSuccessUrl("/index")
.permitAll()
.and()
.csrf()
.and()
.rememberMe().tokenValiditySeconds(1209600)
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.anonymous().disable()
.addFilter(switchUserFilter());
}
Result: in this particular example we try to visit /dba section after we have logged in using admin user (ROLE_ADMIN). Before we created a hierarchy, we had an access denied result, but now we can visit this section without any problems.
Upvotes: 21
Reputation: 557
The issue is in the RoleHierachy, which should be like this:
@Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl r = new RoleHierarchyImpl();
r.setHierarchy("ROLE_ADMIN > ROLE_STAFF and ROLE_ADMIN > ROLE_DEVELOPER and ROLE_STAFF > ROLE_USER and ROLE_DEVELOPER > ROLE_USER");
return r;
}
keep calling setHierarchy()
will override the setting before
Upvotes: 21