SpringSecurity 可以使用注解对方法进行细颗粒权限控制,但是很不灵活,必须在编码期间,就已经写死权限

其实关于SpringSecurity,大部分类都不需要重写,需要的只是妥善的配置.

每次修改权限以后,需要让MetaDataSource刷新 资源-权限 的MAP,这里应该需要做一些处理,或者优化.

这里实现,可以在后台随时开启关闭权限,不需要硬编码写死.而且资源的RequestMapping,可以是有多个地址

可以根据角色分配权限,也可以精确到为每一个用户分配权限,模块,或者方法.

这样比较灵活,但是UI会很复杂,用户也不好理解

资源注解:注解使用在控制器类,或者方法中.注解在类中,粗颗粒控制,注解在方法中细颗粒

/**
* Created by ZhenWeiLai on on 2016-10-16.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AclResc {
int id();//ACLResource 因为特殊原因不使用 id 自动增长,所以必须自定义ID ,并且不能重复
String code();
String name();
String homePage() default "";
boolean isMenu() default true;
}

注解在类:

@AclResc(id = 5000,code = "aclRescUser", name = AclRescUserController.MODULE_NAME,homePage = AclRescUserController.HOME_PAGE)
public class AclRescUserController extends BaseController<AclRescUser>

注解在方法:

    @RequestMapping(value = "/list",method = RequestMethod.GET)
@AclResc(id = 5001,code = "list",name = "用户资源列表")
public ResultDataDto list(){ }

系统完全启动后,更新资源信息:

/**
* Created by ZhenWeiLai on on 2016-10-16.
* SpringBoot 启动完毕做些事情
*/
@Component
public class ApplicationStartup implements CommandLineRunner { @Resource
private AclResourceService aclResourceService;
@Resource
private AclAuthService aclAuthService; @Resource
private RequestMappingHandlerMapping requestMappingHandlerMapping; @Resource
private MySecurityMetadataSource securityMetadataSource; @Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void run(String... strings) throws Exception {
/**
* 初始化资源,保存到数据库
*/
initModule();
/**
* Spring Security 需要的资源-权限
*/
securityMetadataSource.doLoadResourceDefine();
} /**
* 读取所有Controller包括以内的方法
*/
private void initModule() {
/**
* 模块 - 方法map
*/
Map<AclResource, List<AclResource>> resourcesMap = new HashMap<>();
Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
for (RequestMappingInfo info : map.keySet()) {
AclResc moduleAclResc = map.get(info).getBeanType().getAnnotation(AclResc.class);
if (moduleAclResc != null) {
if (StringUtils.isBlank(moduleAclResc.homePage()))
throw new RuntimeException("使用:" + AclResc.class.getName() + " 注解类时,请配置 homePage ");
Class<?> aclResourceClass = map.get(info).getBeanType();
RequestMapping moduleMapping = aclResourceClass.getAnnotation(RequestMapping.class);
AclResource moduleResc = new AclResource(moduleAclResc.id(), moduleAclResc.code(), moduleAclResc.name(), Arrays.toString(moduleMapping.value()), AclResource.Type.MODULE.getCode(), moduleAclResc.homePage(), moduleAclResc.isMenu());
if (moduleMapping != null) {
List<AclResource> resources;
AclResource methodResc;
Method method = map.get(info).getMethod();
AclResc methodAclResc = method.getAnnotation(AclResc.class);
if (methodAclResc != null) {
methodResc = new AclResource(methodAclResc.id(), methodAclResc.code(), methodAclResc.name(), info.getPatternsCondition().toString().replace("||", Delimiter.COMMA.getDelimiter()), AclResource.Type.METHOD.getCode(), null);
if (resourcesMap.get(moduleResc) == null) {
resources = new ArrayList<>();
resources.add(methodResc);
resourcesMap.put(moduleResc, resources);
} else {
resourcesMap.get(moduleResc).add(methodResc);
}
}
}
}
}
addModule(resourcesMap);
} /**
* 检查新模块,添加到数据库,并更新视图的模块ID
*
* @param resourcesMap
*/
private void addModule(Map<AclResource, List<AclResource>> resourcesMap) {
for (Map.Entry<AclResource, List<AclResource>> item : resourcesMap.entrySet()) {
AclResource resultResc = aclResourceService.findEntityById(item.getKey().getId());
//如果模块是新模块,那么新增到数据库
if (resultResc == null) {
aclResourceService.addEntity(item.getKey());
List<AclResource> resources = item.getValue();
for (AclResource resc : resources) {
resc.setModuleId(item.getKey().getId());
}
} else {
//如果已存在模块,那么更新需要的字段
aclResourceService.updateEntity(item.getKey());
List<AclResource> resources = item.getValue();
for (AclResource methodResc : resources) {
//方法模块CODE 根据 模块CODE + 方法CODE 生成
methodResc.setCode(item.getKey().getCode() + "_" + methodResc.getCode());
methodResc.setModuleId(resultResc.getId());
AclResource oringinalMethodResc = aclResourceService.findEntityById(methodResc.getId());
if (oringinalMethodResc != null) {
//RequestMapping可能被修改,所以这里要做一次更新
aclResourceService.updateEntity(methodResc);
//同时code也可能被更改,所以更新权限code
aclAuthService.updateCodeByRescId(methodResc.getCode(), methodResc.getId());
} else {
aclResourceService.addEntity(methodResc);
}
}
}
}
} }

