前言html
前面咱們已經實現了用戶的自定義登陸及密碼的加密,接下來就是動態的權限驗證了,也就是實現Spring Security的決策管理器AccessDecisionManager。web
權限資源 SecurityMetadataSourcespring
要實現動態的權限驗證,固然要先有對應的訪問權限資源了。Spring Security是經過SecurityMetadataSource來加載訪問時所須要的具體權限,因此第一步須要實現SecurityMetadataSource。數據庫
SecurityMetadataSource是一個接口,同時還有一個接口FilterInvocationSecurityMetadataSource繼承於它,但FilterInvocationSecurityMetadataSource只是一個標識接口,對應於FilterInvocation,自己並沒有任何內容:json
/**xcode
下面是一個自定義實現類CustomSecurityMetadataSource的示例代碼,它的主要責任就是當訪問一個url時返回這個url所須要的訪問權限。app
/**ide
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 權限控制