上篇《Spring Security权限管理(源码)杂谈》介绍了
Spring Security权限控制管理的源码及实现,然而某些情况下,它默认的实现并不能满足我们项目的实际需求,有时候需要做一些自己的实现,本次将围绕上次的内容进行一次项目实战。
实战背景
背景描述
项目中需要做细粒的权限控制,细微至url + httpmethod (满足restful,例如: https://.../xxx/users/1, 某些角色只能查看(HTTP GET), 而无权进行增改删(POST, PUT, DELETE))。
表设计
为避嫌,只列出要用到的关键字段,其余敬请自行脑补。
- admin_user 管理员用户表, 关键字段( id, role_id )。
- t_role 角色表, 关键字段( id, privilege_id )。
- t_privilege 权限表, 关键字段( id, url, method )
三个表的关联关系就不用多说了吧,看字段一眼就能看出。
实现前分析
我们可以逆向思考:
要实现我们的需求,最关键的一步就是让Spring Security的AccessDecisionManager来判断所请求的url + httpmethod 是否符合我们数据库中的配置。然而,AccessDecisionManager并没有来判定类似需求的相关Voter, 因此,我们
需要自定义一个Voter的实现(默认注册的AffirmativeBased的策略是只要有Voter投出ACCESS_GRANTED票,则判定为通过,这也正符合我们的需求)。实现voter后,有一个关键参数(Collection attributes),ConfigAttribute根据不同的情况,所代表的语义不一样。我们在此也需要实现。然而,Collection attributes参数由SecurityMetadataSource获取,因此,我们还应该实现SecurityMetadataSource。众所周知,在Spring Security中,当前用户认证信息都是通过Authentication表示,因此,我们还应该让Authentication包含用户(admin)实例。Authentication同时还包含了用户的权限信息(GrantedAuthority), 因此还应该实现GrantedAuthority。
总结一下思路步骤:
1.自定义voter实现。2.自定义ConfigAttribute实现。3.自定义SecurityMetadataSource实现。4.Authentication包含用户实例(这个其实不用说,大家应该都已经这么做了)。5.自定义GrantedAuthority实现。
项目实战
1.自定义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;}@Overridepublic String getAuthority() {return url;}public String getHttpMethod() {return httpMethod;}public String getUrl() {return url;}@Overridepublic 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;}@Overridepublic int hashCode() {int result = httpMethod != null ? httpMethod.hashCode() : 0;result = 31 * result + (url != null ? url.hashCode() : 0);return result;}}2.自定义认证用户实例
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;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.grantedAuthorities;}@Overridepublic String getPassword() {return admin.getPassword();}@Overridepublic String getUsername() {return null;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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.自定义UrlConfigAttribute实现
public class UrlConfigAttribute implements ConfigAttribute {
private final HttpServletRequest httpServletRequest;public UrlConfigAttribute(HttpServletRequest httpServletRequest) {this.httpServletRequest = httpServletRequest;}@Overridepublic String getAttribute() {return null;}public HttpServletRequest getHttpServletRequest() {return httpServletRequest;}}public class UrlFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {@Overridepublic 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;}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}}5.自定义voter实现
public class UrlMatchVoter implements AccessDecisionVoter<Object> {@Overridepublic boolean supports(ConfigAttribute attribute) {if (attribute instanceof UrlConfigAttribute) return true;return false;}@Overridepublic boolean supports(Class<?> clazz) {return true;}@Overridepublic 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;//如果数据库的method字段为null,则默认为所有方法都支持String httpMethod = StringUtils.isNotBlank(urlGrantedAuthority.getHttpMethod()) ? urlGrantedAuthority.getHttpMethod(): urlConfigAttribute.getHttpServletRequest().getMethod();//用Spring已经实现的AntPathRequestMatcher进行匹配,这样我们数据库中的url也就支持ant风格的配置了(例如:/xxx/user/**)AntPathRequestMatcher antPathRequestMatcher = new AntPathRequestMatcher(urlGrantedAuthority.getAuthority(), httpMethod);if (antPathRequestMatcher.matches(urlConfigAttribute.getHttpServletRequest()))return ACCESS_GRANTED;}}return ACCESS_ABSTAIN;}}6.自定义FilterSecurityInterceptor实现
public class UrlFilterSecurityInterceptor extends FilterSecurityInterceptor {public UrlFilterSecurityInterceptor() {super();}@Overridepublic void init(FilterConfig arg0) throws ServletException {super.init(arg0);}@Overridepublic void destroy() {super.destroy();}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {super.doFilter(request, response, chain);}@Overridepublic FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {return super.getSecurityMetadataSource();}@Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return super.obtainSecurityMetadataSource();}@Overridepublic void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {super.setSecurityMetadataSource(newSource);}@Overridepublic Class<?> getSecureObjectClass() {return super.getSecureObjectClass();}@Overridepublic void invoke(FilterInvocation fi) throws IOException, ServletException {super.invoke(fi);}@Overridepublic boolean isObserveOncePerRequest() {return super.isObserveOncePerRequest();}@Overridepublic void setObserveOncePerRequest(boolean observeOncePerRequest) {super.setObserveOncePerRequest(observeOncePerRequest);}}配置文件关键配置
<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>好啦,接下来享受你的Spring Security权限控制之旅吧。
更多Spring Security相关教程见以下内容:Spring Security 学习笔记 http://www.linuxidc.com/Linux/2016-10/135820.htmSpring Security3.1高级详细开发指南 PDF http://www.linuxidc.com/Linux/2016-05/131482.htmSpring Security 学习之数据库认证 http://www.linuxidc.com/Linux/2014-02/97407.htmSpring Security 学习之LDAP认证 http://www.linuxidc.com/Linux/2014-02/97406.htmSpring Security 学习之OpenID认证 http://www.linuxidc.com/Linux/2014-02/97405.htmSpring Security 学习之X.509认证 http://www.linuxidc.com/Linux/2014-02/97404.htmSpring Security 学习之HTTP基本认证和HTTP摘要认证 http://www.linuxidc.com/Linux/2014-02/97403.htmSpring Security 学习之HTTP表单验证 http://www.linuxidc.com/Linux/2014-02/97402.htmSpring Security异常之You must provide a configuration attribute http://www.linuxidc.com/Linux/2015-02/113364.htm
Spring Security 的详细介绍:请点这里
Spring Security 的下载地址:请点这里
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-12/138096.htm