构建权限菜单:

/**
* 根据用户权限构建菜单
*/
@Override
public Map<AclMenu, List<AclResource>> getAclUserMenus() { //创建完整的菜单,然后删除没有权限的菜单
Map<AclMenu, List<AclResource>> userMenuModuleMap = findAclMenuModuleMap();
//获取资源/权限集
Map<String, Collection<ConfigAttribute>> moduleMap = securityMetadataSource.getModuleMap();
for (String path : moduleMap.keySet()) {
//如果没有权限
if (!SecurityUtil.hastAnyAuth(moduleMap.get(path))) {
Iterator<AclMenu> userMenuModuleMapKey = userMenuModuleMap.keySet().iterator();
while (userMenuModuleMapKey.hasNext()) {
AclMenu key = userMenuModuleMapKey.next();
List<AclResource> modules = userMenuModuleMap.get(key);
if (modules.isEmpty()) {
userMenuModuleMapKey.remove();
continue;
}
Iterator<AclResource> aclResourceIterator = modules.iterator();
while (aclResourceIterator.hasNext()) {
String rescPath = aclResourceIterator.next().getPath();
String[] pathArr = rescPath.substring(1, rescPath.length() - 1).split(Delimiter.COMMA.getDelimiter());
for (String item : pathArr) {
if (item.equals(path)) {
//从菜单模块中删除
aclResourceIterator.remove();
//如果模块为空
if (modules.isEmpty()) {
//删除菜单
userMenuModuleMapKey.remove();
}
}
} }
}
}
}
return userMenuModuleMap;
}
FilterInvocationSecurityMetadataSource:
/**
* Created by ZhenWeiLai on 2016-10-16.
*/
@Component("securityMetadataSource")
public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private static Map<String, Collection<ConfigAttribute>> moduleMap = null; private static Map<String, Collection<ConfigAttribute>> methodMap = null; @Resource
private AclResourceService aclResourceService; @Resource
private AclRescRoleService aclRescRoleService; @Resource
private AclRoleService aclRoleService; @Resource
private AclAuthService aclAuthService; @Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
Collection<ConfigAttribute> collection;
collection = getAttributesHandler(methodMap, object);
if (collection != null)
return collection;
collection = getAttributesHandler(moduleMap, object);
return collection;
} /**
* 处理方法
*
* @param map
* @return
*/
private Collection<ConfigAttribute> getAttributesHandler(Map<String, Collection<ConfigAttribute>> map, Object object) {
HttpServletRequest request = ((FilterInvocation) object).getRequest();
Iterator var3 = map.entrySet().iterator();
Map.Entry entry;
do {
if (!var3.hasNext()) {
return null;
}
entry = (Map.Entry) var3.next(); } while (!(new AntPathRequestMatcher(entry.getKey().toString())).matches(request));
return (Collection) entry.getValue();
} //
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
Set<ConfigAttribute> allAttributes = new HashSet();
Map<String, Collection<ConfigAttribute>> all = new HashMap<>(this.moduleMap);
all.putAll(this.methodMap);
Iterator var2 = all.entrySet().iterator();
while (var2.hasNext()) {
Map.Entry<String, Collection<ConfigAttribute>> entry = (Map.Entry) var2.next();
allAttributes.addAll(entry.getValue());
} return allAttributes;
} //
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
} @Transactional(readOnly = true)
private void loadResourceDefine() {
loadModuleResources();
loadMethodResources();
} /**
* 提供一个外部使用方法.获取module权限MAP;
*
* @return
*/
public Map<String, Collection<ConfigAttribute>> getModuleMap() {
Map<String, Collection<ConfigAttribute>> map = new HashMap<>(moduleMap);
return map;
} /**
* 提供外部方法让Spring环境启动完成后调用
*/
public void doLoadResourceDefine() {
loadResourceDefine();
} /**
* 读取模块资源
*/
private void loadModuleResources() {
/**
* 查询模块资源权限,配置模块权限验证
*/
List<AclResource> aclResources = aclResourceService.findAllModule(); //模块资源为KEY,角色为Value 的list
moduleMap = new HashMap<>();
for (AclResource module : aclResources) {
/**
* 加载所有模块资源
*/
List<AclRescRole> aclRescRoles = aclRescRoleService.findByRescId(module.getId()); /**
* 无论如何超级管理员拥有所有权限
*/
stuff(new SecurityConfig(SecurityUtil.ADMIN), moduleMap, module.getPath()); for (AclRescRole aclRescRole : aclRescRoles) {
Integer roleId = aclRescRole.getRoleId();//角色ID
String roleCode = aclRoleService.findEntityById(roleId).getCode();//角色编码
stuff(new SecurityConfig(roleCode.toUpperCase()), moduleMap, module.getPath());
}
}
} /**
* 读取精确方法权限资源
*/
private void loadMethodResources() {
/**
* 因为只有权限控制的资源才需要被拦截验证,所以只加载有权限控制的资源
*/
//方法资源为key,权限编码为
methodMap = new HashMap<>();
List<Map<String, String>> pathAuths = aclAuthService.findPathCode();
for (Map pathAuth : pathAuths) {
String path = pathAuth.get("path").toString();
ConfigAttribute ca = new SecurityConfig(pathAuth.get("code").toString().toUpperCase());
stuff(ca, methodMap, path);
}
} private void stuff(ConfigAttribute ca, Map<String, Collection<ConfigAttribute>> map, String path) { String[] pathArr = path.substring(1, path.length() - 1).split(Delimiter.COMMA.getDelimiter());
for (String item : pathArr) {
Collection<ConfigAttribute> collection = map.get(item + "/**");
if (collection != null) {
collection.add(ca);
} else {
collection = new ArrayList<>();
collection.add(ca);
String pattern = StringUtils.trimToEmpty(item) + "/**";
map.put(pattern, collection);
}
}
}
}

