Spring Security之动态配置资源权限

时间:2024-01-08 13:11:26

  在Spring Security中实现通过数据库动态配置url资源权限,需要通过配置验证过滤器来实现资源权限的加载、验证。系统启动时,到数据库加载系统资源权限列表,当有请求访问时,通过对比系统资源权限列表和用户资源权限列表(在用户登录时添加到用户信息中)来判断用户是否有该url的访问权限。

  在配置验证过滤器时需要的配置项有如下几个:

  • filterSecurityInterceptor:通过继承AbstractSecurityInterceptor并实现Filter接口自定义一个验证过滤器,替换默认验证过滤器。
  • accessDecisionManager:通过实现AccessDecisionManager接口自定义一个决策管理器,判断是否有访问权限。判断逻辑可以写在决策管理器的决策方法中,也可以通过投票器实现,除了框架提供的三种投票器还可以添加自定义投票器。自定义投票器通过实现AccessDecisionVoter接口来实现。
  • securityMetadataSource:实现FilterInvocationSecurityMetadataSource接口,在实现类中加载资源权限,并在filterSecurityInterceptor中注入该实现类。
  • WebSecurityConfig:系统配置类,需要在配置类中配置启用filterSecurityInterceptor。

  

一、配置securityMetadataSource

  securityMetadataSource这里简单理解为资源权限数据源,主要维护系统的资源权限信息。系统启动时,可以将权限资源信息从配置文件、数据库中加载到内存。我们在数据库中维护了权限信息,所以这里从数据库中加载资源权限,如果有需要,在系统权限变动时可以直接反馈到内存。

@Service
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private PermissionMapper permissionMapper; /**
* 资源权限
*/
private volatile HashMap<String, Collection<ConfigAttribute>> urlPermMap = null; @PostConstruct
public void init() {
loadResourceDefine();
} /**
* 加载资源,初始化资源变量
*/
public void loadResourceDefine() {
urlPermMap = new HashMap<>;
...
} @Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl(); // 资源权限为空,初始化资源
if (null == urlPermMap) {
synchronized (MyFilterInvocationSecurityMetadataSource.class) {
if (null == urlPermMap) {
loadResourceDefine();
}
}
} return urlPermMap.get(url);
} @Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
} @Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}

  创建一个 FilterInvocationSecurityMetadataSource 接口的实现类,在内部定义一个Map用来维护资源权限信息,bean创建的时候初始化Map。然后重写getAttributes()方法,决策器会调用该方法获取url对应的权限。

二、配置accessDecisionManager
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* 决策方法:权限判断
*
* @param authentication 用户的身份信息;
* @param object 包含客户端发起的请求的request信息,可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* @param configAttributes 是MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法返回的结果,
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法,用来判定用户是否有此权限;如果不在权限表中则放行。
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (Collections.isEmpty(configAttributes)) {
return;
}
for (GrantedAuthority ga : authentication.getAuthorities()) {
       if (configAttributes.contains(ga.getAuthority())){
return;
}
}
throw new AccessDeniedException(StatusCodeEnum.UNAUTHORIZED.getValue());
} @Override
public boolean supports(ConfigAttribute attribute) {
return true;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
}

  重写AccessDecisionManager 的decide()方法,在该方法中定义具体的判断逻辑,也可以通过定义投票器来实现。

三、配置filterSecurityInterceptor

  验证过滤器的功能实际是通过依赖的资源权限和决策管理器来实现的,参照默认的验证过滤器FilterSecurityInterceptor来实现一个自定义验证过滤器。

public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private MyFilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired
public void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
super.setAccessDecisionManager(myAccessDecisionManager);
} @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);
} /**
* @param fi 里面有一个被拦截的url,调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限,
* 再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
* @throws IOException
* @throws ServletException
*/
public void invoke(FilterInvocation fi) throws IOException, ServletException {
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 执行下一个拦截器
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
} finally {
super.afterInvocation(token, null);
}
} @Override
public void destroy() { } @Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
} @Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
return this.securityMetadataSource;
}
}

  这里创建一个自定义验证过滤器,然后将前面定义的MyFilterInvocationSecurityMetadataSource 和MyAccessDecisionManager 配置进来。最后还需要在系统配置文件中启用该验证过滤器。

四、配置WebSecurityConfig
@Configuration
@EnableWebSecurity // 禁用Spring Boot默认的Security配置,配合@Configuration启用自定义配置
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
   // 自定义决策管理器
@Autowired
private MyAccessDecisionManager myAccessDecisionManager; /*
* 加密工具
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} /*
* 认证管理器
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} /*
* 身份验证配置,用于注入自定义身份验证Bean和密码校验规则
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} /**
* 无需权限校验直接放行的路径
*/
private final String[] PATH_PASS = {
// 根据实际情况添加
}; /**
* Request层面的配置,对应XML Configuration中的<http>元素
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(PATH_PASS).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf().disable()
.httpBasic();
// 将自定义的过滤器配置在FilterSecurityInterceptor之前
http.addFilterBefore(myFilterSecurityInterceptor(), FilterSecurityInterceptor.class);
} /**
* Web层面的配置,一般用来配置无需权限校验的路径,也可以在HttpSecurity中配置,但是在web.ignoring()中配置效率更高。
* web.ignoring()是一个忽略的过滤器,而HttpSecurity中定义了一个过滤器链,即使permitAll()放行还是会走所有的过滤器,
* 直到最后一个过滤器FilterSecurityInterceptor认定是可以放行的,才能访问。
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/favor.ioc");
} /**
* 管理自定义的权限过滤器
*/
@Bean
public MyFilterSecurityInterceptor myFilterSecurityInterceptor() {
MyFilterSecurityInterceptor myFilterSecurityInterceptor = new MyFilterSecurityInterceptor();
myFilterSecurityInterceptor.setMyAccessDecisionManager(myAccessDecisionManager);
return myFilterSecurityInterceptor;
}
}

  代码中注释标红的部分为配置自定义验证过滤器需要注意的地方。

  * 这里是在授权服务器中实现的动态配置资源权限,在资源服务器中方法一样。