自定义Spring Security权限控制管理(实战篇)
上篇《话说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;
}
@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权限控制之旅吧。
欢迎访问我的个人博客:
自定义Spring Security权限控制管理(实战篇)的更多相关文章
- Spring Security权限控制
Spring Security官网 : https://projects.spring.io/spring-security/ Spring Security简介: Spring Security是一 ...
- ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理
在前面两篇随笔<ABP开发框架前后端开发系列---(7)系统审计日志和登录日志的管理>和<ABP开发框架前后端开发系列---(8)ABP框架之Winform界面的开发过程>开始 ...
- Spring Security学习笔记-自定义Spring Security过滤链
Spring Security使用一系列过滤器处理用户请求,下面是spring-security.xml配置文件. <?xml version="1.0" encoding= ...
- 自定义Spring Security的身份验证失败处理
1.概述 在本快速教程中,我们将演示如何在Spring Boot应用程序中自定义Spring Security的身份验证失败处理.目标是使用表单登录方法对用户进行身份验证. 2.认证和授权(Authe ...
- spring security 权限框架原理
spring security 权限框架原理
- 话说Spring Security权限管理(源码)
最近项目需要用到Spring Security的权限控制,故花了点时间简单的去看了一下其权限控制相关的源码(版本为4.2). AccessDecisionManager spring security ...
- spring 的权限控制:security
下面我们将实现关于Spring Security3的一系列教程. 最终的目标是整合Spring Security + Spring3MVC 完成类似于SpringSide3中mini-web的功能. ...
- Spring Security 入门原理及实战
目录 从一个Spring Security的例子开始 创建不受保护的应用 加入spring security 保护应用 关闭security.basic ,使用form表单页面登录 角色-资源 访问控 ...
- 学习Spring Boot:(二十八)Spring Security 权限认证
前言 主要实现 Spring Security 的安全认证,结合 RESTful API 的风格,使用无状态的环境. 主要实现是通过请求的 URL ,通过过滤器来做不同的授权策略操作,为该请求提供某个 ...
随机推荐
- javascript实现九九乘法表
CSS代码部分: <style type="text/css"> table { width: 800px; height: 300px; border-collaps ...
- h5网页中使用打电话功能
如果需要在移动浏览器中实现拨打电话,发送email,美国服务器,调用sns等功能,移动手机WEB页面(HTML5)Javascript提供的接口是一个好办法. 采用url链接的方式,实现在Safari ...
- [TCPIP] IP路由表及选路 Note
TCP/IP IP路由表及选路 1.路由表信息 路由表一般包含信息:目的IP地址.下一站路由器的IP地址.标志. 为数据报传送指定的一个网络接口. 查看路由表信息mac-abeen:~ abeen$ ...
- 后端Apache获取前端Nginx反向代理的真实IP地址 (原创贴-转载请注明出处)
====================说在前面的话==================== 环境:前段Nginx是反向代理服务器:后端是Apache是WEB项目服务器 目的:让后端Apapche获取 ...
- Android 进程常驻----native保活5.0以下方案推演过程以及代码
正文: 今天继续昨天,一鼓作气,争取这个礼拜全部写完. 上一篇文章留了一个别人的github链接,他里面的native保活实现方案也是大多数公司采用的方案. 我们先来讲一下他的方案. 他是首先开启一个 ...
- Protobuf3 + Netty4: 在socket上传输多种类型的protobuf数据
Protobuf序列化的字节流数据是不能自描述的,当我们通过socket把数据发送到Client时,Client必须知道发送的是什么类型的数据,才能正确的反序列化它.这严重影响限制了C/S功能的实现, ...
- R语言多项式回归
含有x和y这两个变量的线性回归是所有回归分析中最常见的一种:而且,在描述它们关系的时候,也是最有效.最容易假设的一种模型.然而,有些时候,它的实际情况下某些潜在的关系是非常复杂的,不是二元分析所能解决 ...
- 建立docker私有库(docker registry)(转)
建立docker私有库(docker registry) 博客分类: docker 我的目标还是无互联网安装,部署内部的docker私有库,目前docker镜像的获得还是需要互联网,将下载好的do ...
- PSR-1:基本的代码风格
PHP标签 必须把PHP代码放在<?php ?>或<?= ?>标签中.不得使用其他PHP标签句法. 编码 所有PHP文件都必须使用UTF-8字符集编码,而且不能有字节顺序标记( ...
- qt creator 使用Ui文件的问题
一.显式地调用uic.exe 如何将UI文件生成头文件 1,将设计的UI文件拷贝到uic.exe的目录下Qt\Qt5.3.2\5.3\mingw482_32\bin. 2, 打开windows的CMD ...