SpringSecurity动态url拦截 SpringSecurity实现动态url拦截(基于rbac模型)
wx59ef4723c1b54 人气:5想了解SpringSecurity实现动态url拦截(基于rbac模型)的相关内容吗,wx59ef4723c1b54在本文为您仔细讲解SpringSecurity动态url拦截的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:SpringSecurity动态url拦截,SpringSecurity,url拦截,下面大家一起来学习吧。
后续会讲解如何实现方法拦截。其实与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的过滤器链中。
加载全部内容