SpringSecurity實現動態url攔截(基於rbac模型)
後續會講解如何實現方法攔截。其實與url攔截大同小異。
攔截方法,會更簡單一點吧 基於PermissionEvaluator 可以自行先瞭解
1、瞭解主要的過濾器
1、SecurityMetadataSource
權限資源攔截器。
有一個接口繼承與它FilterInvocationSecurityMetadataSource,但FilterInvocationSecurityMetadataSource隻是一個標識接口,
對應於FilterInvocation,本身並無任何內容:
主要方法:
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { }
每一次請求url,都會調用這個方法。object存儲瞭請求的信息。如;rul
2、UserDetailsService
用戶登錄,會先調用這裡面的 loadUserByUsername。通過用戶名去查詢用戶是否存在數據庫。
在這裡面進行查詢,獲得用戶權限信息
3、AccessDecisionManager
裡面的decide方法。
// decide 方法是判定是否擁有權限的決策方法, //authentication 是釋UserService中循環添加到 GrantedAuthority 對象中的權限信息集合. //object 包含客戶端發起的請求的requset信息 ,可轉換為 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); //configAttributes 為MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法返回的結果, 此方法是為瞭判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 decide 方法, 用來判定用戶是否有此權限。如果不在權限表中則放行。 @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes)
2、正式實戰瞭
1 使用idea的Srping Initializr 創建一個項目 我的版本如下Pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <mybatis.version>3.2.7</mybatis.version> <mybatis-spring.version>1.2.2</mybatis-spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <!--提供security相關標簽,可選可不選--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> <!--bootstrap組件,可選可不選--> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.3.7</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <!--mybatis--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>${mybatis-spring.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2,創建一個springSecurity配置類,你也可以使用配置文件的方法。我這裡使用瞭boot的配置類
package com.example.config; import com.example.service.CustomUserService; import com.example.service.MyFilterSecurityInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; @Configuration //聲明為配置類 @EnableWebSecurity //這裡啟動security public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{ @Autowired //下面會編寫這個類 private MyFilterSecurityInterceptor myFilterSecurityInterceptor; @Autowired //下面會編寫這個類 private DefaultAccessDeniedHandler defaultAccessDeniedHandler; @Bean UserDetailsService customUserService(){ //註冊UserDetailsService 的bean return new CustomUserService(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(customUserService()); //user Details Service驗證 } @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .accessDeniedHandler(defaultAccessDeniedHandler); http.authorizeRequests() .antMatchers("/css/**").permitAll() .anyRequest().authenticated() //任何請求,登錄後可以訪問 .and() .formLogin().loginPage("/login").failureUrl("/login?error").permitAll() //登錄頁面用戶任意訪問 .and() .logout().permitAll(); //註銷行為任意訪問 http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class); } }
3、自定義SecurityMetadataSource攔截器
package com.example.service; import com.example.dao.PermissionDao; import com.example.domain.Permission; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.util.*; /** */ @Service public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource { private HashMap<String, Collection<ConfigAttribute>> map =null; @Autowired private PermissionDao permissionDao; /** * 自定義方法。最好在項目啟動時,去數據庫查詢一次就好。 * 數據庫查詢一次 權限表出現的所有要攔截的url */ public void loadResourceDefine(){ map = new HashMap<>(); Collection<ConfigAttribute> array; ConfigAttribute cfg; //去數據庫查詢 使用dao層。 你使用自己的即可 List<Permission> permissions = permissionDao.findAll(); for(Permission permission : permissions) { array = new ArrayList<>(); //下面你可以添加你想要比較的信息過去。 註意的是,需要在用戶登錄時存儲的權限信息一致 cfg = new SecurityConfig(permission.getName()); //此處添加瞭資源菜單的名字,例如請求方法到ConfigAttribute的集合中去。此處添加的信息將會作為MyAccessDecisionManager類的decide的第三個參數。 array.add(cfg); //用權限的getUrl() 作為map的key,用ConfigAttribute的集合作為 value, map.put(permission.getUrl(), array); } } //此方法是為瞭判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 decide 方法,用來判定用戶是否有此權限。如果不在權限表中則放行。 @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation filterInvocation = (FilterInvocation) object; String fullRequestUrl = filterInvocation.getFullRequestUrl(); //若是靜態資源 不做攔截 下面寫瞭單獨判斷靜態資源方法 if (isMatcherAllowedRequest(filterInvocation)) { System.out.println("我沒有被攔截"+fullRequestUrl); return null; } //map 為null 就去數據庫查 if(map ==null) { loadResourceDefine(); } //測試 先每次都查 //object 中包含用戶請求的request 信息 HttpServletRequest request = filterInvocation.getHttpRequest(); AntPathRequestMatcher matcher; String resUrl; for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) { resUrl = iter.next(); matcher = new AntPathRequestMatcher(resUrl); if(matcher.matches(request)) { return map.get(resUrl); } } return null; } /** * 判斷當前請求是否在允許請求的范圍內 * @param fi 當前請求 * @return 是否在范圍中 */ private boolean isMatcherAllowedRequest(FilterInvocation fi){ return allowedRequest().stream().map(AntPathRequestMatcher::new) .filter(requestMatcher -> requestMatcher.matches(fi.getHttpRequest())) .toArray().length > 0; } /** * @return 定義允許請求的列表 */ private List<String> allowedRequest(){ return Arrays.asList("/login","/css/**","/fonts/**","/js/**","/scss/**","/img/**"); } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> clazz) { return true; } }
自定義UserDetailsService 。登錄的時候根據用戶名去數據庫查詢用戶擁有的權限信息
package com.example.service; import com.example.dao.PermissionDao; import com.example.dao.UserDao; import com.example.domain.Permission; import com.example.domain.SysRole; import com.example.domain.SysUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /** * Created by yangyibo on 17/1/18. */ @Service public class CustomUserService implements UserDetailsService { //自定義UserDetailsService 接口 @Autowired UserDao userDao; @Autowired PermissionDao permissionDao; public UserDetails loadUserByUsername(String username) { SysUser user = userDao.findByUserName(username); for (SysRole role : user.getRoles()) { System.out.println(role.getName()); } if (user != null) { //根據用戶id 去查找用戶擁有的資源 List<Permission> permissions = permissionDao.findByAdminUserId(user.getId()); List<GrantedAuthority> grantedAuthorities = new ArrayList <>(); for (Permission permission : permissions) { if (permission != null && permission.getName()!=null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName()); //1:此處將權限信息添加到 GrantedAuthority 對象中,在後面進行全權限驗證時會使用GrantedAuthority 對象。 grantedAuthorities.add(grantedAuthority); } } return new User(user.getUsername(), user.getPassword(), grantedAuthorities); } else { throw new UsernameNotFoundException("admin: " + username + " do not exist!"); } } }
自定義AccessDecisionManager
package com.example.service; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.authentication.InsufficientAuthenticationException; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Iterator; @Service public class MyAccessDecisionManager implements AccessDecisionManager { // decide 方法是判定是否擁有權限的決策方法, //authentication 是釋CustomUserService中循環添加到 GrantedAuthority 對象中的權限信息集合. //object 包含客戶端發起的請求的requset信息,可轉換為 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest(); //configAttributes 為MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法返回的結果,此方法是為瞭判定用戶請求的url 是否在權限表中,如果在權限表中,則返回給 decide 方法,用來判定用戶是否有此權限。如果不在權限表中則放行。 @Override public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if (null == configAttributes || configAttributes.size() <= 0 ) { return; } ConfigAttribute c; String needRole; for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) { c = iter.next(); needRole = c.getAttribute(); for (GrantedAuthority ga : authentication.getAuthorities()) { //authentication 為在註釋1 中循環添加到 GrantedAuthority 對象中的權限信息集合 if (needRole.trim().equals(ga.getAuthority())) { return; } } } throw new AccessDeniedException("no right"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
自定義攔截器
package com.example.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.SecurityMetadataSource; import org.springframework.security.access.intercept.AbstractSecurityInterceptor; import org.springframework.security.access.intercept.InterceptorStatusToken; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.stereotype.Service; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; @Service public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { @Autowired private FilterInvocationSecurityMetadataSource securityMetadataSource; @Autowired private void setMyAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) { super.setAccessDecisionManager(myAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain); invoke(fi); } public void invoke(FilterInvocation fi) throws IOException, ServletException { //fi裡面有一個被攔截的url //裡面調用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個方法獲取fi對應的所有權限 //再調用MyAccessDecisionManager的decide方法來校驗用戶的權限是否足夠 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; } }
運行項目就實現瞭。去試試吧。
記得將自定義攔截器放進security的過濾器鏈中。
到此這篇關於SpringSecurity實現動態url攔截(基於rbac模型)的文章就介紹到這瞭,更多相關SpringSecurity 動態url攔截內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- springboot+springsecurity如何實現動態url細粒度權限認證
- springboot2.5.2與 flowable6.6.0整合流程引擎應用分析
- Spring Security實現自定義訪問策略
- Spring Security動態權限的實現方法詳解
- 淺談SpringSecurity基本原理