在本例中,主要讲解spring-boot与spring-security的集成,实现方式为:

  • 将用户、权限、资源(url)采用数据库存储
  • 自定义过滤器,代替原有的 FilterSecurityInterceptor
  • 自定义实现 UserDetailsService、AccessDecisionManager和InvocationSecurityMetadataSourceService,并在配置文件进行相应的配置
    GitHub 地址:https://github.com/fp2952/spring-boot-security-demo

用户角色表(基于RBAC权限控制)

  • 用户表(base_user)
code type length
ID varchar 32
USER_NAME varchar 50
USER_PASSWORD varchar 100
NIKE_NAME varchar 50
STATUS int 11
  • 用户角色表(base_user_role)
code type length
ID varchar 32
USER_ID varchar 32
ROLE_ID varchar 32
  • 角色表(base_role)
code type length
ID varchar 32
ROLE_CODE varchar 32
ROLE_NAME varchar 64
  • 角色菜单表(base_role_menu)
code type length
ID varchar 32
ROLE_ID varchar 32
MENU_ID varchar 32
  • 菜单表(base_menu)
code type length
ID varchar 32
MENU_URL varchar 120
MENU_SEQ varchar 120
MENU_PARENT_ID varchar 32
MENU_NAME varchar 50
MENU_ICON varchar 20
MENU_ORDER int 11
IS_LEAF varchar 20

实现主要配置类

实现AbstractAuthenticationProcessingFilter

用于用户表单验证,内部调用了authenticationManager完成认证,根据认证结果执行successfulAuthentication或者unsuccessfulAuthentication,无论成功失败,一般的实现都是转发或者重定向等处理。

   @Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {if (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}//获取表单中的用户名和密码String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();//组装成username+password形式的tokenUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);//交给内部的AuthenticationManager去认证,并返回认证信息return this.getAuthenticationManager().authenticate(authRequest);}

AuthenticationManager

AuthenticationManager是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法authenticate(),该方法只接收一个代表认证请求的Authentication对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的Authentication对象进行返回。
Authentication authenticate(Authentication authentication) throws AuthenticationException;
在Spring Security中,AuthenticationManager的默认实现是ProviderManager,而且它不直接自己处理认证请求,而是委托给其所配置的AuthenticationProvider列表,然后会依次使用每一个AuthenticationProvider进行认证,如果有一个AuthenticationProvider认证后的结果不为null,则表示该AuthenticationProvider已经认证成功,之后的AuthenticationProvider将不再继续认证。然后直接以该AuthenticationProvider的认证结果作为ProviderManager的认证结果。如果所有的AuthenticationProvider的认证结果都为null,则表示认证失败,将抛出一个ProviderNotFoundException。
校验认证请求最常用的方法是根据请求的用户名加载对应的UserDetails,然后比对UserDetails的密码与认证请求的密码是否一致,一致则表示认证通过。
Spring Security内部的DaoAuthenticationProvider就是使用的这种方式。其内部使用UserDetailsService来负责加载UserDetails。在认证成功以后会使用加载的UserDetails来封装要返回的Authentication对象,加载的UserDetails对象是包含用户权限等信息的。认证成功返回的Authentication对象将会保存在当前的SecurityContext中。

实现UserDetailsService

UserDetailsService只定义了一个方法 loadUserByUsername,根据用户名可以查到用户并返回的方法。

@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {logger.debug("权限框架-加载用户");List<GrantedAuthority> auths = new ArrayList<>();BaseUser baseUser = new BaseUser();baseUser.setUserName(username);baseUser = baseUserService.selectOne(baseUser);if (baseUser == null) {logger.debug("找不到该用户 用户名:{}", username);throw new UsernameNotFoundException("找不到该用户!");}if(baseUser.getStatus()==2){logger.debug("用户被禁用,无法登陆 用户名:{}", username);throw new UsernameNotFoundException("用户被禁用!");}List<BaseRole> roles = baseRoleService.selectRolesByUserId(baseUser.getId());if (roles != null) {//设置角色名称for (BaseRole role : roles) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRoleCode());auths.add(authority);}}return new org.springframework.security.core.userdetails.User(baseUser.getUserName(), baseUser.getUserPassword(), true, true, true, true, auths);}

实现AbstractSecurityInterceptor

访问url时,会被AbstractSecurityInterceptor拦截器拦截,然后调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,再调用授权管理器AccessDecisionManager鉴权。

public class CustomSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {private FilterInvocationSecurityMetadataSource securityMetadataSource;@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {FilterInvocation fi = new FilterInvocation(request, response, chain);invoke(fi);}@Overridepublic void destroy() {}@Overridepublic Class<?> getSecureObjectClass() {return FilterInvocation.class;}@Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return this.securityMetadataSource;}public void invoke(FilterInvocation fi) throws IOException {InterceptorStatusToken token = super.beforeInvocation(fi);try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());} catch (ServletException e) {super.afterInvocation(token, null);}}public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {return securityMetadataSource;}public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {this.securityMetadataSource = securityMetadataSource;}
}

