250.Spring Boot+Spring Security:基于URL动态权限:自定义AccssDesionManager
Posted SpringBoot
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了250.Spring Boot+Spring Security:基于URL动态权限:自定义AccssDesionManager相关的知识,希望对你有一定的参考价值。
需求缘起
在上一篇我们通过扩展access()的SpEL表达式实现了
动态权限控制,本节将通过AccessDesionManager进行实现动态权限,
代码是基于《基于URL动态权限:准备工作》在往下编码。
Spring Security的简单原理
SpringSecurity使用众多的拦截器对url拦截,以此来管理权限,这里主要讲里面核心流程的两个:
(1)登陆验证拦截器
AuthenticationProcessingFilter。
(2)资源管理拦截器
AbstractSecurityInterceptor。
对于拦截器里面的实现是由
AuthenticationManager、accessDecisionManager
等组件来支撑的。
对于这个基本的认知,我们来理一下整个流程:
(1)用户登陆,会被 AuthenticationProcessingFilter拦截;
(2)AuthenticationProcessingFilter会调用AuthenticationManager的实现;
(3)AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用;
(4)访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截;
(5)会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限;
(6)再调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
这里我们先有一个基本的认识,主要是为了方便我们编码的理解,在之后的章节中我们重点介绍下SpringSecurity的原理。
编码思路
对于URL动态权限配置,主要解决如下几个问题:
(1)基于URL的用户权限信息保存在哪里:需要定义一张权限表,保存权限信息,然后角色和权限有一个关联关系。
(2)怎么加载用户的权限信息:仍然是通过loadUserByUsername进行加载,用户的权限信息这块的编码不变。
(3)URL对应的权限配置:这个主要是通过FilterInvocationSecurityMetadataSource进行配置。
(4)如何决定某一个用户是否有权限访问某个URL : 自定义AccessDecisionManager类的decide方法,决定某一个用户是否有权限访问某个URL。
(5)如何使用自定义的处理器:使用HttpSecurity的withObjectPostProcessor进行指定。
一、基于URL动态权限配置
接下来我们看下具体的编码步骤,我们的代码在基本工作之后进行编码,所以权限的实体类,初始化数据就不重复说明了。
1.1 加载权限信息
继承FilterInvocationSecurityMetadataSource重写getAttributes的方法进行通过uri获取权限配置信息:
package com.kfit.config;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
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.security.web.util.matcher.AntPathRequestMatcher;
import com.kfit.permission.service.PermissionService;
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource{
@Autowired
private PermissionService permissionService;
/**
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法。
* object-->FilterInvocation
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
System.out.println("MyFilterInvocationSecurityMetadataSource.getAttributes()");
Map<String, Collection<ConfigAttribute>> map = permissionService.getPermissionMap();
FilterInvocation filterInvocation = (FilterInvocation) object;
System.out.println(filterInvocation.getFullRequestUrl());
if (isMatcherAllowedRequest(filterInvocation)) return null ; //return null 表示允许访问,不做拦截
HttpServletRequest request = filterInvocation.getHttpRequest();
String resUrl;
//URL规则匹配.
AntPathRequestMatcher matcher;
for(Iterator<String> it = map.keySet().iterator();it.hasNext();) {
resUrl = it.next();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
System.out.println(map.get(resUrl));
return map.get(resUrl);
}
}
//SecurityConfig.createList("ROLE_USER");
//方式一:没有匹配到,直接是白名单了.不登录也是可以访问的。
//return null;
//方式二:配有匹配到,需要指定相应的角色:
return SecurityConfig.createList("ROLE_admin");
}
/**
* 判断当前请求是否在允许请求的范围内
* @param fi 当前请求
* @return 是否在范围中
*/
private boolean isMatcherAllowedRequest(FilterInvocation fi){
return allowedRequest().stream().map(AntPathRequestMatcher::new)
.filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest()))
.toArray().length > 0;
}
/**
* @return 定义允许请求的列表
*/
private List<String> allowedRequest(){
return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
1.2 权限校验
继承AccessDecisionManager重写decide方法进行编码:
package com.kfit.config;
import java.util.Collection;
import java.util.Iterator;
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;
public class MyAccessDecisionManager implements AccessDecisionManager{
/**
* 方法是判定是否拥有权限的决策方法,
* (1)authentication 是释CustomUserService中循环添加到 GrantedAuthority 对象中的权限信息集合.
* (2)object 包含客户端发起的请求的requset信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* (3)configAttributes 为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
System.out.println("MyAccessDecisionManager.decide()");
if(configAttributes == null || configAttributes.size()==0) {
throw new AccessDeniedException("permission denied");
}
ConfigAttribute cfa;
String needRole;
//遍历基于URL获取的权限信息和用户自身的角色信息进行对比.
for(Iterator<ConfigAttribute> it=configAttributes.iterator();it.hasNext();) {
cfa = it.next();
needRole = cfa.getAttribute();
System.out.println("decide,needRole:"+needRole+",authentication="+authentication);
//authentication 为CustomUserDetailService中添加的权限信息.
for(GrantedAuthority grantedAuthority:authentication.getAuthorities()) {
if(needRole.equals(grantedAuthority.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("permission denied");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
1.3 指定配置类
在WebSecurityConfig类中指定我们刚编码的配置类,核心类是使用withObjectPostProcessor进行指定:
package com.kfit.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
.antMatchers("/login").permitAll()// 设置所有人都可以访问登录页面
.antMatchers("/","/index").permitAll()
.antMatchers("/test/**","/test1/**").permitAll()
.antMatchers("/res/**/*.{js,html}").permitAll()
.withObjectPostProcessor(new MyObjectPostProcessor())
.anyRequest().authenticated() // 任何请求,登录后可以访问
.and()
.formLogin().loginPage("/login")
;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource() {
return new MyFilterInvocationSecurityMetadataSource();
}
@Bean
public MyAccessDecisionManager accessDecisionManager() {
return new MyAccessDecisionManager();
}
private class MyObjectPostProcessor implements ObjectPostProcessor<FilterSecurityInterceptor> {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
fsi.setSecurityMetadataSource(filterInvocationSecurityMetadataSource());
fsi.setAccessDecisionManager(accessDecisionManager());
return fsi;
}
}
}
到这里就可以测试下效果了,和之前的结果应该是一样的。
历史文章
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
à悟空学院:http://t.cn/Rg3fKJD
学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!
SpringBoot视频:http://t.cn/R3QepWG
Spring Cloud视频:http://t.cn/R3QeRZc
SpringBoot Shiro视频:http://t.cn/R3QDMbh
SpringBoot交流平台:http://t.cn/R3QDhU0
SpringData和JPA视频:http://t.cn/R1pSojf
SpringSecurity5.0视频:http://t.cn/EwlLjHh
Sharding-JDBC分库分表实战:http://t.cn/E4lpD6e
以上是关于250.Spring Boot+Spring Security:基于URL动态权限:自定义AccssDesionManager的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot??????????????????Spring boot??????MySql,Mybatis???PageHelper??????