在本例中,主要讲解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,无论成功失败,一般的实现都是转发或者重定向等处理。

   @Override
public 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形式的token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(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,根据用户名可以查到用户并返回的方法。

@Override
public 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;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
@Override
public void destroy() {
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public 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 获取所需权限

    @Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//获取当前访问url
String 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 鉴权

    @Override
public 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()); @Autowired
private UserDetailsService userDetailsService; @Autowired
private PropertySource propertySourceBean; @Override
protected 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
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(getDaoAuthenticationProvider());
} /**
* spring security 配置
* @return
*/
@Bean
public CustomLoginAuthEntryPoint getCustomLoginAuthEntryPoint() {
return new CustomLoginAuthEntryPoint();
} /**
* 用户验证
* @return
*/
@Bean
public DaoAuthenticationProvider getDaoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setHideUserNotFoundExceptions(false);
provider.setPasswordEncoder(new BCryptPasswordEncoder());
return provider;
} /**
* 登陆
* @return
*/
@Bean
public 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;
} @Bean
public CustomLoginAuthSuccessHandler getCustomLoginAuthSuccessHandler() {
CustomLoginAuthSuccessHandler handler = new CustomLoginAuthSuccessHandler();
if (propertySourceBean.getProperty("security.successUrl")!=null){
handler.setAuthSuccessUrl(propertySourceBean.getProperty("security.successUrl").toString());
}
return handler;
} /**
* 登出
* @return
*/
@Bean
public CustomLogoutSuccessHandler getCustomLogoutSuccessHandler() {
CustomLogoutSuccessHandler handler = new CustomLogoutSuccessHandler();
if (propertySourceBean.getProperty("security.logoutSuccessUrl")!=null){
handler.setLoginUrl(propertySourceBean.getProperty("security.logoutSuccessUrl").toString());
}
return handler;
} /**
* 过滤器
* @return
*/
@Bean
public 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;
} @Bean
public CustomMetadataSourceService getCustomMetadataSourceService() {
CustomMetadataSourceService sourceService = new CustomMetadataSourceService();
if (propertySourceBean.getProperty("security.successUrl")!=null){
sourceService.setIndexUrl(propertySourceBean.getProperty("security.successUrl").toString());
}
return sourceService;
}
}

spring-security权限控制详解的更多相关文章

  1. 自定义Spring Security权限控制管理(实战篇)

    上篇<话说Spring Security权限管理(源码)>介绍了Spring Security权限控制管理的源码及实现,然而某些情况下,它默认的实现并不能满足我们项目的实际需求,有时候需要 ...

  2. spring security 注解@EnableGlobalMethodSecurity详解

     1.Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解 ...

  3. Odoo权限控制详解

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/10826105.html 一:Odoo中的权限设置主要有以下5种 1)菜单.报表的访问权限 Odoo可以设置菜 ...

  4. Spring Security权限控制

    Spring Security官网 : https://projects.spring.io/spring-security/ Spring Security简介: Spring Security是一 ...

  5. spring security xml配置详解

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

  6. thinkphp基于角色的权限控制详解

    一.什么是RBAC 基于角色的访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注. 在RBAC中,权限与角色相关联,用户通 ...

  7. 若依管理系统RuoYi-Vue(二):权限系统设计详解

    若依Vue系统中的权限管理部分的功能都集中在了系统管理菜单模块中,如下图所示.其中权限部分主要涉及到了用户管理.角色管理.菜单管理.部门管理这四个部分. 一.若依Vue系统中的权限分类 根据观察,若依 ...

  8. spring框架 AOP核心详解

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...

  9. Spring Boot 集成 FreeMarker 详解案例(十五)

    一.Springboot 那些事 SpringBoot 很方便的集成 FreeMarker ,DAO 数据库操作层依旧用的是 Mybatis,本文将会一步一步到来如何集成 FreeMarker 以及配 ...

随机推荐

  1. Spoken English Practice(1、This is between you and me, Don't let it out. 2、Don't let your dreams be dreams, no matter how hard it gets, say to yourself, I'm going to make it.)

    绿色:连读:                  红色:略读:               蓝色:浊化:               橙色:弱读     下划线_为浊化 口语蜕变(2017/7/12) ...

  2. However, a general purpose protocol or its implementation sometimes does not scale very well.

    https://netty.io/wiki/user-guide-for-4.x.html The Problem Nowadays we use general purpose applicatio ...

  3. qt and redis desktop manager

    (ubuntu desktop)http://blog.csdn.net/ficksong/article/details/7497827 redis manager in ubuntu wget h ...

  4. 使用JCONSOLE远程监控JVM

    启动JMS服务 JConsole是从Java 5中开始引入的一个用于对JVM性能和资源消耗进行监控的图形化工具.JConsole可以连接本地的Java程序,也可以连接远程的Java程序.由于是GUI的 ...

  5. AntiSamy测试

    AntiSamy为owasp针对xss提供的处理库,可以配置xml策略来决定过滤的内容,比如标签.属性.css等,自定义策略给开发人员使用成本比较高,AntiSamy也提供了几个内置的策略,其安全级别 ...

  6. qemu进程页表和EPT的同步问题

    背景分析: 在之前分析EPT violation的时候,没有太注意qemu进程页表和EPT的关系,从虚拟机运行过程分析,虚拟机访存使用自身页表和EPT完成地址转换,没有用到qemu进程页表,所以也就想 ...

  7. git学习------>如何汉化GitLab?

    在上一篇博客中,已经正常安装好了GitLab,然而全部界面都是纯英文的,为了照顾整个团队的英文水平,因此这篇博客的目的是将纯英文的GitLab进行汉化. 纯英文界面 第一步: 确认GitLab版本号 ...

  8. HTML容易遗忘内容(二)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> </head> ...

  9. 子集和问题(应用--换零钱)POJ2229:Sumsets

    我一直在纠结换零钱这一类型的题目,今天好好絮叨一下,可以说他是背包的应用,也可以说他是单纯的dp.暂且称他为dp吧. 先上一道模板题目. sdut2777: 小P的故事——神奇的换零钱 题目描述 已知 ...

  10. hashmap,ConcurrentHashMap与hashtable的区别

    1.hashmap与hashtable的区别 1.我们从他们的定义就可以看出他们的不同,HashTable基于Dictionary类,而HashMap是基于AbstractMap.Dictionary ...