I just spent the last month crawling through Spring Security 3.1. The more I wanted to customise how authentication was handled, the worse things got. And then I hit this.
I have a simple ROLE-->permissions authorisation model. That is to say, a ROLE_manager would have several permissions assigned to it: PERM_create, PERM_delete, PERM_update, and PERM_read, whereas ROLE_reader would only have PERM_read assigned to it. Pretty simple.
For @PreAuthorize annotations, I was able to override the standard behaviour of the namespace configuration with a combination of a custom AccessDecisionManager injected with a custom RoleVoter, which operate on custom GrantedAuthority objects. The custom GrantedAuthority objects would contain a single Role object.
<bean id="almanacAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<bean id="webExpressionVoter" class="org.springframework.security.web.access.expression.WebExpressionVoter" />
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean id="almanacRoleVoter" class="com.facets.w101.almanac.spring.sec.AlmanacRoleVoter">
<constructor-arg name="masterRoleShelf" ref="masterRoleShelf" />
So, in my custom RoleVoter, I was now able to receive a PreAuthorise declaration like this:
<custom-filter ref="almanacUsrPwdAuthProcFilter" position="FORM_LOGIN_FILTER" />
<custom-filter ref="almanacAnonAuthFilter" position="ANONYMOUS_FILTER"/>
<anonymous enabled="false" />
... and the role voter searched through the authenticated user's roles to see if any of them contained the PERM_read permission. With that done, I turned to the namespace intercept-url entries. It looks to me that there is no way to customize the way the WebExpressionVoter handles the "hasRole" expressions.
I found that SecurityExpressionRoot is the class that handles the final role comparison, and someone decided to make all the relevant methods either final or private. The first place you would implement the same custom logic for URL security would be the hasRole and hasAnyRole methods. They are final. The next place you would rig this would be the getAuthoritySet method. That's private. How about a custom RoleHierarchy? My custom GrantedAuthority class encapsulates a single Role object, since Granted Authority can only return a single String. Well, because RoleHierarchy has to return Collection<? extends GrantedAuthority>, the only thing I could really do is to modify my GrantedAuthority to represent both my Role and Permission classes, which breaks the "assign only roles to authenticated entities" convention and would probably cause problems later.
I can't/won't override the DefaultWebSecurityExpressionHandler because there is too much functionality that I would have to re-implement or rewrite, and the same goes even more for the WebExpressionVoter. That I can't just override the SecurityExpressionRoot (and inject it via a custom AbstractSecurityExpressionHandler) is annoying to say the least after getting this far.
What are my options here? Do I just forgo fine-grained control for URLs? Is there a way to do this that I haven't found yet? Anyone got any ideas?