FilterInvocationSecurityMetadataSource 获取所需权限

    @Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {//获取当前访问urlString url = ((FilterInvocation) object).getRequestUrl();int firstQuestionMarkIndex = url.indexOf("?");if (firstQuestionMarkIndex != -1) {url = url.substring(0, firstQuestionMarkIndex);}List<ConfigAttribute> result = new ArrayList<>();try {//设置不拦截if (propertySourceBean.getProperty("security.ignoring") != null) {String[] paths = propertySourceBean.getProperty("security.ignoring").toString().split(",");//判断是否符合规则for (String path: paths) {String temp = StringUtil.clearSpace(path);if (matcher.match(temp, url)) {return SecurityConfig.createList("ROLE_ANONYMOUS");}}}//如果不是拦截列表里的, 默认需要ROLE_ANONYMOUS权限if (!isIntercept(url)) {return SecurityConfig.createList("ROLE_ANONYMOUS");}//查询数据库url匹配的菜单List<BaseMenu> menuList = baseMenuService.selectMenusByUrl(url);if (menuList != null && menuList.size() > 0) {for (BaseMenu menu : menuList) {//查询拥有该菜单权限的角色列表List<BaseRole> roles = baseRoleService.selectRolesByMenuId(menu.getId());if (roles != null && roles.size() > 0) {for (BaseRole role : roles) {ConfigAttribute conf = new SecurityConfig(role.getRoleCode());result.add(conf);}}}}} catch (Exception e) {e.printStackTrace();}return result;}/*** 判断是否需要过滤* @param url* @return*/public boolean isIntercept(String url) {String[] filterPaths = propertySourceBean.getProperty("security.intercept").toString().split(",");for (String filter: filterPaths) {if (matcher.match(StringUtil.clearSpace(filter), url) & !matcher.match(indexUrl, url)) {return true;}}return false;}

AccessDecisionManager 鉴权

    @Overridepublic void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {if (collection == null) {return;}for (ConfigAttribute configAttribute : collection) {String needRole = configAttribute.getAttribute();for (GrantedAuthority ga : authentication.getAuthorities()) {if (needRole.trim().equals(ga.getAuthority().trim()) || needRole.trim().equals("ROLE_ANONYMOUS")) {return;}}}throw new AccessDeniedException("无权限!");}

配置 WebSecurityConfigurerAdapter

/*** spring-security配置*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {private final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate PropertySource propertySourceBean;@Overrideprotected void configure(HttpSecurity http) throws Exception {logger.debug("权限框架配置");String[] paths = null;//设置不拦截if (propertySourceBean.getProperty("security.ignoring") != null) {paths = propertySourceBean.getProperty("security.ignoring").toString().split(",");paths = StringUtil.clearSpace(paths);}//设置过滤器http    // 根据配置文件放行无需验证的url.authorizeRequests().antMatchers(paths).permitAll().and().httpBasic()// 配置验证异常处理.authenticationEntryPoint(getCustomLoginAuthEntryPoint())// 配置登陆过滤器.and().addFilterAt(getCustomLoginFilter(), UsernamePasswordAuthenticationFilter.class)// 配置 AbstractSecurityInterceptor.addFilterAt(getCustomSecurityInterceptor(), FilterSecurityInterceptor.class)// 登出成功处理.logout().logoutSuccessHandler(getCustomLogoutSuccessHandler())// 关闭csrf.and().csrf().disable()// 其他所有请求都需要验证.authorizeRequests().anyRequest().authenticated()// 配置登陆url, 登陆页面并无需验证.and().formLogin().loginProcessingUrl("/login").loginPage("/login.ftl").permitAll()// 登出.and().logout().logoutUrl("/logout").permitAll();logger.debug("配置忽略验证url");}@Autowired@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(getDaoAuthenticationProvider());}/*** spring security 配置* @return*/@Beanpublic CustomLoginAuthEntryPoint getCustomLoginAuthEntryPoint() {return new CustomLoginAuthEntryPoint();}/*** 用户验证* @return*/@Beanpublic DaoAuthenticationProvider getDaoAuthenticationProvider() {DaoAuthenticationProvider provider = new DaoAuthenticationProvider();provider.setUserDetailsService(userDetailsService);provider.setHideUserNotFoundExceptions(false);provider.setPasswordEncoder(new BCryptPasswordEncoder());return provider;}/*** 登陆* @return*/@Beanpublic CustomLoginFilter getCustomLoginFilter() {CustomLoginFilter filter = new CustomLoginFilter();try {filter.setAuthenticationManager(this.authenticationManagerBean());} catch (Exception e) {e.printStackTrace();}filter.setAuthenticationSuccessHandler(getCustomLoginAuthSuccessHandler());filter.setAuthenticationFailureHandler(new CustomLoginAuthFailureHandler());return filter;}@Beanpublic CustomLoginAuthSuccessHandler getCustomLoginAuthSuccessHandler() {CustomLoginAuthSuccessHandler handler =  new CustomLoginAuthSuccessHandler();if (propertySourceBean.getProperty("security.successUrl")!=null){handler.setAuthSuccessUrl(propertySourceBean.getProperty("security.successUrl").toString());}return handler;}/*** 登出* @return*/@Beanpublic CustomLogoutSuccessHandler getCustomLogoutSuccessHandler() {CustomLogoutSuccessHandler handler = new CustomLogoutSuccessHandler();if (propertySourceBean.getProperty("security.logoutSuccessUrl")!=null){handler.setLoginUrl(propertySourceBean.getProperty("security.logoutSuccessUrl").toString());}return handler;}/*** 过滤器* @return*/@Beanpublic CustomSecurityInterceptor getCustomSecurityInterceptor() {CustomSecurityInterceptor interceptor = new CustomSecurityInterceptor();interceptor.setAccessDecisionManager(new CustomAccessDecisionManager());interceptor.setSecurityMetadataSource(getCustomMetadataSourceService());try {interceptor.setAuthenticationManager(this.authenticationManagerBean());} catch (Exception e) {e.printStackTrace();}return interceptor;}@Beanpublic CustomMetadataSourceService getCustomMetadataSourceService() {CustomMetadataSourceService sourceService = new CustomMetadataSourceService();if (propertySourceBean.getProperty("security.successUrl")!=null){sourceService.setIndexUrl(propertySourceBean.getProperty("security.successUrl").toString());}return sourceService;}
}

