Example of Custom Spring Security Authority Control management in java
- 2020-06-07 04:28:44
- OfStack
Background description
In the project, we need to make fine-grained permission control to url + httpmethod (meeting restful, for example: https://... /xxx/users/1, some roles can only be viewed (HTTP GET), not added or deleted (POST, PUT, DELETE).
Table design
For the avoidance of doubt, only the key fields to be used are listed, and the rest is up to your imagination.
1.admin_user Administrator User table, key fields (id, role_id).
2.t_role role table, key field (id, privilege_id)
3.t_privilege permissions table, key fields (id, url, method)
The relationship between the three tables is not necessary to say, look at the field 1 can see.
Pre-implementation analysis
We can think backwards:
The most critical step to achieving our requirements is to have AccessDecisionManager of Spring Security determine whether the requested url + httpmethod matches the configuration in our database. However, AccessDecisionManager did not determine the relevant Voter with similar requirements, so we need to customize an implementation of Voter (the default registered AffirmativeBased's policy is to pass as long as Voter votes ACCESS_GRANTED, which also meets our requirements). When voter is implemented, there is one key parameter (Collection)
To summarize the following steps:
1. Custom implementation of voter.
2. Custom ConfigAttribute implementation.
3. Customize SecurityMetadataSource implementation.
Authentication includes user instances (which, needless to say, everyone has already done).
5. Custom GrantedAuthority implementation.
The project of actual combat
1. Custom implementation of GrantedAuthority
UrlGrantedAuthority.java
public class UrlGrantedAuthority implements GrantedAuthority {
private final String httpMethod;
private final String url;
public UrlGrantedAuthority(String httpMethod, String url) {
this.httpMethod = httpMethod;
this.url = url;
}
@Override
public String getAuthority() {
return url;
}
public String getHttpMethod() {
return httpMethod;
}
public String getUrl() {
return url;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UrlGrantedAuthority target = (UrlGrantedAuthority) o;
if (httpMethod.equals(target.getHttpMethod()) && url.equals(target.getUrl())) return true;
return false;
}
@Override
public int hashCode() {
int result = httpMethod != null ? httpMethod.hashCode() : 0;
result = 31 * result + (url != null ? url.hashCode() : 0);
return result;
}
}
2. Custom authentication user instances
public class SystemUser implements UserDetails {
private final Admin admin;
private List<MenuOutput> menuOutputList;
private final List<GrantedAuthority> grantedAuthorities;
public SystemUser(Admin admin, List<AdminPrivilege> grantedPrivileges, List<MenuOutput> menuOutputList) {
this.admin = admin;
this.grantedAuthorities = grantedPrivileges.stream().map(it -> {
String method = it.getMethod() != null ? it.getMethod().getLabel() : null;
return new UrlGrantedAuthority(method, it.getUrl());
}).collect(Collectors.toList());
this.menuOutputList = menuOutputList;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.grantedAuthorities;
}
@Override
public String getPassword() {
return admin.getPassword();
}
@Override
public String getUsername() {
return null;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
public Long getId() {
return admin.getId();
}
public Admin getAdmin() {
return admin;
}
public List<MenuOutput> getMenuOutputList() {
return menuOutputList;
}
public String getSalt() {
return admin.getSalt();
}
}
3. Custom UrlConfigAttribute implementation
public class UrlConfigAttribute implements ConfigAttribute {
private final HttpServletRequest httpServletRequest;
public UrlConfigAttribute(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
@Override
public String getAttribute() {
return null;
}
public HttpServletRequest getHttpServletRequest() {
return httpServletRequest;
}
}
4. Custom SecurityMetadataSource implementation
public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
Set<ConfigAttribute> allAttributes = new HashSet<>();
ConfigAttribute configAttribute = new UrlConfigAttribute(request);
allAttributes.add(configAttribute);
return allAttributes;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}
5. Custom voter implementation
public class UrlMatchVoter implements AccessDecisionVoter<Object> {
@Override
public boolean supports(ConfigAttribute attribute) {
if (attribute instanceof UrlConfigAttribute) return true;
return false;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
@Override
public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
if(authentication == null) {
return ACCESS_DENIED;
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (ConfigAttribute attribute : attributes) {
if (!(attribute instanceof UrlConfigAttribute)) continue;
UrlConfigAttribute urlConfigAttribute = (UrlConfigAttribute) attribute;
for (GrantedAuthority authority : authorities) {
if (!(authority instanceof UrlGrantedAuthority)) continue;
UrlGrantedAuthority urlGrantedAuthority = (UrlGrantedAuthority) authority;
if (StringUtils.isBlank(urlGrantedAuthority.getAuthority())) continue;
// If the database's method Field is null , the default is that all methods are supported
String httpMethod = StringUtils.isNotBlank(urlGrantedAuthority.getHttpMethod()) ? urlGrantedAuthority.getHttpMethod()
: urlConfigAttribute.getHttpServletRequest().getMethod();
// with Spring Realized AntPathRequestMatcher Do a match so that we have in our database url It also support ant Styles are configured (for example: /xxx/user/** )
AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(urlGrantedAuthority.getAuthority(), httpMethod);
if (antPathRequestMatcher.matches(urlConfigAttribute.getHttpServletRequest()))
return ACCESS_GRANTED;
}
}
return ACCESS_ABSTAIN;
}
}
6. Custom FilterSecurityInterceptor implementation
public class UrlFilterSecurityInterceptor extends FilterSecurityInterceptor {
public UrlFilterSecurityInterceptor() {
super();
}
@Override
public void init(FilterConfig arg0) throws ServletException {
super.init(arg0);
}
@Override
public void destroy() {
super.destroy();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
super.doFilter(request, response, chain);
}
@Override
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return super.getSecurityMetadataSource();
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return super.obtainSecurityMetadataSource();
}
@Override
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
super.setSecurityMetadataSource(newSource);
}
@Override
public Class<?> getSecureObjectClass() {
return super.getSecureObjectClass();
}
@Override
public void invoke(FilterInvocation fi) throws IOException, ServletException {
super.invoke(fi);
}
@Override
public boolean isObserveOncePerRequest() {
return super.isObserveOncePerRequest();
}
@Override
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
super.setObserveOncePerRequest(observeOncePerRequest);
}
}
Profile key configuration
<security:http>
...
<security:custom-filter ref="filterSecurityInterceptor" before="FILTER_SECURITY_INTERCEPTOR" />
</security:http>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="daoAuthenticationProvider"/>
</security:authentication-manager>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<list>
<bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter" />
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter" />
<bean id="urlMatchVoter" class="com.mobisist.app.security.access.voter.UrlMatchVoter" />
</list>
</constructor-arg>
</bean>
<bean id="securityMetadataSource" class="com.mobisist.app.security.access.UrlFilterInvocationSecurityMetadataSource" />
<bean id="filterSecurityInterceptor"
class="com.mobisist.app.security.access.UrlFilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="securityMetadataSource" />
</bean>
So enjoy your Spring Security access control tour.