上篇《话说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. libtool: line 990: g++: command not found的解决

    yum -y install gcc+ gcc-c++

  2. Graphics平移缩放旋转(转载)+点睛

    点睛:可以进行多次旋转和平移,也就是平移以后再平移,旋转以后再旋转,有时候一次达不到要求,如果你想一次调整完美的话很麻烦,所以最好多次,上代码 private void btnTranslate_Cl ...

  3. CSS零基础学习笔记.

    酸菜记 之 CSS的零基础. 这篇是我自己从零基础学习CSS的笔记加理解总结归纳的,如有不对的地方,请留言指教, 学前了解: CSS中字母是不分大小写的; CSS文件可以使用在各种程序文件中(如:PH ...

  4. 【转】创建SVN仓库的步骤

    转载地址:http://www.cnblogs.com/ivan0626/p/3783053.html   今天在客户现场联调,两个开发人员之间的代码想用SVN来管理,所以就临时在本地机器上搭建一个S ...

  5. Android存储

    Android的四种数据存储方式: 1.SharedPrefrences 2.SQLite 3.Content Provider 4.File SharedPrefrences: 1.是一种轻型的数据 ...

  6. Manthan, Codefest 16 -A Ebony and Ivory

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  7. android 命名 数组 所有国家 String[] COUNTRIES

    static final String[] COUNTRIES = new String[] { "Afghanistan", "Albania", " ...

  8. 微信小程序-画布组件

    canvas 画布. 注: canvas 标签默认宽度300px.高度225px 同一页面中的 canvas-id 不可重复,如果使用一个已经出现过的 canvas-id,该 canvas 标签对应的 ...

  9. 简单播放器(增加sdl事件控制)

    #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscal ...

  10. 修改shell提示符的显示格式

    默认的提示符显示方式如下: [root@localhost foldername]# 由于太长,希望能缩短到: [foldername]# 修改方式: # vim /etc/bashrc 找到第41行 ...