Spring Security系列四 自定義決策管理器(動態權限碼)

前言html

前面咱們已經實現了用戶的自定義登陸及密碼的加密,接下來就是動態的權限驗證了,也就是實現Spring Security的決策管理器AccessDecisionManager。web

權限資源 SecurityMetadataSourcespring

要實現動態的權限驗證,固然要先有對應的訪問權限資源了。Spring Security是經過SecurityMetadataSource來加載訪問時所須要的具體權限,因此第一步須要實現SecurityMetadataSource。數據庫

SecurityMetadataSource是一個接口,同時還有一個接口FilterInvocationSecurityMetadataSource繼承於它,但FilterInvocationSecurityMetadataSource只是一個標識接口,對應於FilterInvocation,自己並沒有任何內容:json

/**xcode

  • Marker interface for <code>SecurityMetadataSource</code> implementations that are
  • designed to perform lookups keyed on {@link FilterInvocation}s.
  • @author Ben Alex */ public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource { } 由於咱們作的通常都是web項目,因此實際須要實現的接口是FilterInvocationSecurityMetadataSource,這是由於Spring Security中不少web才使用的類參數類型都是FilterInvocationSecurityMetadataSource。

下面是一個自定義實現類CustomSecurityMetadataSource的示例代碼,它的主要責任就是當訪問一個url時返回這個url所須要的訪問權限。app

/**ide

  • Created by liyd on 16/12/9. */ public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; Map<String, Collection<ConfigAttribute>> metadataSource = CustomSecurityContext.getMetadataSource(); for (Map.Entry<String, Collection<ConfigAttribute>> entry : metadataSource.entrySet()) { String uri = entry.getKey(); RequestMatcher requestMatcher = new AntPathRequestMatcher(uri); if (requestMatcher.matches(fi.getHttpRequest())) { return entry.getValue(); } } return null; } public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } } getAttributes方法返回本次訪問須要的權限,能夠有多個權限。在上面的實現中若是沒有匹配的url直接返回null,也就是沒有配置權限的url默認都爲白名單,想要換成默認是黑名單隻要修改這裏便可。

getAllConfigAttributes方法若是返回了全部定義的權限資源,Spring Security會在啓動時校驗每一個ConfigAttribute是否配置正確,不須要校驗直接返回null。post

supports方法返回類對象是否支持校驗,web項目通常使用FilterInvocation來判斷,或者直接返回true。測試

在上面咱們主要定義了兩個權限碼:

user=/user admin=/admin 也就是CustomSecurityContext.getMetadataSource()加載的內容,主要加載代碼以下:

ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); Resource[] resources = resourcePatternResolver.getResources("classpath*:/security/*.properties"); if (ArrayUtils.isEmpty(resources)) { return; } Properties properties = new Properties(); for (Resource resource : resources) { properties.load(resource.getInputStream()); } for (Map.Entry<Object, Object> entry : properties.entrySet()) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); String[] values = StringUtils.split(value, ","); Collection<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); ConfigAttribute configAttribute = new SecurityConfig(key); configAttributes.add(configAttribute); for (String v : values) { METADATA_SOURCE_MAP.put(StringUtils.trim(v), configAttributes); } } 這裏咱們把權限的配置信息寫在了properties文件中,固然你也能夠存在數據庫中或其它任何地方。

在加載的時候,這裏的url是key,value是訪問須要的權限碼,一個權限碼能夠對應多個url,一個url也能夠有多個權限碼,想要怎麼玩均可以在這裏實現,示例中只是最簡單的。

權限決策 AccessDecisionManager

有了權限資源,知道了當前訪問的url須要的具體權限,接下來就是決策當前的訪問是否能經過權限驗證了。

這須要經過實現自定義的AccessDecisionManager來實現。Spring Security內置的幾個AccessDecisionManager就不講了,在web項目中基本用不到。

如下是示例代碼:

public class CustomAccessDecisionManager implements AccessDecisionManager { public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while (iterator.hasNext()) { if (authentication == null) { throw new AccessDeniedException("當前訪問沒有權限"); } ConfigAttribute configAttribute = iterator.next(); String needCode = configAttribute.getAttribute(); Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); for (GrantedAuthority authority : authorities) { if (StringUtils.equals(authority.getAuthority(), "ROLE_" + needCode)) { return; } } } throw new AccessDeniedException("當前訪問沒有權限"); } public boolean supports(ConfigAttribute attribute) { return true; } public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } } 一樣的也有三個方法,其它兩個和SecurityMetadataSource相似,這裏主要講講decide方法。

