本文转自:https://www.cnblogs.com/weilu2/p/springsecurity_custom_decision_metadata.html

本文在SpringMVC和MyBatis项目框架的基础上整合Spring Security作为权限管理。并且完全实现一套自定义的权限管理规则。

1.权限管理
在本例中所使用的权限管理的思路如下图所示,在系统中存在着许多帐号,同时存在着许多资源,在一个Web系统中一个典型的资源就是访问页面的URL,控制了这个就能够直接控制用户的访问权。

由于资源非常多,直接针对资源与用户进行设置关系会比较繁琐,因此针对同一类或者同一组的资源打个包,称为一组权限,这样将权限分配给用户的时候,一组权限中的资源也就都分配给用户了。

这个只是一个非常简单的权限管理方案,并且只能适用于较小的项目,因为此处给出这个只是为了便于理解自定义的Spring Security认证规则。

2.Spring Security的认证规则
要编写自定义的认证规则,首先需要对Spring Security中的认证规则有一定的了解,下面简单介绍下Spring Security的认证规则。

1)在Spring Security中每个URL都是一个资源,当系统启动的时候,Spring Security会根据配置将所有的URL与访问这个URL所需要的权限的映射数据加载到Spring Security中。

2)当一个请求访问一个资源时,Spring Security会判断这个URL是否需要权限验证,如果不需要,那么直接访问即可。

3)如果这个URL需要进行权限验证,那么Spring Security会检查当前请求来源所属用户是否登录,如果没有登录,则跳转到登录页面,进行登录操作,并加载这个用户的相关信息

4)如果登录,那么判断这个用户所拥有的权限是否包含访问这个URL所需要的权限,如果有则允许访问

5)如果没有权限,那么就给出相应的提示信息

3.自定义认证规则思路
根据上面一小节介绍的Spring Security认证的过程,我们相应的就能够分析出对于这个过程我们如果要修改的话,需要进行哪些方面的改动。

3.1.自定义SecurityMetadataSource
在Spring Security中的 SecurityMetadataSource 处于上面的步骤一中,也就是用于加载URL与权限对应关系的,对于这个我们需要自己进行定义

package com.oolong.customsecurity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set; import org.apache.log4j.LogManager;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component; /**
* 加载URL与权限资源,并提供根据URL匹配权限的方法
* @author weilu2
* @date 2016年12月17日 上午11:18:52
*
*/
@Component
public class CustomSecurityMetadataSource
implements FilterInvocationSecurityMetadataSource { private Map<String, List<ConfigAttribute>> resources; public CustomSecurityMetadataSource() {
loadAuthorityResources();
} private void loadAuthorityResources() {
// 此处在创建时从数据库中初始化权限数据
// 将权限与资源数据整理成 Map<resource, List<Authority>> 的形式
// 注意:加载URL资源时,需要对资源进行排序,要由精确到粗略进行排序,让精确的URL优先匹配
resources = new HashMap<>(); // 此处先伪造一些数据
List<ConfigAttribute> authorityList = new ArrayList<>();
ConfigAttribute auth = new SecurityConfig("AUTH_WELCOME");
authorityList.add(auth);
resources.put("/welcome", authorityList);
} @Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { String url = ((FilterInvocation) object).getRequestUrl(); Set<String> keys = resources.keySet(); for (String k : keys) {
if (url.indexOf(k) >= 0) {
return resources.get(k);
}
}
return null;
} @Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
// TODO Auto-generated method stub
return null;
} @Override
public boolean supports(Class<?> clazz) {
return true;
} }

在这个类中,实现了FilterInvocationSecurityMetadataSource接口,这个接口中的 getAttributes(Object object)方法能够根据请求的URL,获取这个URL所需要的权限,那么我们就可以在这个类初始化的时候将所有需要的权限加载进来,然后根据我们的规则进行获取,因此这里还需要编写一个加载数据的方法 loadAuthorityResources(),并且在构造函数中调用。

此处加载资源为了简化,只是随意填充了一些数据,实际可以从数据库中获取。

3.2.自定义AccessDecisionManager
编写自定义的决策管理器,决策管理器是Spring Security用来决定对于一个用户的请求是否基于通过的中心控制。

package com.oolong.customsecurity;

import java.util.Collection;
import java.util.Iterator; import org.apache.log4j.LogManager;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component; /**
* 进行决策,根据URL获得访问这个资源所需要的权限,然后在与当前用户所拥有的权限进行对比
* 如果当前用户拥有相关权限,就直接返回,否则抛出 AccessDeniedException异常
* @author weilu2
* @date 2016年12月17日 上午11:30:40
*
*/
@Component
public class CustomAccessDecisionManager implements AccessDecisionManager { @Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException { LogManager.getLogger("CustomAccessDecisionManager").info("decide invoke"); if (configAttributes == null) {
return;
} if (configAttributes.size() <= 0) {
return;
} Iterator<ConfigAttribute> authorities = configAttributes.iterator();
String needAuthority = null; while(authorities.hasNext()) {
ConfigAttribute authority = authorities.next(); if (authority == null || (needAuthority = authority.getAttribute()) == null) {
continue;
} LogManager.getLogger("CustomAccessDecisionManager").info("decide == " + needAuthority); for (GrantedAuthority ga : authentication.getAuthorities()) {
if (needAuthority.equals(ga.getAuthority().trim())) {
return;
}
}
}
throw new AccessDeniedException("No Authority");
} @Override
public boolean supports(ConfigAttribute attribute) {
return true;
} @Override
public boolean supports(Class<?> clazz) {
return true;
} }

