在本例中,主要讲解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. 后端UI框架

    BootStrap EasyUI DWZ ExtJS

  2. How an event flows in a pipeline Netty Internal I/O Threads (Transport Implementation)

    C:\Users\sas\.m2\repository\io\netty\netty-all\4.1.30.Final\netty-all-4.1.30.Final-sources.jar!\io\n ...

  3. http 服务器编程 适配器

    小结: 1.HandleFunc 只是一个适配器 go http 服务器编程(1) - 云+社区 - 腾讯云 https://cloud.tencent.com/developer/article/1 ...

  4. SmokePing安装手册

    SmokePing安装部署 SmokePing简介 Smokeping是一款用于网络性能监测的开源监控软件,主要用于对IDC的网络状况,网络质量,稳定性等做检测,通过rrdtool制图方式,图形化地展 ...

  5. nginx.conf常用配置解析

    一.全局配置 user username groupname 运行用户及用户组 worker_processes auto 启动进程,通常设置成和cpu数量相等的一个数值,默认为1.可以设置为auto ...

  6. 【云安全与同态加密_调研分析(4)】云计算安全领域主要研究成果——By Me

    下表列举了在云安全问题研究表现突出的ICT公司和研究机构以及其在云计算安全方面主要研究成果: ◆ICT公司和研究机构(云计算安全领域主要研究成果)◆ ◆机构名称◆ ◆机构类别◆ ◆主要研究成果◆ ◆备 ...

  7. Oracle trigger 触发器

    触发器使用教程和命名规范 目  录触发器使用教程和命名规范 11,触发器简介 12,触发器示例 23,触发器语法和功能 34,例一:行级触发器之一 45,例二:行级触发器之二 46,例三:INSTEA ...

  8. 20165324 学习基础与C语言学习心得

    20165324 技能学习心得与C语言学习 一.读后感及技能学习 做中学读后感 我认为给学生具体的, 能实践的, 能马上看到因果关系的教材和练习, 是激发学生兴趣, 好奇心, 求知欲的好方法. 我就是 ...

  9. 关于softnet的加密硬件狗 也就是所谓的赛孚耐

    SuperDog-R-2.2.1.iso 上面那个文件就是光盘里面的东西.你买了他们的产品 自然后带着这个玩意. 按照默认路径安装一下. 安装完毕如下图:

  10. PKU 1573 Robot Motion(简单模拟)

    原题大意:原题链接 给出一个矩阵(矩阵中的元素均为方向英文字母),和人的初始位置,问是否能根据这些英文字母走出矩阵.(因为有可能形成环而走不出去) 此题虽然属于水题,但是完全独立完成而且直接1A还是很 ...