decide方法的三個參數中:

authentication包含了當前的用戶信息,包括擁有的權限。這裏的權限來源就是前面登陸時UserDetailsService中設置的authorities。 object就是FilterInvocation對象,能夠獲得request等web資源。 configAttributes是本次訪問須要的權限。 上面的實現中,當須要多個權限時只要有一個符合則校驗經過,即或的關係,想要並的關係只須要修改這裏的邏輯便可。

配置使用自定義實現類

上面權限的資源和驗證咱們已經都實現了,接下來就是指定讓Spring Security使用咱們自定義的實現類了。

在之前xml的配置中,通常都是本身實現一個FilterSecurityInterceptor,而後注入自定義的SecurityMetadataSource和AccessDecisionManager,就像下面這樣:

<b:bean id="customFilterSecurityInterceptor" class="com.dexcoder.security.CustomFilterSecurityInterceptor">
<b:property name="authenticationManager" ref="customAuthenticationManager" />
<b:property name="accessDecisionManager" ref="customAccessDecisionManager" />
<b:property name="securityMetadataSource" ref="customSecurityMetadataSource" />
</b:bean> 在Spring Boot的JavaConfig中並無這樣的實現方式,可是提供了ObjectPostProcessor以讓用戶實現更多想要的高級配置。具體看下面代碼,注意withObjectPostProcessor部分:

@Bean public AccessDecisionManager accessDecisionManager() { return new CustomAccessDecisionManager(); } @Bean public FilterInvocationSecurityMetadataSource securityMetadataSource() { return new CustomSecurityMetadataSource(); } protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/", "/index").permitAll().anyRequest().authenticated().and().formLogin() .loginPage("/login").defaultSuccessUrl("/user").permitAll().and().logout().permitAll() .and().authorizeRequests().anyRequest().authenticated() .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { public <O extends FilterSecurityInterceptor> O postProcess(O fsi) { fsi.setAccessDecisionManager(accessDecisionManager()); fsi.setSecurityMetadataSource(securityMetadataSource()); return fsi; } }); } 主要是在建立默認的FilterSecurityInterceptor的時候把咱們的accessDecisionManager和securityMetadataSource設置進去,至於authenticationManager由於咱們已經聲明瞭authenticationProvider並設置了userDetailService,因此這裏能夠省去。

既然扯到了FilterSecurityInterceptor這裏再嘮叨兩句,Spring Security內部默認主要有三個實現,見下圖:

Spring Security Interceptor

AspectJMethodSecurityInterceptor和MethodSecurityInterceptor在spring-security-core包內,FilterSecurityInterceptor在spring-security-web包內,這也說明FilterSecurityInterceptor是web項目專用的。

在前面默認的實現中,Controller上加註解@PreAuthorize使用的是MethodSecurityInterceptor,可是在通過咱們一番改造後,已經使用了FilterSecurityInterceptor,MethodSecurityInterceptor已經沒用到了。

固然你須要把Controller方法上的註解去掉:

@RequestMapping(value = "/admin", method = RequestMethod.GET) // @PreAuthorize("hasAnyRole('admin')") public String helloAdmin() { return "admin"; } @RequestMapping(value = "/user", method = RequestMethod.GET) // @PreAuthorize("hasAnyRole('admin','user')") public String helloUser() { return "user"; } 測試

由於原來Controller中的helloUser方法註解上,hasAnyRole中有兩個值,所在這裏在受權的時候也須要受權兩個,也就是數據庫中admin要多加一條權限記錄。

隨後訪問不一樣的url,發現跟基本同樣,也是在咱們預期當中。

最後

到這裏通常狀況下已經夠用了,可是項目中每每會有不少「奇葩」的需求,下一章將講解如何實現自定義的登陸過濾器,實現各類客戶端各類方式的登陸,如區分手勢密碼、指紋登陸及正常html外的.json、.xml等方式的訪問。

附件列表

security-demo(4).zip 標籤: Spring Security 權限控制

相關文章
相關標籤/搜索