spring boot 与spring security 结合jwt实现动态权限管理
网上搜索的东西太杂太乱了,所以自己整了一下发给大家,仅供借鉴,原理什么的不做过多解释,流程什么的都在代码注释里
密码加密什么的没有做,直接用的明文密码,没有加密,太懒了
基本的mysql啥的不做过多解释
权限url写在数据库中
登录的话会把登录信息放入security 缓存中,
并且生成一个token,存储用户信息
访问受保护的url时需要带上token信息
查询yml配置,如果是不需要拦截的,直接放行,否则会查询该用户所拥有的权限,如果不存在则返回403
sql表
sql
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
-- 导出 表 test.menu 结构
CREATE TABLE IF NOT EXISTS `menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(50) DEFAULT NULL,
`role_id` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- 正在导出表 test.menu 的数据:~2 rows (大约)
/*!40000 ALTER TABLE `menu` DISABLE KEYS */;
INSERT INTO `menu` (`id`, `url`, `role_id`) VALUES
(1, '/account/all\r\n', '1'),
(2, '/account/abc', '1');
/*!40000 ALTER TABLE `menu` ENABLE KEYS */;
-- 导出 表 test.role 结构
CREATE TABLE IF NOT EXISTS `role` (
`id` int(11) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
`user_id` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 正在导出表 test.role 的数据:~1 rows (大约)
/*!40000 ALTER TABLE `role` DISABLE KEYS */;
INSERT INTO `role` (`id`, `name`, `user_id`) VALUES
(1, 'admin', 1);
/*!40000 ALTER TABLE `role` ENABLE KEYS */;
-- 导出 表 test.user 结构
CREATE TABLE IF NOT EXISTS `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL DEFAULT '0',
`password` varchar(200) NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- 正在导出表 test.user 的数据:~2 rows (大约)
/*!40000 ALTER TABLE `user` DISABLE KEYS */;
INSERT INTO `user` (`id`, `username`, `password`) VALUES
(1, 'admin', '123'),
(2, 'admini', '123');
/*!40000 ALTER TABLE `user` ENABLE KEYS */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
POM
<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>spring_security</groupId>
<artifactId>spring_security</artifactId>
<version>1.0.0</version>
<name>spring_security</name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
</parent>
<!-- 定义引入的POM包的版本号 -->
<properties>
<mybatis-spring-boot>1.2.0</mybatis-spring-boot>
<mysql-connector>5.1.39</mysql-connector>
<druid>1.0.18</druid>
<java.version>1.8</java.version>
<shiro-spring>1.4.0</shiro-spring>
<java-jwt>3.4.0</java-jwt>
</properties>
<dependencies>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- =============================================== -->
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-security
</artifactId>
</dependency>
<!-- 监控 -->
<!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId>
</dependency> -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- =============================================== -->
<!-- Spring Boot Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- MySQL 连接驱动依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector}</version>
</dependency>
<!-- Druid 数据连接池依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid}</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
<!-- 返回html页面 -->
<!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency> -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- springboot热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional><!-- 为true时热部署才会生效,可以不用写配置文件 -->
</dependency>
<!-- 代码生成 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
</dependencies>
<!-- 开启热部署 -->
<build>
<plugins>
<!-- =========================================== -->
<!-- spring热部署 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<!-- ===================================== -->
<!-- Srping Boot 打包工具 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.7.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- ===================================== -->
<!-- 指定JDK编译版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- ===================================== -->
</plugins>
</build>
</project>
yml
server:
port: 8082
logging:
level:
learning: trace
spring:
#数据源配置
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
connectionProperties: druid.stat.mergeSql=true
maxActive: 100
initialSize: 10
minIdle: 5
testWhileIdle: true
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 30000
validationQuery: SELECT 1
http:
encoding:
charset: UTF-8
jwt:
path: /user/**,/account/b
header: Authorization
secretkey: WZH1998&1998
expiration: 8640000000
authPrefix: auth
基础类
JwtProperties 权限配置类
JwtUser安全用户类型 需要实现 UserDetails
RoleUrlJWt 管理权限的 实现GrantedAuthority类
package com.qsh.filter;
import java.security.Key;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
@ConfigurationProperties(prefix="jwt") // jwt begin.
public class JwtProperties {
//header参数名
private String header;
//过期毫秒数
private Long expiration;
private String secretkey;
private String path;
private String authPrefix;
public String getAuthPrefix() {
return authPrefix;
}
public void setAuthPrefix(String authPrefix) {
this.authPrefix = authPrefix;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public Long getExpiration() {
return expiration;
}
public void setExpiration(Long expiration) {
this.expiration = expiration;
}
public String getSecretkey() {
return secretkey;
}
public void setSecretkey(String secretkey) {
this.secretkey = secretkey;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Key getSecrt() {
return new SecretKeySpec(secretkey.getBytes(),
SignatureAlgorithm.HS512.getJcaName());
}
}
=========================================================================================================
package com.qsh.filter;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 安全用户类型 需要实现 UserDetails
* @author Administrator
*
*/
@SuppressWarnings("all")
@Component
public class JwtUser implements UserDetails{
private Long id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities;//权限集合
/**
* id:主键
*
* @return the id
* @since Ver 1.0
*/
public Long getId() {
return id;
}
/**
* 返回用户的角色列表
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// TODO Auto-generated method stub
return authorities;
}
/**
* 账户密码:禁用序列化
*/
@JSONField(serialize=false)
@Override
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
/**
* 用户账号
*/
@Override
public String getUsername() {
// TODO Auto-generated method stub
return username;
}
/**
* 账号是否未过期
*/
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return false;
}
/**
* 账号是否锁定
*/
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return false;
}
/**
* 密码是否未过期
*/
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return false;
}
/**
* 账号是否可用
*/
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return false;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
}
public JwtUser() {
super();
}
public JwtUser(String username) {
super();
this.username = username;
}
public JwtUser(String username, String password) {
super();
this.username = username;
this.password = password;
}
public JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super();
this.username = username;
this.password = password;
this.authorities = authorities;
}
public JwtUser(Long id, String username, String password, Collection<? extends GrantedAuthority> authorities) {
super();
this.id = id;
this.username = username;
this.password = password;
this.authorities = authorities;
}
public JwtUser(String id, String username, String password, Collection<? extends GrantedAuthority> authorities) {
super();
this.id = Long.valueOf(id);
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return super.toString();
}
}
==============================================================================================================
package com.qsh.filter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
/**
* 管理权限的 实现GrantedAuthority类
* 重写equals,toString
* @author Administrator
*
*/
@Component
public final class RoleUrlJWt implements GrantedAuthority{
private String url;
@Override
public String getAuthority() {
// TODO Auto-generated method stub
return url;
}
public RoleUrlJWt(String url) {
super();
this.url = url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public RoleUrlJWt() {
super();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((url == null) ? 0 : url.hashCode());
return result;
}
public String toString() {
return this.url;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RoleUrlJWt other = (RoleUrlJWt) obj;
if (url == null) {
if (other.url != null)
return false;
} else if (!url.equals(other.url))
return false;
return true;
}
}
1:继承WebSecurityConfigurerAdapter类 配置默认过滤器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
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.config.http.SessionCreationPolicy;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsUtils;
import com.qsh.filter.renzheng.WZHFilterSecurityInterceptor;
/**
* 注意 protected void configure(AuthenticationManagerBuilder auth) throws Exception { 方法中
* 密码校验具有加密和不加密两种情况,所谓加密是值,前台传递来的是明文,后台被比较的-从数据库取出来的密码是明文加密后存储的,
* 这样需要将前台明文密码加密后和数据库存储的密码进行比较,
* 需要结合 com.bestcxx.stu.springbootsecuritydb.security.service.CustomUserService 类中的注释进行理解
* @author wj
*
*/
@Component
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
JWTAuthenticationProvider provider;
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private JwtProperties propersion;
@Autowired
private WZHFilterSecurityInterceptor WZHFilterSecurityInterceptor;
@Autowired
public void getJwtAuthenticationTokenFilter(JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter,JwtProperties propersion) {
this.jwtAuthenticationTokenFilter=jwtAuthenticationTokenFilter;
this.propersion=propersion;
// this.myFilterSecurityInterceptor=myFilterSecurityInterceptor;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()//后续需配置使用自己的默认登录页等基本设置
.authorizeRequests().antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js").permitAll()
// 对于获取token的rest api要允许匿名访问
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
.antMatchers(propersion.getPath().split(",")).permitAll()
.anyRequest().authenticated();
http.headers().cacheControl();
// 自定义Filter完成验证码校验
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);//token过滤器
http.addFilterBefore(WZHFilterSecurityInterceptor, FilterSecurityInterceptor.class);//权限过滤器
}
/**
* 如果不把验证交给系统则会一直出现账户已被锁定,失效等异常
* @param auth
* @throws Exception
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
//将验证过程交给系统验证提供者
auth.authenticationProvider(provider);
}
}
2:自定义token拦截器
package com.qsh.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.qsh.exception.MyException;
import com.qsh.util.JwtHelper;
import io.jsonwebtoken.Claims;
/**
* Token过滤器
*
* @author hackyo Created on 2017/12/8 9:28.
*/
@SuppressWarnings("all")
@Service
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtHelper jwtHelper;
private JwtProperties porpersion;
@Autowired
public JwtProperties getJwtPropersion(JwtProperties propersion) {
return this.porpersion = propersion;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//循环匹配看当前访问url是否需要过滤,如果为true标识不需要被拦截的,比如/login
Boolean aa = this.matchers(porpersion.getPath(), request);
Boolean bb = CorsUtils.isPreFlightRequest(request);
if (aa || bb) {
filterChain.doFilter(request, response);
return;
}
String token = request.getHeader(porpersion.getHeader());
String tokenHead = "Bearer ";
if (token != null && token.startsWith(tokenHead)) {
//执行userDetailsService.loadUserByUsername方法校验用户的正确性
UserDetails userDetaile = userDetailsService
.loadUserByUsername(jwtHelper.getUsernameFromToken(token.substring(7)));
try {
//校验token的安全性
boolean b = jwtHelper.validateToken(token.substring(7), userDetaile);
if(b) {
Claims claims = jwtHelper.getClaimsFromToken(token.substring(7));
//把当前用户信息放入Security全局缓存中
SecurityContextHolder.getContext().setAuthentication(getAuth(claims));
filterChain.doFilter(request, response);
}
} catch (MyException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}
}
/**
* 循环匹配url如果为true,则属于受保护应用不被过滤
* @param path
* @param request
* @return
*/
private Boolean matchers(String path, HttpServletRequest request) {
String[] temp = path.split("[,]");
Boolean b = false;
for (String s : temp) {
AntPathRequestMatcher matcher = new AntPathRequestMatcher(s.trim());
if (matcher.matches(request)) {
b = true;
break;
} else {
continue;
}
}
return b;
}
private Authentication getAuth(Claims claims) {
ArrayList<RoleUrlJWt> authorities = new ArrayList<>();
@SuppressWarnings("unchecked")
List<Map<String, String>> Auths = (List<Map<String, String>>) claims.get(porpersion.getAuthPrefix());
for (Map<String, String> authority : Auths) {
authorities.add(new RoleUrlJWt(authority.get("authority")));
}
JwtUser principal = new JwtUser(claims.getId(), claims.getSubject(), "", authorities);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
principal, "", authorities);
return usernamePasswordAuthenticationToken;
}
}
实现UserDetailsService 重写loadUserByUsername方法
UserDetailsService验证用户名、密码和授权处理。
包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。
package com.qsh.filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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 com.qsh.entity.User;
import com.qsh.service.UserService;
/**
* UserDetailsService验证用户名、密码和授权处理。
*包含 GrantedAuthority 的 UserDetails对象在构建 Authentication对象时填入数据。
*/
@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserService userService;
/**
* 登陆验证时,通过username获取用户的所有权限信息,
* 并返回JwtUser放到spring的全局缓存SecurityContextHolder中,以供授权器使用
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.login(username);
System.err.println("UserDetailsService:loadUserByUsername:"+username);
if(user == null){
return null;
}else {
//我的是一个用户对应一个角色,一个角色有多个权限
//通过用户名查询对应的角色
String role = userService.getRolesByUserName(username);
//通过角色查询url并放入GrantedAuthority中
List<String> set = userService.getPermissionsByRoles(role);
Collection<GrantedAuthority> authorities=generateAuthorities(set);
return new JwtUser(user.getId(),username, user.getPassword(), authorities);
}
}
/**
* 把url放入JwtUser权限集合中
* @param privileges
* @return
*/
private Collection<GrantedAuthority> generateAuthorities(Collection<String> privileges) {
List<GrantedAuthority> auth = new ArrayList<GrantedAuthority>(
privileges.size());
for (String privilege : privileges) {
GrantedAuthority authority = new RoleUrlJWt(
privilege);
auth.add(authority);
}
return auth;
}
}
以上为登录验证的
下面是每次访问鉴权
访问受保护的权限
- 访问受保护url时,会通过AbstractSecurityInterceptor拦截器拦截
- 其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限
- 在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,
- 还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
package com.qsh.filter.renzheng;
import java.io.IOException;
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 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;
/**
* 访问受保护的权限第一步
*
* 访问受保护url时,会通过AbstractSecurityInterceptor拦截器拦截
* 其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限
* 在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,
* 还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
* @author Administrator
*
*/
@Service
public class WZHFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
@Autowired
private FilterInvocationSecurityMetadataSource securityMetadataSource;
@Autowired
public void setLYJAccessDecisionManager(WZHAccessDecisionManager lyjAccessDecisionManager) {
super.setAccessDecisionManager(lyjAccessDecisionManager);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
//登陆后,每次访问资源都通过这个拦截器拦截
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
FilterInvocation filterInvocation = new FilterInvocation(request, response, chain);
//filterInvocation里面有一个被拦截的url
//里面调用WZHMetadataSource.java的getAttributes(Object object)这个方法获取fi对应的所有权限
//再调用WZHAccessDecisionManager.java的decide方法来校验用户的权限是否足够
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
//执行下一个拦截器
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.afterInvocation(token, null);
}
}
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public Class<?> getSecureObjectClass() {
return FilterInvocation.class;
}
@Override
public SecurityMetadataSource obtainSecurityMetadataSource() {
// TODO Auto-generated method stub
return this.securityMetadataSource;
}
}
================================================================================================
package com.qsh.filter.renzheng;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;
/**
* 访问受保护的权限第二步
*
* FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限
* @author Administrator
*
*/
@Service
public class WZHMetadataSource implements FilterInvocationSecurityMetadataSource{
//参数是要访问的url,返回这个url对于的所有权限(或角色)
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
ConfigAttribute configAttribute = new WZHConfigAttribute(request);
allAttributes.add(configAttribute);
return allAttributes;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
================================================================================================
/**
* LYJAccessDecisionManager.java
* com.lyj.springboot.footstone.security.decision
* Function: TODO add descript
*
* ver date author
* ──────────────────────────────────
* 2017年10月30日 lyj
*
* Copyright (c) 2017, TNT All Rights Reserved.
*/
package com.qsh.filter.renzheng;
import java.util.Collection;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
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.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.web.cors.CorsUtils;
import com.qsh.filter.JwtProperties;
import com.qsh.filter.RoleUrlJWt;
/**
* 第三步
* 授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,
* 还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。
*/
@Service
public class WZHAccessDecisionManager implements AccessDecisionManager {
@Autowired
JwtProperties jwtProperties;
//检查用户是否够权限访问资源
//参数authentication是从spring的全局缓存SecurityContextHolder中拿到的,里面是用户的权限信息
//参数object是url
//参数configAttributes所需的权限
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
if(matchers("/",request) || matchers("/*.html",request) || matchers("/favicon.ico", request) || matchers("/**/*.css", request) || matchers("/**/*.js", request)
|| matchers(jwtProperties.getPath(), request) || CorsUtils.isPreFlightRequest(request)){
return;
}else{
for(GrantedAuthority grant : authentication.getAuthorities()){
if(grant instanceof RoleUrlJWt){
String url = ((RoleUrlJWt)grant).getAuthority();
if(matchers(url, request)){
return;
}
}
}
}
//注意:执行这里,后台是会抛异常的,但是界面会跳转到所配的access-denied-page页面
throw new AccessDeniedException("You do not have permission !");
}
/**
* @see org.springframework.security.access.AccessDecisionManager#supports(org.springframework.security.access.ConfigAttribute)
*/
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
/**
* @see org.springframework.security.access.AccessDecisionManager#supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> clazz) {
return true;
}
/**
* 循环匹配url
* @param path
* @param request
* @return
*/
private Boolean matchers(String path,HttpServletRequest request) {
String[] temp = path.split("[,]");
Boolean b = false;
for(String s : temp) {
AntPathRequestMatcher matcher = new AntPathRequestMatcher(s.trim());
if(matcher.matches(request)) {
b = true;
break;
}else {
continue;
}
}
return b;
}
}
=====================================================================================================
package com.qsh.filter.renzheng;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.access.ConfigAttribute;
/**
* @author
*
*/
public class WZHConfigAttribute implements ConfigAttribute{
private static final long serialVersionUID = 9155663980275938220L;
private final HttpServletRequest httpServletRequest;
public WZHConfigAttribute(HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
@Override
public String getAttribute() {
// TODO Auto-generated method stub
return null;
}
public HttpServletRequest getHttpServletRequest() {
return httpServletRequest;
}
}