「工程实践」Spring Security(七)前后端分离权限控制-指令级权限
每个按钮对应一个
权限标识,后台根据用户角色计算出当前用户可访问的权限标识列表,前端登录后得到权限标识列表存入全局,通过单个按钮的权限标识去匹配列表里的。来实现按钮级别的权限判断。权限标识,后台根据用户角色计算出当前用户可访问的权限标识列表,前端登录后得到权限标识列表存入全局,通过单个按钮的权限标识去匹配列表里的。来实现按钮级别的权限判断。本文基于上一篇文章:《Spring Security(三)整合 JWT 实现无状态登录示例》。
在 SpringSecurity 整合 JWT 实现无状态登录示例中,我们在 JwtAuthenticationFilter (自定义JWT认证过滤器) 解析 Token 成功后,提供了续签逻辑:
/**
* 刷新Token的时机:
* 1. 当前时间 < token过期时间
* 2. 当前时间 > (签发时间 + (token过期时间 - token签发时间)/2)
*/
private void refreshToken(HttpServletResponse response, Claims claims) {
// 当前时间
long current = System.currentTimeMillis();
// token签发时间
long issuedAt = claims.getIssuedAt().getTime();
// token过期时间
long expiration = claims.getExpiration().getTime();
// (当前时间 < token过期时间) && (当前时间 > (签发时间 + (token过期时间 - token签发时间)/2))
if ((current < expiration) && (current > (issuedAt + ((expiration - issuedAt) / 2)))) {
/*
* 重新生成token
*/
Calendar calendar = Calendar.getInstance();
// 设置签发时间
calendar.setTime(new Date());
Date now = calendar.getTime();
// 设置过期时间: 5分钟
calendar.add(Calendar.MINUTE, 5);
Date time = calendar.getTime();
String refreshToken = Jwts.builder()
.setSubject(claims.getSubject())
// 签发时间
.setIssuedAt(now)
// 过期时间
.setExpiration(time)
// 算法与签名(同生成token):这里算法采用HS512,常量中定义签名key
.signWith(SignatureAlgorithm.HS512, ConstantKey.SIGNING_KEY)
.compact();
// 主动刷新token,并返回给前端
response.addHeader("refreshToken", refreshToken);
log.info("刷新token执行时间: {}", (System.currentTimeMillis() - current) + " 毫秒");
}
}这里的逻辑是:Token 未过期并且当前时间已经超过 Token 有效时间的一半,重新生成一个 refreshToken,并返回给前端,前端需要用 refreshToken 替换之前旧的 Token。
JSON Web Token(缩写 JWT)基于JSON格式信息一种Token令牌,是目前最流行的跨域认证解决方案。
pom.xml 中引入依赖:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>application.yml 配置文件中配置:server:
port: 8080
spring:
datasource:
url: jdbc:mysql://192.168.2.100:3306/security?characterEncoding=UTF8&serverTimezone=Asia/Shanghai
username: developer
password: 05bZ/OxTB:X+yd%1
mybatis:
mapper-locations: classpath:mapper/*.xml/**
* 1. 通过继承 WebSecurityConfigurerAdapter 实现自定义Security策略
* @Configuration:声明当前类是一个配置类
* @EnableWebSecurity:开启WebSecurity模式
* @EnableGlobalMethodSecurity(securedEnabled=true):开启注解,支持方法级别的权限控制
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private UserDetailsService userDetailsService;
@Resource
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* 全局请求忽略规则配置
*/
@Override
public void configure(WebSecurity web) {
// 需要放行的URL
web.ignoring().antMatchers("/register", "/hello");
}
/**
* 自定义认证策略:登录的时候会进入
*/
@Override
public void configure(AuthenticationManagerBuilder auth) {
// 2.通过实现 AuthenticationProvider 自定义身份认证验证组件
auth.authenticationProvider(new AuthenticationProviderImpl(userDetailsService, bCryptPasswordEncoder));
}
/**
* HTTP 验证规则
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭Session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 所有请求需要身份认证
.and().authorizeRequests().anyRequest().authenticated()
.and()
// 3.自定义JWT登录过滤器
.addFilter(new JwtLoginFilter(authenticationManager()))
// 4.自定义JWT认证过滤器
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
// 5.自定义认证拦截器,也可以直接使用内置实现类Http403ForbiddenEntryPoint
.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointImpl())
// 允许跨域
.and().cors()
// 禁用跨站伪造
.and().csrf().disable();
}
}BCryptPasswordEncoder 解析器注入到容器