在Spring Security中實現通過數據庫動態配置url資源權限,需要通過配置驗證過濾器來實現資源權限的加載、驗證。系統啟動時,到數據庫加載系統資源權限列表,當有請求訪問時,通過對比系統資源權限列表和用戶資源權限列表(在用戶登錄時添加到用戶信息中)來判斷用戶是否有該url的訪問權限。
在配置驗證過濾器時需要的配置項有如下幾個:
- filterSecurityInterceptor:通過繼承AbstractSecurityInterceptor並實現Filter接口自定義一個驗證過濾器,替換默認驗證過濾器。
- accessDecisionManager:通過實現AccessDecisionManager接口自定義一個決策管理器,判斷是否有訪問權限。判斷邏輯可以寫在決策管理器的決策方法中,也可以通過投票器實現,除了框架提供的三種投票器還可以添加自定義投票器。自定義投票器通過實現AccessDecisionVoter接口來實現。
- securityMetadataSource:實現FilterInvocationSecurityMetadataSource接口,在實現類中加載資源權限,並在filterSecurityInterceptor中注入該實現類。
- WebSecurityConfig:系統配置類,需要在配置類中配置啟用filterSecurityInterceptor。
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對應的權限。
@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來實現一個自定義驗證過濾器。
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 配置進來。最后還需要在系統配置文件中啟用該驗證過濾器。
@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; } }
代碼中注釋標紅的部分為配置自定義驗證過濾器需要注意的地方。
* 這里是在授權服務器中實現的動態配置資源權限,在資源服務器中方法一樣。