最后:

/**
* Created by ZhenWeiLai on on 2016-10-16.
* <p>
* 三种方法级权限控制
* <p>
* 1.securedEnabled: Spring Security’s native annotation
* 2.jsr250Enabled: standards-based and allow simple role-based constraints
* 3.prePostEnabled: expression-based
*/
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource
private UserDetailsService userDetailsService; @Resource
private MySecurityMetadataSource securityMetadataSource; @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/images/**");
web.ignoring().antMatchers("/js/**");
//忽略登录界面
web.ignoring().antMatchers("/login"); //注册地址不拦截
// web.ignoring().antMatchers("/reg");
} @Override
protected void configure(HttpSecurity http) throws Exception {
//解决不允许显示在iframe的问题
http.headers().frameOptions().disable(); http.addFilterAt(usernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); http.authorizeRequests().anyRequest().fullyAuthenticated(); //自定义过滤器
MyFilterSecurityInterceptor filterSecurityInterceptor = new MyFilterSecurityInterceptor(securityMetadataSource,accessDecisionManager(),authenticationManagerBean());
//在适当的地方加入
http.addFilterAt(filterSecurityInterceptor,FilterSecurityInterceptor.class); http.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")).and().logout().logoutUrl("/logout").logoutSuccessUrl("/login").and().exceptionHandling().accessDeniedPage("/accessDenied"); // 关闭csrf
http.csrf().disable(); //session管理
//session失效后跳转
http.sessionManagement().invalidSessionUrl("/login");
//只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
http.sessionManagement().maximumSessions(1).expiredUrl("/login");
} @Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
// 自定义UserDetailsService,设置加密算法
auth.userDetailsService(userDetailsService);
//.passwordEncoder(passwordEncoder())
//不删除凭据,以便记住用户
auth.eraseCredentials(false);
} UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setPostOnly(true);
usernamePasswordAuthenticationFilter.setAuthenticationManager(this.authenticationManager());
usernamePasswordAuthenticationFilter.setUsernameParameter("name_key");
usernamePasswordAuthenticationFilter.setPasswordParameter("pwd_key");
usernamePasswordAuthenticationFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/checkLogin", "POST"));
usernamePasswordAuthenticationFilter.setAuthenticationFailureHandler(simpleUrlAuthenticationFailureHandler());
usernamePasswordAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
return usernamePasswordAuthenticationFilter;
} // @Bean
// public LoggerListener loggerListener() {
// System.out.println("org.springframework.security.authentication.event.LoggerListener");
// return new LoggerListener();
// }
//
// @Bean
// public org.springframework.security.access.event.LoggerListener eventLoggerListener() {
// System.out.println("org.springframework.security.access.event.LoggerListener");
// return new org.springframework.security.access.event.LoggerListener();
// } /**
* 投票器
*/
private AbstractAccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList();
decisionVoters.add(new AuthenticatedVoter());
decisionVoters.add(new RoleVoter());//角色投票器,默认前缀为ROLE_
RoleVoter AuthVoter = new RoleVoter();
AuthVoter.setRolePrefix("AUTH_");//特殊权限投票器,修改前缀为AUTH_
decisionVoters.add(AuthVoter);
AbstractAccessDecisionManager accessDecisionManager = new AffirmativeBased(decisionVoters);
return accessDecisionManager;
} @Override
public AuthenticationManager authenticationManagerBean() {
AuthenticationManager authenticationManager = null;
try {
authenticationManager = super.authenticationManagerBean();
} catch (Exception e) {
e.printStackTrace();
}
return authenticationManager;
} /**
* 验证异常处理器
*
* @return
*/
private SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
return new SimpleUrlAuthenticationFailureHandler("/getLoginError");
} // /**
// * 表达式控制器
// *
// * @return
// */
// private DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
// DefaultWebSecurityExpressionHandler webSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler();
// return webSecurityExpressionHandler;
// } // /**
// * 表达式投票器
// *
// * @return
// */
// private WebExpressionVoter webExpressionVoter() {
// WebExpressionVoter webExpressionVoter = new WebExpressionVoter();
// webExpressionVoter.setExpressionHandler(webSecurityExpressionHandler());
// return webExpressionVoter;
// } // Code5 官方推荐加密算法
// @Bean("passwordEncoder")
// public BCryptPasswordEncoder passwordEncoder() {
// BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// return bCryptPasswordEncoder;
// } // // Code3---------------------------------------------- /**
* 登录成功后跳转
* 如果需要根据不同的角色做不同的跳转处理,那么继承AuthenticationSuccessHandler重写方法
*
* @return
*/
private SimpleUrlAuthenticationSuccessHandler authenticationSuccessHandler() {
return new SimpleUrlAuthenticationSuccessHandler("/loginSuccess");
} /**
* Created by ZhenWeiLai on on 2016-10-16.
*/
public static class MyFilterSecurityInterceptor extends FilterSecurityInterceptor { public MyFilterSecurityInterceptor(FilterInvocationSecurityMetadataSource securityMetadataSource, AccessDecisionManager accessDecisionManager, AuthenticationManager authenticationManager){
this.setSecurityMetadataSource(securityMetadataSource);
this.setAccessDecisionManager(accessDecisionManager);
this.setAuthenticationManager(authenticationManager); }
} }

