安全框架
- Spring Boot整合Spring Security JWT实现登录认证以及权限认证
Spring Security
-
简介
- SpringSecurity是一个用于Java 企业级应用程序的安全框架(简单说是对访问权限进行控制)
- 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)
-
认证过程
-
Spring Security 的认证过程
- (前端)用户使用用户名和密码进行登录。
- (后端)Spring Security 将获取到的用户名和密码封装成一个实现了 Authentication 接口的 UsernamePasswordAuthenticationToken。
- (后端)将上述产生的 token 对象传递给 AuthenticationManager 进行登录认证。
- (后端)AuthenticationManager 认证成功后将会返回一个封装了用户权限等信息的 Authentication 对象。
- (后端)通过调用 SecurityContextHolder.getContext().setAuthentication(…) 将 AuthenticationManager 返回的 Authentication 对象赋予给当前的 SecurityContext。
-
在认证成功后,用户就可以继续操作去访问其它受保护的资源了,但是在访问的时候将会使用保存在 SecurityContext 中的 Authentication 对象进行相关的权限鉴定。
-
流程展示
-
登录流程
-
接口验证流程
搭建项目
- 技术栈
数据库: mysql 8.x
连接池: druid
持久层框架: MyBatis-plus
安全框架: Spring Security
安全传输工具: JWT
Json解析: fastjson
- 新建Spring Boot工程
导入依赖和配置
pom.xml
中,引入相关依赖
<!-- web 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mysql 依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--整合mybatis plus https://baomidou.com/-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- hutool 工具类)-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<!-- springboot security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.4</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- redis 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<!-- swagger2依赖-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!-- swagger 第三方ui依赖-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
<!-- 谷歌验证码 kaptcha-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
- 配置文件
application.yml
# 开发环境配置文件
server:
port: 9090
spring:
# 配置数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver #数据库链接驱动
url: jdbc:mysql://localhost:3306/base?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai #url
username: root #用户名
password: root1234 #密码
#Druid数据源配置
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 10 #初始化时建立物理连接的个数
min-idle: 5 #最小连接池数量
maxActive: 30 #最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
# mybatis-plus 配置
mybatis-plus:
# 配置mapper映射文件
mapper-locations: classpath*:/mapper/*Mapper.xml
type-aliases-package: com.fang.system.entity
configuration:
# 自动驼峰命名
map-underscore-to-camel-case: true
# mybatis sql打印(方法接口所在的包,不是mapper.xml文件所在的包)
logging:
level:
com.example.admin.mapper: debug
# Redis配置
redis:
host: 127.0.0.1 #服务器地址
port: 6379 #服务器端口
database: 0 #数据库
password: #密码
timeout: 100000ms #超时时间
lettuce:
pool:
max-active: 1024 #最大连接数
max-wait: 10000ms #最大连接阻塞时间 默认-1
max-idle: 200 #最大空闲连接
min-idle: 5 #最小空闲连接
# jwt 配置
jwt:
tokenHeader: Authorization
tokenHead: Bearer # Token前缀字符
expiration: 604800 #7天,秒单位
secret: base-security-secret # 密匙KEY
动态权限控制
- spring security的简单原理
登录验证:
用户登陆,会被
AuthenticationProcessingFilter
拦截,调用AuthenticationManager
的实现,AuthenticationManager
会调用ProviderManager
来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等);如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存
SecurityContextHolder
中,以备后面访问资源时使用。
访问资源(即授权管理):访问资源(即授权管理):
访问资源(即授权管理),访问url时,会通过
AbstractSecurityInterceptor
拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource
的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager
,这个授权管理器会通过spring的全局缓存SecurityContextHolde
r获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,配置权限策略,如果权限足够,则返回,权限不够则报错并调用权限不足页面。
-
数据库表设计
-
权限过滤器
CustomAccessFilter.java
package com.example.admin.filter;
import com.example.admin.entity.Menu;
import com.example.admin.entity.Role;
import com.example.admin.service.IMenuService;
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.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
import java.util.List;
/**
* 权限控制
* @author fangqi174956
*/
@Component
public class CustomAccessFilter implements FilterInvocationSecurityMetadataSource {
@Autowired
private IMenuService menuService;
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 获取请求的url
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Menu> menus = menuService.getMenusWithRole();
for (Menu menu : menus) {
// 判断请求url 与菜单角色是否匹配
if(antPathMatcher.match(menu.getUrl(),requestUrl)){
String[] roleList = menu.getRoles().stream().map(Role::getUniqueCode).toArray(String[]::new);
return SecurityConfig.createList(roleList);
}
}
// 没有匹配上的url 默认角色ROLE_login (即登录即可访问)
return SecurityConfig.createList("ROLE_login");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass