引言
上一篇文章实现了自定义用户动态登录,不同的用户必定有不同的用户权限和资源,我们需要根据不同的用户的角色以及资源,来给用于对应的权限控制。在查看本文章之前,需要实现了解RBAC权限设计思想和概念,以便更好的理解用户资源权限动态控制。
准备
定义好相关的数据表:用户表、角色表、资源表、用户角色关联表、角色资源关联表。此处以最基础的RBAC作为应用场景来实现。
实现
FilterSecurityInterceptor是SpringSecurity中最后一个Filter,作用非常重要,通过对FilterSecurityInterceptor安全拦截过滤接口的实现;组织资源角色关系的授权源(SecurityMetadataSource),过滤器将用户(User)的权限资源同授权源进行分析做出权限动态控制。
FilterSecurityInterceptor类的关系结构如下图:
SecurityMetadataSource
通过loadResourcesDefine方法构建权限资源对应的源数据,SecurityMetadataSource中getAttributes非常重要,通过getAttributes(Object o)方法返回对应的Object o的权限资源对应关系集合Collection<ConfigAttribute>,供AccessDecisionManager进行权鉴。
AccessDecisionManager
通过decide方法将Authentication用户的权限同Collection<ConfigAttribute>进行匹配来判断权限是否有效。
通过这两个核心做出权限动态控制。
代码示例
OursFilterSecurityInterceptor
public class OursFilterSecurityInterceptor extends FilterSecurityInterceptor {
private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
private FilterInvocationSecurityMetadataSource securityMetadataSource;
private boolean observeOncePerRequest = true;
public OursFilterSecurityInterceptor(){
System.out.println("4:OursFilterSecurityInterceptor");
}
@Override
public void init(FilterConfig filterConfig) {
System.out.println("安全拦截器4.1");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("安全拦截器4.2");
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
return this.securityMetadataSource;
}
@Override
public void destroy() {
System.out.println("安全拦截器4.3");
}
@Override
public Class<? extends Object> getSecureObjectClass() {
System.out.println("安全拦截器4.4");
return FilterInvocation.class;
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
System.out.println("安全拦截器4.5");
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
System.out.println("安全拦截器4.6");
return this.securityMetadataSource;
}
public void setSecurityMetadataSource(
FilterInvocationSecurityMetadataSource newSource) {
System.out.println("安全拦截器4.7");
this.securityMetadataSource = newSource;
}
}
OursSecurityMetadataSource
public class OursSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
/*public OursSecurityMetadataSource() {
//System.out.println("2:OursSecurityMetadataSource");
}*/
@Autowired
private IRoleService roleService;
@Autowired
private IAuthAccessService authAccessService;
@Autowired
private IMenuService menuService;
private AntPathMatcher urlMatcher = new AntPathMatcher();
//保存资源和权限的相应关系 key(资源url) value(权限)
private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
//此处需要根据 数据库结构调整进行改造,满足角色权限配置,同时满足超级管理员的实现
//(超级管理员Code默认设定为ROLE_ADMIN)
private void loadResourcesDefine() {
System.out.println("安全元数据2.1:开始载入资源列表数据");
resourceMap = new HashMap<String, Collection<ConfigAttribute>>();
List<AuthAccess> authAccesses = authAccessService.findAll();
Collection<ConfigAttribute> configAttributes;
for (AuthAccess authAccess : authAccesses) {
//根据资源列表获取对于的role信息
Role role = roleService.findById(authAccess.getRole_id());
ConfigAttribute configAttribute = new SecurityConfig(role.getCode());
//
if (resourceMap.containsKey(authAccess.getRule_name())) {
configAttributes = resourceMap.get(authAccess.getRule_name());
configAttributes.add(configAttribute);
} else {
configAttributes = new ArrayList<ConfigAttribute>();
configAttributes.add(configAttribute);
resourceMap.put(authAccess.getRule_name(), configAttributes);
}
}
//--加入超级权限对应菜单
List<Menu> menus = menuService.findAll();
ConfigAttribute configAttributeAdmin = new SecurityConfig("ROLE_ADMIN");
for (Menu menu : menus) {
if (resourceMap.containsKey(menu.getPermission())) {
configAttributes = resourceMap.get(menu.getPermission());
configAttributes.add(configAttributeAdmin);
} else {
configAttributes = new ArrayList<ConfigAttribute>();
configAttributes.add(configAttributeAdmin);
resourceMap.put(menu.getPermission(), configAttributes);
}
}
//--展示权限对应关系--
/*Set<String> set = resourceMap.keySet();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println("key:" + s + ",value:" + resourceMap.get(s));
}*/
//--
}
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
loadResourcesDefine();
//避免返回null值,否则AccessDecisionManager失效
Collection<ConfigAttribute> collection = new LinkedList<>();
//o 是一个URL?
String url = ((FilterInvocation) o).getRequestUrl();
System.out.println("请求地址为:" + url);
//--
Iterator<String> it = resourceMap.keySet().iterator();
while (it.hasNext()) {
String resUrl = it.next();
//
if (resUrl.indexOf("?") != -1) {
resUrl = resUrl.substring(0, resUrl.indexOf("?"));
}
if (urlMatcher.match(resUrl, url)) {
System.out.println("须要的权限是:" + resourceMap.get(resUrl));
return resourceMap.get(resUrl);
}
}
collection.add(new SecurityConfig("ROLE_ANONYMOUS"));
return collection;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
System.out.println("安全元数据2.3");
return null;
}
@Override
public boolean supports(Class<?> aClass) {
System.out.println("安全元数据2.4");
return true;
}
}
此处注意几点
@Autowired
private IRoleService roleService;
@Autowired
private IAuthAccessService authAccessService;
@Autowired
private IMenuService menuService;
该部分,是根据你自己的用户管理表接口来视情况去设计,不做赘述。
Collection<ConfigAttribute> getAttributes(Object o)
此处也往往是个坑,不要返回null值,当然你可以在OursAccessDecisionManager的decide中做判断。
OursAccessDecisionManager
public class OursAccessDecisionManager implements AccessDecisionManager {
public OursAccessDecisionManager() {
System.out.println("3:OursAccessDecisionManager");
}
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
//System.out.println("访问决策管理器3.1:验证用户是否具有一定的权限");
if (collection == null) {
return;
}
if (collection.size() <= 0) {
return;
}
Iterator<ConfigAttribute> it = collection.iterator();
String needResource = null;
while (it.hasNext()) {
ConfigAttribute authority = it.next();
if (authority == null || (needResource = authority.getAttribute()) == null) {
continue;
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority ga : authorities) {
if (needResource.equals(ga.getAuthority().trim())) {
return;
}
}
}
System.out.println("没有访问权限!");
throw new AccessDeniedException("没有访问权限!");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
//System.out.println("访问决策管理器2.2");
return true;
}
@Override
public boolean supports(Class<?> aClass) {
//System.out.println("访问决策管理器2.3");
return true;
}
}
配置
SecurityConfig
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler oursAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler oursAuthenticationFailureHandler;
@Autowired
private LogoutSuccessHandler oursLogoutSuccessHandler;
@Autowired
private AuthenticationEntryPoint oursAuthenticationEntryPoint;
@Autowired
private UserDetailsService oursUserDetailsService;
@Autowired
private PasswordEncoder oursPasswordEncoder;
@Autowired
private OursSecurityMetadataSource oursSecurityMetadataSource;
@Autowired
private OursAccessDecisionManager oursAccessDecisionManager;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/login","/logout").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/index")
//登录成功
//.successHandler(oursAuthenticationSuccessHandler)
//登录失败
//.failureHandler(oursAuthenticationFailureHandler)
.and()
.logout()
.logoutUrl("/logout")
//注销成功
//.logoutSuccessHandler(oursLogoutSuccessHandler)
.permitAll()
.and()
//.addFilterBefore(oursFilterSecurityInterceptor(), FilterSecurityInterceptor.class)
.headers().frameOptions().sameOrigin()
.and()
.exceptionHandling()
//未认证处理
//.authenticationEntryPoint(oursAuthenticationEntryPoint)
;
http
.addFilterAfter(oursFilterSecurityInterceptor(), ExceptionTranslationFilter.class);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers( "/css/**", "/images/**", "/js/**", "/plugins/**", "/fonts/**","/layui/**");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(oursUserDetailsService).passwordEncoder(oursPasswordEncoder);
}
private FilterSecurityInterceptor oursFilterSecurityInterceptor() throws Exception {
OursFilterSecurityInterceptor filterSecurityInterceptor = new OursFilterSecurityInterceptor();
filterSecurityInterceptor.setRejectPublicInvocations(false);
filterSecurityInterceptor.setAccessDecisionManager(oursAccessDecisionManager);
filterSecurityInterceptor.setAuthenticationManager(authenticationManager());
filterSecurityInterceptor.setSecurityMetadataSource(oursSecurityMetadataSource);
return filterSecurityInterceptor;
}
}
至此,自定义用户资源权限动态控制实现了,感谢你的阅读,希望这个学习总结对你有所帮助,也感谢你的关注,我们共同畅游在这冷冷的语言世界吧。