转载于:https://www.cnblogs.com/fp2952/p/8933107.html

spring-security权限控制详解相关推荐

  1. 8.Spring Security 权限控制

    Spring Security权限控制可以配合授权注解使用,具体有哪些注解可以参考Spring-Security保护方法.接着上一节,要开启这些注解,只需要在Spring Security配置文件中添 ...

  2. maven 构建 springmvc + spring security 权限控制示例

    2019独角兽企业重金招聘Python工程师标准>>> maven 构建 springmvc + spring security 权限控制示例. 介绍 :Spring Securit ...

  3. spring boot security 权限用postman测试_Spring Security(五):前后端权限控制详解

    文章回顾: Spring Security(一):整合JWT实现登录功能 Spring Security(二):获取用户权限菜单树 Spring Security(三):与Vue.js整合 Sprin ...

  4. Spring Security 认证授权详解

    1.Spring Security 概述 1.1.Spring Security 简介 Spring Security 是 Spring 家族中的成员.Spring Security 基于 Sprin ...

  5. zookeeper权限控制详解

    文章目录 1.预备知识 1.1 权限列表 1.2 ACL权限特点 1.3 权限相关命令 2.添加认证用户:addauth 3. 设置znode节点操作权限命令:SetAcl 4. 设置节点权限命令中节 ...

  6. java 数据 权限_Java如何利用Mybatis进行数据权限控制详解

    前言 权限控制主要分为两块,认证(Authentication)与授权(Authorization).认证之后确认了身份正确,业务系统就会进行授权,现在业界比较流行的模型就是RBAC(Role-Bas ...

  7. spring security xml配置详解

    security 3.x <?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns= ...

  8. Spring Security 的 RememberMe 详解 !!!!!

    目录 目录 一.介绍 二.基本使用 2.1 开启记住我 三.原理分析 3.1 页面参数 3.2 RememberMeServices 3.3 TokenBasedRememberMeServices ...

  9. java权限控制详解,比较全

    权限控制是什么 权限一词在安全领域出现的频率很高."权限"实际上是一种"能力".对于权限的合理分配,一直是安全设计中的核心问题. 权限控制,广泛应用于各个系统中 ...

最新文章

  1. 【身边的经济学】节约打印用纸
  2. spring源码 — 一、IoC容器初始化
  3. 从微信AI首席顾问到金融文档智能,一位中科院计算机科学家AI产品化实践
  4. ||x||的错误理解
  5. 硬计算、软计算与混合计算
  6. 解决Error spawning 'cmd.exe'的问题
  7. Highcharts图表-ajax-获取json数据生成图表
  8. qnap nas web php,如何在QNAP NAS上建立并使用 iSCSI Target
  9. LINQ是死是活?——很奇怪为什么会有这样的话题?
  10. Condition 接口
  11. mapreduce实现矩阵相乘
  12. 1.6QQ拼音输入的亮点——相框
  13. C# 回调函数的实现和应用场景
  14. [ARC093-F][容斥原理][DP]Dark Horse
  15. supermap leaflet (二) 运动点及轨迹
  16. 攻防世界 web NaNNaNNaNNaN-Batman
  17. 开学季准备什么蓝牙耳机好?五款性价比高的蓝牙耳机品牌推荐
  18. 关于 数学 线性代数
  19. 点云配准 3- icp-交互式ICP点云配准
  20. 数学建模美赛、国赛真题训练——滑雪场的设计(原创)

热门文章

  1. 在ubuntu16下安装virtualenv+virtualenvwrapper
  2. 元素随着滚动条滚动而滚动,但是到浏览器窗口顶部的距离是固定的功能代码...
  3. Source insight 支持汇编
  4. CSS3动画之百度钱包
  5. LeetCode Gray Code 格雷码
  6. python2.x文件编码
  7. select下拉框美化
  8. LPS在无人机数据处理中的应用
  9. 2018秋寒假作业5—PTA编程总结2
  10. 项目忙还要过软考,您需要简练!