SpringBoot SpringSecurity4整合,灵活权限配置,弃用注解方式.的更多相关文章

  1. springBoot 官方整合的redis 使用教程:(StringRedisTemplate 方式存储 Object类型value)

    前言:最近新项目准备用 redis 简单的缓存 一些查询信息,以便第二次查询效率高一点. 项目框架:springBoot.java.maven  说明:edis存储的数据类型,key一般都是Strin ...

  2. spring与hibernate整合配置基于Annotation注解方式管理实务

    1.配置数据源 数据库连接基本信息存放到properties文件中,因此先加载properties文件 <!-- jdbc连接信息 --> <context:property-pla ...

  3. SpringBoot系列-整合Mybatis(XML配置方式)

    目录 一.什么是 MyBatis? 二.整合方式 三.实战 四.测试 本文介绍下SpringBoot整合Mybatis(XML配置方式)的过程. 一.什么是 MyBatis? MyBatis 是一款优 ...

  4. SpringBoot整合Shiro权限框架实战

    什么是ACL和RBAC ACL Access Control list:访问控制列表 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理 例子:常见的文件系 ...

  5. SpringBoot系列七:SpringBoot 整合 MyBatis(配置 druid 数据源、配置 MyBatis、事务控制、druid 监控)

    1.概念:SpringBoot 整合 MyBatis 2.背景 SpringBoot 得到最终效果是一个简化到极致的 WEB 开发,但是只要牵扯到 WEB 开发,就绝对不可能缺少数据层操作,所有的开发 ...

  6. SpringBoot整合Mybatis,TypeAliases配置失败的问题

    SpringBoot整合Mybatis,TypeAliases配置失败的问题 问题描述 在应用MyBatis时,使用对象关系映射,将对象和Aliase映射起来. 在Mybatis的文档明确写出,如果你 ...

  7. 靓仔,整合SpringBoot还在百度搜配置吗?老司机教你一招!!!

    导读 最近陈某公司有些忙,为了保证文章的高质量可能要两天一更了,在这里陈某先说声不好意思了!!! 昨天有朋友问我SpringBoot如何整合Redis,他说百度谷歌搜索了一遍感觉不太靠谱.我顿时惊呆了 ...

  8. SpringBoot 整合MyBatis 统一配置bean的别名

    所谓别名, 就是在mappper.xml配置文件中像什么resultType="xxx" 不需要写全限定类名, 只需要写类名即可. 配置方式有两种: 1. 在 applicatio ...

  9. SpringBoot+SpringData 整合入门

    SpringData概述 SpringData :Spring的一个子项目.用于简化数据库访问,支持NoSQL和关系数据存储.其主要目标是使用数据库的访问变得方便快捷. SpringData 项目所支 ...