决策管理器最重要的就是这个 decide()方法,Spring Security会将当前登录用户信息包装到一个 Authentication对象中,并传入这个方法;并且调用 SecurityMetadataSource.getAttributes() 方法获取这个URL相关的权限以参数 Collection<ConfigAttribute> 的形式传入这个方法。

然后这个decide方法获取到这两个信息之后就可以进行对比决策了。如果当前用户允许登录,那么直接return即可。如果当前用户不许运行登录,则抛出一个 AccessDeniedException异常。

3.3.自定义 UserDetailsService 和 AuthenticationProvider

前面说过,要进行验证,除了有URL与权限的映射关系,还需要有用户的权限信息。要编写自定义的用户数据加载,就需要实现这两个接口。

3.3.1.UserDetailsService

package com.oolong.customsecurity;

import java.util.ArrayList;
import java.util.List; import org.apache.log4j.LogManager;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component; import com.oolong.model.AccountInfoModel;
import com.oolong.model.AuthorityModel; @Component
public class CustomUserDetailsService implements UserDetailsService { @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { LogManager.getLogger("CustomUserDetailsService").info("loadUserByUsername invoke"); // 提供到数据库查询该用户的权限信息
// 关于角色和权限的转换关系在此处处理,根据用户与角色的关系、角色与权限的关系,
// 将用户与权限的管理整理出来 // 此处伪造一些数据
// 伪造权限
AuthorityModel authority = new AuthorityModel("AUTH_WELCOME");
List<AuthorityModel> authorities = new ArrayList<>();
authorities.add(authority); AccountInfoModel account = new AccountInfoModel("oolong", "12345");
account.setAuthorities(authorities); return account;
}
}

3.3.2.AuthenticationProvider
AuthenticationProvider用于包装UserDetailsService,并将其提供给 Spring Security使用。这个接口中最重要的是实现 retrieveUser() 方法,这个请参考接口的说明进行实现,此处不再赘述。

package com.oolong.customsecurity;

import org.apache.log4j.LogManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component; /**
* 这两个方法用于添加额外的检查功能,此处不需要添加,因此空着,直接实现这个抽象类即可。
* @author weilu2
* @date 2016年12月17日 下午12:20:27
*
*/
@Component
public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { @Autowired
private UserDetailsService userDetailsService; public UserDetailsService getUserDetailService() {
return this.userDetailsService;
} public void setUserDetailService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
} @Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { } @Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException { LogManager.getLogger("CustomUserDetailsAuthenticationProvider").info("retrieveUser invoke"); if (userDetailsService == null) {
throw new AuthenticationServiceException("");
} UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (userDetails == null) {
throw new UsernameNotFoundException(username);
} if (userDetails.getUsername().equals(authentication.getPrincipal().toString())
&& userDetails.getPassword().equals(authentication.getCredentials().toString())) {
return userDetails;
} throw new BadCredentialsException(username + authentication.getCredentials());
}
}

3.4.UserDetails和GrantedAuthority
这两个接口非常简单,请参考源码,此处不再赘述

4.配置
上面编写的这些自定义的实现都有了,但是仅仅这样是没有用的,如何配置能够让它们起作用呢?

package com.oolong.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import com.oolong.customsecurity.CustomAccessDecisionManager;
import com.oolong.customsecurity.CustomSecurityMetadataSource;
import com.oolong.customsecurity.CustomUserDetailsAuthenticationProvider;
import com.oolong.customsecurity.TempHook; @Configuration
@ComponentScan(basePackageClasses={TempHook.class})
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private CustomUserDetailsAuthenticationProvider customAuthenticationProvider; @Autowired
private CustomAccessDecisionManager customAccessDecisionManager; @Autowired
private CustomSecurityMetadataSource customSecurityMetadataSource; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(customFilterSecurityInterceptor(), ExceptionTranslationFilter.class);
http.formLogin();
} @Bean
public FilterSecurityInterceptor customFilterSecurityInterceptor() {
FilterSecurityInterceptor fsi = new FilterSecurityInterceptor();
fsi.setAccessDecisionManager(customAccessDecisionManager);
fsi.setSecurityMetadataSource(customSecurityMetadataSource); return fsi;
}
}

在Spring MVC中,Spring Security是通过过滤器发挥作用的,因此我们就爱那个决策管理器与数据加载放到一个过滤器中,然后将这个过滤器插入到系统的过滤器链中。

此外,我们向系统中提供了一个用于检索用户的 AuthenticationProvicer。

