上篇《话说Spring Security权限管理(源码)》介绍了Spring Security权限控制管理的源码及实现,然而某些情况下,它默认的实现并不能满足我们项目的实际需求,有时候需要做一些自己的实现,本次将围绕上次的内容进行一次项目实战。

实战背景

背景描述

项目中需要做细粒的权限控制,细微至url + httpmethod (满足restful,例如: https://.../xxx/users/1, 某些角色只能查看(HTTP GET), 而无权进行增改删(POST, PUT, DELETE))。

表设计

为避嫌,只列出要用到的关键字段,其余敬请自行脑补。

  1. admin_user 管理员用户表, 关键字段( id, role_id )。
  2. t_role 角色表, 关键字段( id, privilege_id )。
  3. 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;
} @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.自定义认证用户实例

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.自定义UrlConfigAttribute实现

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.自定义SecurityMetadataSource实现

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.自定义voter实现

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;
//如果数据库的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();
} @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);
}
}

配置文件关键配置

<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权限控制之旅吧。


欢迎访问我的个人博客:

www.javafan.cn

自定义Spring Security权限控制管理(实战篇)的更多相关文章

  1. Spring Security权限控制

    Spring Security官网 : https://projects.spring.io/spring-security/ Spring Security简介: Spring Security是一 ...

  2. ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理

    在前面两篇随笔<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>和<ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程>开始 ...

  3. Spring Security学习笔记-自定义Spring Security过滤链

    Spring Security使用一系列过滤器处理用户请求,下面是spring-security.xml配置文件. <?xml version="1.0" encoding= ...

  4. 自定义Spring Security的身份验证失败处理

    1.概述 在本快速教程中,我们将演示如何在Spring Boot应用程序中自定义Spring Security的身份验证失败处理.目标是使用表单登录方法对用户进行身份验证. 2.认证和授权(Authe ...

  5. spring security 权限框架原理

    spring security 权限框架原理

  6. 话说Spring Security权限管理(源码)

    最近项目需要用到Spring Security的权限控制,故花了点时间简单的去看了一下其权限控制相关的源码(版本为4.2). AccessDecisionManager spring security ...

  7. spring 的权限控制:security

    下面我们将实现关于Spring Security3的一系列教程. 最终的目标是整合Spring Security + Spring3MVC 完成类似于SpringSide3中mini-web的功能. ...

  8. Spring Security 入门原理及实战

    目录 从一个Spring Security的例子开始 创建不受保护的应用 加入spring security 保护应用 关闭security.basic ,使用form表单页面登录 角色-资源 访问控 ...

  9. 学习Spring Boot:(二十八)Spring Security 权限认证

    前言 主要实现 Spring Security 的安全认证,结合 RESTful API 的风格,使用无状态的环境. 主要实现是通过请求的 URL ,通过过滤器来做不同的授权策略操作,为该请求提供某个 ...

随机推荐

  1. Wpf 中的DataGrid的Header属性,动态bind时不起作用

    在使用wpf开发软件时,有使用到DataGrid,DataGridTextColumn的Header 属性使用DynamicResource binding,在修改绑定数据源时,header并没有更新 ...

  2. hibernate缓存机制详细分析 复制代码 内部资料 请勿转载 谢谢合作

    您可以通过点击 右下角 的按钮 来对文章内容作出评价, 也可以通过左下方的 关注按钮 来关注我的博客的最新动态. 如果文章内容对您有帮助, 不要忘记点击右下角的 推荐按钮 来支持一下哦 如果您对文章内 ...

  3. 自定义控件(View的绘制流程源码解析)

    参考声明:这里的一些流程图援引自http://a.codekk.com/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7% ...

  4. SpringMVC框架下的异常处理

    在eclipse的javaEE环境下:导包.... 1. 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象 2. @Excep ...

  5. EntityFramework Core 学习笔记 —— 添加主键约束

    原文地址:https://docs.efproject.net/en/latest/modeling/keys.html Keys (primary) Key 是每个实体例的主要唯一标识.EF Cor ...

  6. HTML 链接 - href

    链接 在HTML的学习中,链接的标签发挥着很大的作用,HTML 使用超级链接与网络上的另一个文档相连.几乎可以在所有的网页中找到链接.点击链接可以从一张页面跳转到另一张页面. 比如说:实例 创建超级链 ...

  7. [LeetCode_2] Add Two Numbers

    LeetCode: 2. Add Two Numbers /** * Definition for singly-linked list. * struct ListNode { * int val; ...

  8. Linux术语全称

    bin = BINaries (binary) /dev = devices /etc = ETCetera etcetera附加的人, 附加物, 以及其它, 等等 /lib = LIBrary /p ...

  9. 设计一个泛型类orderedCollection

    设计一个泛型类orderedCollection,它存储的Comparable对象的集合(在数组中),以及该集合的当前大小.提供public方法isEmpty,makeEmpty,insert,rem ...

  10. Apache配置文件讲解

        持续作用扩展自 HTTP/1.0 和 HTTP/1.1 的长连接特性.提供了长效的 HTTP 会话,用以在同 一个 TCP 连接中进行多次请求. 在某些情况下, 这样的方式会对包含大量图片的 ...