前后端分离(SpringBoot+Vue)-基础的权限管理系统
简介
采用前后端分离式开发的一个简单的,基础的后台权限管理系统,功能模块包括用户管理、角色管理、权限管理。
前端主要技术
- vue + Element-ui + axios
后端主要技术
- SpringCloud(Gateway、Feign)
- SpringCloudAlibaba(Nacos、Sentinel)
- SpringBoot
- SpringSecurity
- Redis
- Mybatis
- Mysql
- 通用Mapper
- JWT
- knife4j(Swagger)
项目结构
- changgou-parent |-- 父工程
- changgou-common |-- 公共模块(工具类、公共常量等)
- changgou-api |-- 各微服务api(实体类等)
- changgou-gateway |-- Gateway网关服务
- changgou-auth |-- Auth认证服务
- changgou-service |-- 各业务服务的父工程
- changgou-service-sys |-- Sys系统管理服务
项目架构
详细介绍
- 请求各资源必须经过 Gateway网关服务 ,不能绕过网关的限制,否则无法访问。在网关处放行 登录 及 Swagger 资源;限制访问内部接口。在网关处还整合了 统一Swagger 接口文档。所有的服务都在 Nacos 中进行注册,各服务之间通过 Feign 调用,配合 Sentinel 进行服务的熔断降级。
- 通过 Auth认证服务 ,由 Security 完成认证,使用 JWT 生成token(
subject 包含 id 和 username
),获取权限列表,并根据用户名将权限列表存储在 Redis 中; - 各业务微服务( 如 Sys系统服务 )通过 Gateway网关服务 进行token的检验与解析,获取token中的用户名,并根据用户名获取Redis中对应的权限列表,在转发请求时添加在 请求头中传递给各微服务 ,各微服务在 自定义拦截器 中在请求头中拿到权限列表,结合 自定义注解 实现权限的控制;
安装说明
下载前端和后端代码,分别使用开发工具打开(我这里以Idea和WebStorm为例)
前端项目
- 安装依赖,在Terminal终端执行以下命令
npm install
- 设置启动端口及访问后端项目网关接口地址
- 在config目录 index.js 中设置启动地址和端口
- 在config目录 dev.env.js 和 prod.env.js 中设置访问后端网关接口地址,这里根据实际情况配置
后端项目
在 Gateway 、 Auth 、 Sys 三个服务的 application.yml 配置文件中配置
- 服务端口
server:
port: 10002
- 数据源及Mysql连接
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/changgou?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
- Redis连接
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
timeout: 5000
lettuce:
pool:
max-active: 20
max-wait: -1
max-idle: 5
min-idle: 0
- Nacos连接
nacos:
discovery:
server-addr: 127.0.0.1:8848
- Sentinel面板连接
sentinel:
transport:
port: 8719
dashboard: localhost:8080
启动
- 前端在Terminal窗口使用
npm run dev
命令启动 - 后端项目在启动之前需要开启 Nacos 、 Redis、 Mysql,随后启动 Gateway 、 Auth 、 Sys 三个服务
访问地址
以我项目中的配置为例
- 前端访问地址:http://localhost:8080
- 后端Swagger接口:http://localhost:9999/doc.html
注:Swagger配置在网关处,所以端口号一定要与网关服务的端口保持一致
项目部分截图
Swagger统一文档
登录
工作台
系统管理-用户管理
系统管理-用户管理-列表
系统管理-用户管理-分配角色
系统管理-角色管理
系统管理-角色管理-列表
系统管理-角色管理-分配权限
系统管理-资源管理
系统管理-资源管理-列表
系统管理-资源管理-新增资源
系统管理-资源管理-新增权限
附录
遇到的问题及实现思路
1、认证
- 采用token进行认证和授权
- 使用网关 GateWay 进行请求转发,由网关处进行请求的拦截,如果是登录请求,不进行token的校验与解析,直接放行,转发请求至 Auth认证服务 进行认证
- Auth认证服务 采用 Security+JWT 实现认证,前端请求登录,Security完成认证过程,认证成功后返回由JWT生成的Token字符串给前端,前端将这个token放在请求头中,后面的请求经过网关,由网关去进行token的校验与解析。同时,也在网关处获取登录成功后存放在redis中的权限列表,放在请求头中传递给各微服务进行授权
- 在前后端分离项目中,前后端交互采用的是JSON进行传输的,所以不能采用Security框架自带的表单登录,否则会出现在认证过程中无法获取前端提交过来的登录信息
- 在这里,我们可以自定义登录过滤器继承UsernamePasswordAuthenticationFilter,在自定义过滤器的attemptAuthenticatio方法中通过HttpServletRequest获取登录信息(生成一个新的UsernamePasswordAuthenticationToken对象传输给UserDetailsService由loadUserByUsername方法完成登录逻辑),同时在successfulAuthentication和unsuccessfulAuthentication两个方法中可以实现登录成功与否的逻辑
- 登录成功,由于在登录的逻辑中已经获取过当前登录用户的权限列表,所以登录成功之后我们可以将该用户的权限列表放在redis中,并设置有效期(与JWT生成的token有效期保持一致,这样当token失效时,redis中的权限列表也失效了)
- 登录失败,返回前端失败信息
2、登出及登出后的token安全
- 前端携带token请求登出接口,由网关校验token。然后转发给认证服务进行登出操作,我们可以自定义登出成功处理器实现LogoutSuccessHandler完成登出成功之后一些逻辑,比如token的移除等。
- 在这里,由于JWT生成的token在有效期内都可以使用,并且不能被移除,为了保证安全,我们在登出的时候,将当前的token放在redis中,设置一个token黑名单,在网关进行token校验时,判断是否在token黑名单中以保证安全。同时,设置有效期,采用token字符串拼接有效期的存放形式(如:abc|1小时),后面可以使用认识任务移除redis中过期的token。除此之外,还要移除redis中该用户的权限。
3、授权
- 在认证中提到过由网关获取在redis中存放的权限数据,然后放在请求头中,这样的话,各微服务就可以在请求头中获取到权限列表,进行各自的授权操作。
- 在这里,我并没有为每个微服务使用Security进行授权,因为引入了Security就涉及到认证的操作,我们并不需要为每个微服务进行认证。
- 我采用自定义注解+拦截器的方式完成授权操作的。以sys为例,我们需要自定义一个权限注解BaseAccess,然后自定义拦截器,由于网关已经将权限列表通过请求头的方式传递给各微服务了,所以可以在拦截器中获取请求中的权限列表,然后获取请求的接口上面BaseAccess注解中的权限值,两者进行比较,完成授权操作。可参考文章SpringBoot+拦截器+自定义注解实现权限控制
4、限制只能通过网关进行访问
- 在授权操作中,通过网关获取权限列表以请求头的方式传递各微服务,微服务在拦截器中获取该请求头拿到权限列表,由于这个权限列表是网关传过来的,所以我们可以这样想:如果在微服务中可以获取到权限列表的请求头,那么说明是通过网关请求的(外部不知道这个请求头),如果获取不到,说明不是通过网关进行访问的,就直接拦截返回错误信息。这样,就可以限制直接访问各微服务。
5、各服务随处获取当前登录用户
- 通过Feign调用认证服务(auth)的接口获取当前用户
主要文件介绍
Gateway网关服务
BaseGlobalFilter.java |-网关全局过滤器
- 过滤登录请求、Swagger相关请求
- 拦截访问内部接口
- 校验token
- 获取权限列表通过请求的方式下发至各微服务
BaseCorsConfig.java |-跨域配置
- 在网关处统一配置跨域
SwaggerResourceConfig.java |-网关统一Swagger配置
- 在网关处统一各微服务的Swagger,形成统一的Swagger文档
Auth认证服务
BaseWebSecurityConfig.java |-安全配置类
- 配置统一认证接口和登出接口,实现认证接入点及访问授权异常处理,添加过滤器,完成认证成功与否的逻辑
BaseAuthenticationEntryPoint.java |-未登录处理器
- 处理未登录时访问资源的逻辑
BaseAccessDeniedHandler.java |-访问授权处理器
- 处理登录后访问无权限资源的逻辑
BaseLoginFilter.java |-登录过滤器
- 配置具体的登录请求路径
- 获取请求中的登录信息组装对象并传递
- 实现认证成功与认证失败之后的逻辑
BaseLogoutSuccessHandler.java |-登出成功处理器
- 处理登出成功后的逻辑,如token的处理等
BaseTokenFilter.java |- token过滤器
- 处理调用该服务的内部接口inner未认证问题,主要是通过内部服务去获取当前登录用户的信息。由于使用的Security,所有的请求都会被Security拦截为未登录的状态,通过该过滤器,其他服务通过feign在调用该服务的接口时传递一个token的请求头,该服务在此过滤器中进行token的解析并将相关认证信息放在Security上下文中,这样Security就会认为已登录。
BaseWebMvcConfig.java |-MVC配置
- 用来添加自定义的拦截器
BaseSecurityInterceptor.java |-安全拦截器
- 拦截请求,获取权限列表
- 限制绕过网关直接访问服务
- 授权操作
BaseUserDetailsServiceImpl.java |-认证业务
- 与数据库交互,校验认证信息,获取权限列表并存储在redis
BaseUserDetails.java |- 认证实体
- 进行认证信息的存储交给Security框架
ClearTokenSchedule.java |-清除token任务
- 定期清除redis中过期无效的token
Sys系统管理服务
FeignConfig.java |-feign配置
- 配置feign调用内部接口时添加token请求
Common 公共模块
BaseAccess.java |-权限注解
- 自定义权限注解,用在接口上,在业务服务拦截器中通过反射机制获取该注解中的权限值进行授权操作