# changgou **Repository Path**: java2job/changgou ## Basic Information - **Project Name**: changgou - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2023-09-14 - **Last Updated**: 2023-09-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # changgou ### 简介 - 前端项目代码地址:[前端代码](https://gitee.com/sh-xukun/changgou-admin.git) - 后端项目代码地址:[后端代码](https://gitee.com/sh-xukun/changgou.git) `最后的附录记录了自己在开发过程遇到问题及实现、部分文件的介绍` 采用前后端分离式开发的一个简单的,基础的后台权限管理系统,功能模块包括用户管理、角色管理、权限管理。 > 前端主要技术 - 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系统管理服务 ### 项目架构 ![系统架构图](https://images.gitee.com/uploads/images/2021/0618/120357_a82df9e3_8351614.png "系统架构图.png") ### 详细介绍 - 请求各资源必须经过 **_Gateway网关服务_** ,不能绕过网关的限制,否则无法访问。在网关处放行 **登录** 及 **Swagger** 资源;限制访问内部接口。在网关处还整合了 **_统一Swagger_** 接口文档。所有的服务都在 **_Nacos_** 中进行注册,各服务之间通过 **_Feign_** 调用,配合 **_Sentinel_** 进行服务的熔断降级。 - 通过 **_Auth认证服务_** ,由 **Security** 完成认证,使用 **JWT** 生成token(` subject 包含 id 和 username `),获取权限列表,并根据用户名将权限列表存储在 **Redis** 中; - 各业务微服务( 如 **_Sys系统服务_** )通过 **_Gateway网关服务_** 进行token的检验与解析,获取token中的用户名,并根据用户名获取**Redis**中对应的权限列表,在转发请求时添加在 _请求头中传递给各微服务_ ,各微服务在 *自定义拦截器* 中在请求头中拿到权限列表,结合 *自定义注解* 实现权限的控制; ### 安装说明 下载前端和后端代码,分别使用开发工具打开(我这里以Idea和WebStorm为例) #### 前端项目 1. 安装依赖,在Terminal终端执行以下命令 ``` npm install ``` 2. 设置启动端口及访问后端项目网关接口地址 - 在config目录 **_index.js_** 中设置启动地址和端口 ![主机和端口地址](https://images.gitee.com/uploads/images/2021/0619/113631_47fd15a8_8351614.png "主机和端口地址.png") - 在config目录 **_dev.env.js_** 和 **_prod.env.js_** 中设置访问后端网关接口地址,这里根据实际情况配置 ![配置后端接口网关](https://images.gitee.com/uploads/images/2021/0619/113914_b9907137_8351614.png "配置后端接口网关.png") #### 后端项目 在 **_Gateway_** 、 **_Auth_** 、 **_Sys_** 三个服务的 **_application.yml_** 配置文件中配置 - 服务端口 ~~~yml server: port: 10002 ~~~ - 数据源及Mysql连接 ~~~yml 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连接 ~~~yml 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连接 ~~~yml nacos: discovery: server-addr: 127.0.0.1:8848 ~~~ - Sentinel面板连接 ~~~yml sentinel: transport: port: 8719 dashboard: localhost:8080 ~~~ ### 启动 1. 前端在Terminal窗口使用`npm run dev`命令启动 2. 后端项目在启动之前需要开启 **_Nacos_** 、 **_Redis_**、 **_Mysql_**,随后启动 **_Gateway_** 、 **_Auth_** 、 **_Sys_** 三个服务 ### 访问地址 以我项目中的配置为例 - 前端访问地址:http://localhost:8080 - 后端Swagger接口:http://localhost:9999/doc.html :tw-1f4e2: `注:Swagger配置在网关处,所以端口号一定要与网关服务的端口保持一致` ### 项目部分截图 #### Swagger统一文档 ![Swagger接口文档](https://images.gitee.com/uploads/images/2021/0619/111137_6d582cce_8351614.png "Swagger接口文档.png") #### 登录 ![登录](https://images.gitee.com/uploads/images/2021/0618/150441_a275973d_8351614.png "登录.png") #### 工作台 ![工作台](https://images.gitee.com/uploads/images/2021/0618/150356_bc778b2b_8351614.png "工作台.png") #### 系统管理-用户管理 ##### 系统管理-用户管理-列表 ![用户列表](https://images.gitee.com/uploads/images/2021/0618/150539_40277753_8351614.png "用户列表.png") ##### 系统管理-用户管理-分配角色 ![分配角色](https://images.gitee.com/uploads/images/2021/0618/150928_f068ca46_8351614.png "分配角色.png") #### 系统管理-角色管理 ##### 系统管理-角色管理-列表 ![角色列表](https://images.gitee.com/uploads/images/2021/0619/110303_429e3722_8351614.png "角色列表.png") ##### 系统管理-角色管理-分配权限 ![分配权限](https://images.gitee.com/uploads/images/2021/0619/110352_c8ceed4a_8351614.png "分配权限.png") #### 系统管理-资源管理 ##### 系统管理-资源管理-列表 ![列表](https://images.gitee.com/uploads/images/2021/0619/110516_8a7e9bf2_8351614.png "列表.png") ##### 系统管理-资源管理-新增资源 ![新增资源](https://images.gitee.com/uploads/images/2021/0619/110705_7eb6d8e3_8351614.png "新增资源.png") ##### 系统管理-资源管理-新增权限 ![新增权限](https://images.gitee.com/uploads/images/2021/0619/110828_f6ba5231_8351614.png "新增权限.png") ### 附录 #### 遇到的问题及实现思路 ##### 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+拦截器+自定义注解实现权限控制](https://editor.csdn.net/md/?articleId=108702952) ##### 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 |-权限注解 - 自定义权限注解,用在接口上,在业务服务拦截器中通过反射机制获取该注解中的权限值进行授权操作