随机推荐

  1. Linux磁盘热插拔命令

    对于支持热插拔SCSI技术的服务器,SCSI硬盘和磁带机可以在Linux下实现在线添加和移除,有两种方法实现,先说麻烦的 具体方法如下: 一. 添加或者删除硬盘/磁带机: # echo "s ...

  2. linkin大话数据结构--Google commons工具类

    package tz.web.dao.bean; import java.util.Arrays; import java.util.Collection; import java.util.List ...

  3. 渲染引擎(The rendering engine)

    渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容.这是每一个浏览器的核心部分,所以渲染引擎也称为浏览器内核. 渲染引擎一开始会从网络层获取请求文档的内容. 获取文档后,渲染引擎开始解析 htm ...

  4. Unity 使用Plugins接入安卓SDK 基础篇

    一.须知 本帖适合对安卓一点基础都没有,有一定Unity基础.刚刚接完一个某文档很简单的渠道SDk,也当是自己总结一下. 二.Unity中的目录创建与理解. Plugins:插件目录,该目录再编译项目 ...

  5. 【转】shell学习笔记(二) ——shell变量

    在shell中有3种变量:系统变量,环境变量和用户变量,其中系统变量在对参数判断和命令返回值判断时会使用,环境变量主要是在程序运行时需要设置,用户变量在编程过程中使用量最多. 1 系统变量  变量名 ...

  6. Nginx+Geoserver部署所遇问题总结

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 该问题的最终解决离不开公司大拿whs先生的指点,先表示感谢. ...

  7. Ubantu搭建FTP

    1.安装并启动 FTP 服务 安装 VSFTPD 使用 apt-get 安装 vsftpd kylin@kylin:~$ sudo apt-get install vsftpd -y [sudo] p ...

  8. re模块与正则表达式

    一.正则表达式概念 正则表达式,又称正规表示式.正规表示法.正规表达式.规则表达式.常规表示法(英语:Regular Expression,在代码中常简写为regex.regexp或RE),是计算机科 ...

  9. html集锦

    注意:此内容为复习所总结,非专业,不全,理解记录理解会有偏差. 一.HTML解释: 指的是超文本标记语言 (Hyper Text Markup Language),不是一种编程语言,而是一种标记语言  ...

  10. 使用JavaScript实现机器学习和神经学网络

    欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 下载heaton-javascript-ml.zip - 45.1 KB 基本介绍 在本文中,你会对如何使用JavaScript实现机器学习这个 ...