还有,别忘记了,告诉系统,如果用户没有权限应该怎么办,http.formLogin(),告诉Spring Security要跳转到表单登录页面。

参考

[1] 源码

SpringSecurity——基于Spring、SpringMVC和MyBatis自定义SpringSecurity权限认证规则的更多相关文章

  1. SpringSecurity操作指南-基于Spring、SpringMVC和MyBatis自定义SpringSecurity权限认证规则

  2. Intellij Idea下搭建基于Spring+SpringMvc+MyBatis的WebApi接口架构

    2018-08-16 09:27 更新 强烈推荐使用Springboot来搭建MVC框架! 强烈推荐使用Springboot来搭建MVC框架! 强烈推荐使用Springboot来搭建MVC框架! 后文 ...

  3. 基于Spring+SpringMVC+Mybatis的Web系统搭建

    系统搭建的配置大同小异,本文在前人的基础上做了些许的改动,重写数据库,增加依据权限的动态菜单的实现,也增加了后台返回json格式数据的配置,详细参见完整源码. 主要的后端架构:Spring+Sprin ...

  4. SpringBoot搭建基于Spring+SpringMvc+Mybatis的REST服务

    Maven Plugin管理 通常,让你的Maven POM文件继承 spring-boot-starter-parent,并声明一个或多个 Starter POMs依赖即可. spring-boot ...

  5. Spring + SpringMVC + Druid + MyBatis 给你一个灵活的后端解决方案

    生命不息,折腾不止. 折腾能遇到很多坑,填坑我理解为成长. 两个月前自己倒腾了一套用开源框架构建的 JavaWeb 后端解决方案. Spring + SpringMVC + Druid + JPA(H ...

  6. Spring SpringMVC和Mybatis整合

    1.引入所要的jar包 2.创建Mybatis的sqlMapConfig.xml配置文件,该文件中可以配置mybaits的相关参数,数据源不在这里配置. <?xml version=" ...

  7. shiro开发,shiro的环境配置(基于spring+springMVC+redis)

    特别感谢lhacker分享的文章,对我帮助很大 http://www.aiuxian.com/article/p-1913280.html 基本的知识就不在这里讲了,在实战中体会shiro的整体设计理 ...

  8. swagger-ui 系统配置过程(基于spring+springmvc+swagger+springfox配置 web-api 管理系统)

    web工程部分框架信息:spring springmvc swagger springfox maven 参考文档:https://www.cnblogs.com/exmyth/p/7183753.h ...

  9. 基于Spring Security 的JSaaS应用的权限管理

    1. 概述 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源.资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见.本文介绍的则是基于Saas系统 ...

随机推荐

  1. odoo继承父类中的函数(方法)

    使用_inherit继承父类重新设计新类时,可以调用父类中的函数,具体为: 第一步:获得某个模型('model.name')的数据集并进行某种集合操作(model_function),从而获得想要的数 ...

  2. Spring基本功能-扫描与继承

    一.Spring的扫描 一个稍大的项目中,可能会有成百上千个bean,此时采用xml的配置形式注入bean,一方面是配置文件显得十分庞大,另一方面也会导致后期的维护难度增加,为 此,Spring引入了 ...

  3. python全栈开发从入门到放弃之socket并发编程多进程

    1.1 multiprocessing模块介绍 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程 ...

  4. 在Windows上安装Elasticsearch 5.x

    在Windows上安装Elasticsearch 5.x 自己想学习Elasticsearch,但是又不懂Linux,按照同事给的Linux安装教程,也是搞不明白,于是想先在Windows上安装一下入 ...

  5. android studio 版本修改无效解决方案

    我们都知道android的版本声明,是在AndroidManifest.xml文件里面的.例如 <manifest xmlns:android="http://schemas.andr ...

  6. SSIS 2012 Error: An Integration Services class cannot be found

    升级SSIS到SQL Server 2012,服务器只安装了SSIS一个功能,应用程序执行dtsx包时报错如下: An Integration Services class cannot be fou ...

  7. 在Ubuntu14.4(32位)中配置I.MX6的QT编译环境

    1,开发工具下载 一,下载VMware Workstation虚拟机 地址:http://1.xp510.com:801/xp2011/VMware10.7z 二,下载Ubuntu 14.04.5 L ...

  8. Python3.x:pytesseract识别率提高(样本训练)

    Python3.x:pytesseract识别率提高(样本训练) 1,下载并安装3.05版本的tesseract 地址:https://sourceforge.net/projects/tessera ...

  9. 20145314郑凯杰 《Java程序设计》第4周学习总结

    20145314郑凯杰 <Java程序设计>第4周学习总结 所有代码已上传: 教材学习内容总结 ①继承 设计程序中,因们需要设计多个模块,我想到了李晓东以前教我们的三个字"模块化 ...

  10. 网络攻防工具介绍——Metasploit

    Metasploit 简介 Metasploit是一款开源的安全漏洞检测工具,可以帮助安全和IT专业人士识别安全性问题,验证漏洞的缓解措施,并管理专家驱动的安全性进行评估,提供真正的安全风险情报.这些 ...