mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-18 19:20:05 +08:00
Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/db
# Conflicts: # sql/mysql/ruoyi-vue-pro.sql
This commit is contained in:
commit
c0722b3a19
49
README.md
49
README.md
@ -21,6 +21,18 @@
|
|||||||
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
||||||
* 视频教程:<https://doc.iocoder.cn/video/>
|
* 视频教程:<https://doc.iocoder.cn/video/>
|
||||||
|
|
||||||
|
## 🐰 版本说明
|
||||||
|
|
||||||
|
| 版本 | JDK 8 + Spring Boot 2.7 | JDK 17/21 + Spring Boot 3.2 |
|
||||||
|
|---------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||||
|
| 【完整版】[ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-jdk17/) 分支 |
|
||||||
|
| 【精简版】[yudao-boot-mini](https://gitee.com/yudaocode/yudao-boot-mini) | [`master`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master-jdk17/) 分支 |
|
||||||
|
|
||||||
|
* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
|
||||||
|
* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
|
||||||
|
|
||||||
|
可参考 [《迁移文档》](https://doc.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】
|
||||||
|
|
||||||
## 🐯 平台简介
|
## 🐯 平台简介
|
||||||
|
|
||||||
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||||
@ -31,7 +43,7 @@
|
|||||||
|
|
||||||
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
|
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
|
||||||
|
|
||||||
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7.18,`master-jdk21` 分支为 JDK21 + Spring Boot 3.2.0
|
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7,`master-jdk17` 分支为 JDK 17/21 + Spring Boot 3.2
|
||||||
* 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本
|
* 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本
|
||||||
* 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
|
* 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
|
||||||
* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
|
* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
|
||||||
@ -72,28 +84,6 @@
|
|||||||
| [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台 |
|
| [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台 |
|
||||||
| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
|
| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
|
||||||
|
|
||||||
## 🐰 分支说明
|
|
||||||
|
|
||||||
### ⬅️ 完整版
|
|
||||||
|
|
||||||
【完整版】包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM 等功能
|
|
||||||
|
|
||||||
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro> 的 `master` 分支
|
|
||||||
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/zhijiantianya/ruoyi-vue-pro> 的 `master-jdk21` 分支
|
|
||||||
|
|
||||||
两个分支的功能是一致的,可以放心使用!
|
|
||||||
|
|
||||||
### ➡️️ 精简版
|
|
||||||
|
|
||||||
【精简版】只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM 等功能
|
|
||||||
|
|
||||||
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master` 分支
|
|
||||||
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/yudaocode/yudao-boot-mini> 的 `master-jdk21` 分支
|
|
||||||
|
|
||||||
如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
|
|
||||||
|
|
||||||
如果你想把【完整版】的功能,迁移到【精简版】,可以参考 [《迁移功能到精简版》](https://doc.iocoder.cn/migrate-module/) 文档。
|
|
||||||
|
|
||||||
## 😎 开源协议
|
## 😎 开源协议
|
||||||
|
|
||||||
**为什么推荐使用本项目?**
|
**为什么推荐使用本项目?**
|
||||||
@ -120,16 +110,9 @@
|
|||||||
|
|
||||||
![功能分层](/.image/common/ruoyi-vue-pro-biz.png)
|
![功能分层](/.image/common/ruoyi-vue-pro-biz.png)
|
||||||
|
|
||||||
* 系统功能
|
* 通用模块(必选):系统功能、基础设施
|
||||||
* 基础设施
|
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
||||||
* 工作流程
|
* 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号
|
||||||
* 支付系统
|
|
||||||
* 会员中心
|
|
||||||
* 数据报表
|
|
||||||
* 商城系统
|
|
||||||
* 微信公众号
|
|
||||||
* ERP 系统
|
|
||||||
* CRM 系统
|
|
||||||
|
|
||||||
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
|
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
|
||||||
>
|
>
|
||||||
|
4
pom.xml
4
pom.xml
@ -23,8 +23,6 @@
|
|||||||
<!-- <module>yudao-module-mall</module>-->
|
<!-- <module>yudao-module-mall</module>-->
|
||||||
<!-- <module>yudao-module-crm</module>-->
|
<!-- <module>yudao-module-crm</module>-->
|
||||||
<!-- <module>yudao-module-erp</module>-->
|
<!-- <module>yudao-module-erp</module>-->
|
||||||
<!-- 示例项目 -->
|
|
||||||
<!-- <module>yudao-example</module>-->
|
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
@ -34,7 +32,7 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<revision>2.0.1-snapshot</revision>
|
<revision>2.0.1-snapshot</revision>
|
||||||
<!-- Maven 相关 -->
|
<!-- Maven 相关 -->
|
||||||
<java.version>21</java.version>
|
<java.version>17</java.version>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||||
<maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
|
<maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
|
|
||||||
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-example</artifactId>
|
|
||||||
<version>1.0.0-snapshot</version>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
<modules>
|
|
||||||
<module>yudao-sso-demo-by-code</module>
|
|
||||||
<module>yudao-sso-demo-by-password</module>
|
|
||||||
</modules>
|
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
|
||||||
<description>提供各种示例,例如说:SSO 单点登录</description>
|
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,65 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
|
|
||||||
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-sso-demo-by-code</artifactId>
|
|
||||||
<version>1.0.0-snapshot</version>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
|
||||||
<description>基于授权码模式,如何实现 SSO 单点登录?</description>
|
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<!-- Maven 相关 -->
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
<!-- 统一依赖管理 -->
|
|
||||||
<spring.boot.version>3.2.0</spring.boot.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<!-- 统一依赖管理 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
|
||||||
<version>${spring.boot.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- Web 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.hutool</groupId>
|
|
||||||
<artifactId>hutool-all</artifactId>
|
|
||||||
<version>5.8.22</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,13 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class SSODemoApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(SSODemoApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,157 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.Base64Utils;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OAuth 2.0 客户端
|
|
||||||
*
|
|
||||||
* 对应调用 OAuth2OpenController 接口
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OAuth2Client {
|
|
||||||
|
|
||||||
private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*
|
|
||||||
* 默认使用 1;如果使用别的租户,可以调整
|
|
||||||
*/
|
|
||||||
public static final Long TENANT_ID = 1L;
|
|
||||||
|
|
||||||
private static final String CLIENT_ID = "yudao-sso-demo-by-code";
|
|
||||||
private static final String CLIENT_SECRET = "test";
|
|
||||||
|
|
||||||
|
|
||||||
// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用 code 授权码,获得访问令牌
|
|
||||||
*
|
|
||||||
* @param code 授权码
|
|
||||||
* @param redirectUri 重定向 URI
|
|
||||||
* @return 访问令牌
|
|
||||||
*/
|
|
||||||
public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("grant_type", "authorization_code");
|
|
||||||
body.add("code", code);
|
|
||||||
body.add("redirect_uri", redirectUri);
|
|
||||||
// body.add("state", ""); // 选填;填了会校验
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/token",
|
|
||||||
HttpMethod.POST,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验访问令牌,并返回它的基本信息
|
|
||||||
*
|
|
||||||
* @param token 访问令牌
|
|
||||||
* @return 访问令牌的基本信息
|
|
||||||
*/
|
|
||||||
public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("token", token);
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/check-token",
|
|
||||||
HttpMethod.POST,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用刷新令牌,获得(刷新)访问令牌
|
|
||||||
*
|
|
||||||
* @param refreshToken 刷新令牌
|
|
||||||
* @return 访问令牌
|
|
||||||
*/
|
|
||||||
public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("grant_type", "refresh_token");
|
|
||||||
body.add("refresh_token", refreshToken);
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/token",
|
|
||||||
HttpMethod.POST,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除访问令牌
|
|
||||||
*
|
|
||||||
* @param token 访问令牌
|
|
||||||
* @return 成功
|
|
||||||
*/
|
|
||||||
public CommonResult<Boolean> revokeToken(String token) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("token", token);
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/token",
|
|
||||||
HttpMethod.DELETE,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addClientHeader(HttpHeaders headers) {
|
|
||||||
// client 拼接,需要 BASE64 编码
|
|
||||||
String client = CLIENT_ID + ":" + CLIENT_SECRET;
|
|
||||||
client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
|
|
||||||
headers.add("Authorization", "Basic " + client);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户 User 信息的客户端
|
|
||||||
*
|
|
||||||
* 对应调用 OAuth2UserController 接口
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class UserClient {
|
|
||||||
|
|
||||||
private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
|
|
||||||
|
|
||||||
// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
|
||||||
|
|
||||||
public CommonResult<UserInfoRespDTO> getUser() {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
|
|
||||||
addTokenHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/get",
|
|
||||||
HttpMethod.GET,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
|
|
||||||
addTokenHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
// 使用 updateReqDTO 即可
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/update",
|
|
||||||
HttpMethod.PUT,
|
|
||||||
new HttpEntity<>(updateReqDTO, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void addTokenHeader(HttpHeaders headers) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
Assert.notNull(loginUser, "登录用户不能为空");
|
|
||||||
headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用返回
|
|
||||||
*
|
|
||||||
* @param <T> 数据泛型
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class CommonResult<T> implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误码
|
|
||||||
*/
|
|
||||||
private Integer code;
|
|
||||||
/**
|
|
||||||
* 返回数据
|
|
||||||
*/
|
|
||||||
private T data;
|
|
||||||
/**
|
|
||||||
* 错误提示,用户可阅读
|
|
||||||
*/
|
|
||||||
private String msg;
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌 Response DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class OAuth2AccessTokenRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌
|
|
||||||
*/
|
|
||||||
@JsonProperty("access_token")
|
|
||||||
private String accessToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌
|
|
||||||
*/
|
|
||||||
@JsonProperty("refresh_token")
|
|
||||||
private String refreshToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌类型
|
|
||||||
*/
|
|
||||||
@JsonProperty("token_type")
|
|
||||||
private String tokenType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过期时间;单位:秒
|
|
||||||
*/
|
|
||||||
@JsonProperty("expires_in")
|
|
||||||
private Long expiresIn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权范围;如果多个授权范围,使用空格分隔
|
|
||||||
*/
|
|
||||||
private String scope;
|
|
||||||
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验令牌 Response DTO
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class OAuth2CheckTokenRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private Long userId;
|
|
||||||
/**
|
|
||||||
* 用户类型
|
|
||||||
*/
|
|
||||||
@JsonProperty("user_type")
|
|
||||||
private Integer userType;
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*/
|
|
||||||
@JsonProperty("tenant_id")
|
|
||||||
private Long tenantId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端编号
|
|
||||||
*/
|
|
||||||
@JsonProperty("client_id")
|
|
||||||
private String clientId;
|
|
||||||
/**
|
|
||||||
* 授权范围
|
|
||||||
*/
|
|
||||||
private List<String> scopes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌
|
|
||||||
*/
|
|
||||||
@JsonProperty("access_token")
|
|
||||||
private String accessToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过期时间
|
|
||||||
*
|
|
||||||
* 时间戳 / 1000,即单位:秒
|
|
||||||
*/
|
|
||||||
private Long exp;
|
|
||||||
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.user;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得用户基本信息 Response dto
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class UserInfoRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户账号
|
|
||||||
*/
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户昵称
|
|
||||||
*/
|
|
||||||
private String nickname;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户邮箱
|
|
||||||
*/
|
|
||||||
private String email;
|
|
||||||
/**
|
|
||||||
* 手机号码
|
|
||||||
*/
|
|
||||||
private String mobile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户性别
|
|
||||||
*/
|
|
||||||
private Integer sex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户头像
|
|
||||||
*/
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所在部门
|
|
||||||
*/
|
|
||||||
private Dept dept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所属岗位数组
|
|
||||||
*/
|
|
||||||
private List<Post> posts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Dept {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门名称
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Post {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位名称
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.user;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户基本信息 Request DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class UserUpdateReqDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户昵称
|
|
||||||
*/
|
|
||||||
private String nickname;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户邮箱
|
|
||||||
*/
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号码
|
|
||||||
*/
|
|
||||||
private String mobile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户性别
|
|
||||||
*/
|
|
||||||
private Integer sex;
|
|
||||||
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.controller;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/auth")
|
|
||||||
public class AuthController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private OAuth2Client oauth2Client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用 code 访问令牌,获得访问令牌
|
|
||||||
*
|
|
||||||
* @param code 授权码
|
|
||||||
* @param redirectUri 重定向 URI
|
|
||||||
* @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
|
|
||||||
*/
|
|
||||||
@PostMapping("/login-by-code")
|
|
||||||
public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code,
|
|
||||||
@RequestParam("redirectUri") String redirectUri) {
|
|
||||||
return oauth2Client.postAccessToken(code, redirectUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用刷新令牌,获得(刷新)访问令牌
|
|
||||||
*
|
|
||||||
* @param refreshToken 刷新令牌
|
|
||||||
* @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
|
|
||||||
*/
|
|
||||||
@PostMapping("/refresh-token")
|
|
||||||
public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
|
|
||||||
return oauth2Client.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*
|
|
||||||
* @param request 请求
|
|
||||||
* @return 成功
|
|
||||||
*/
|
|
||||||
@PostMapping("/logout")
|
|
||||||
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
|
||||||
String token = SecurityUtils.obtainAuthorization(request, "Authorization");
|
|
||||||
if (StrUtil.isNotBlank(token)) {
|
|
||||||
return oauth2Client.revokeToken(token);
|
|
||||||
}
|
|
||||||
// 返回成功
|
|
||||||
return new CommonResult<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.controller;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.UserClient;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/user")
|
|
||||||
public class UserController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private UserClient userClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得当前登录用户的基本信息
|
|
||||||
*
|
|
||||||
* @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
|
|
||||||
*/
|
|
||||||
@GetMapping("/get")
|
|
||||||
public CommonResult<UserInfoRespDTO> getUser() {
|
|
||||||
return userClient.getUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新当前登录用户的昵称
|
|
||||||
*
|
|
||||||
* @param nickname 昵称
|
|
||||||
* @return 成功
|
|
||||||
*/
|
|
||||||
@PutMapping("/update")
|
|
||||||
public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
|
|
||||||
UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
|
|
||||||
return userClient.updateUser(updateReqDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.config;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.security.config.Customizer;
|
|
||||||
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.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfiguration{
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private TokenAuthenticationFilter tokenAuthenticationFilter;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private AccessDeniedHandlerImpl accessDeniedHandler;
|
|
||||||
@Resource
|
|
||||||
private AuthenticationEntryPoint authenticationEntryPoint;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
|
||||||
// 设置 URL 安全权限
|
|
||||||
httpSecurity
|
|
||||||
// 开启跨域
|
|
||||||
.cors(Customizer.withDefaults())
|
|
||||||
// CSRF 禁用,因为不使用 Session
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
|
||||||
// 一堆自定义的 Spring Security 处理器
|
|
||||||
.exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint)
|
|
||||||
.accessDeniedHandler(accessDeniedHandler));
|
|
||||||
|
|
||||||
// 设置每个请求的权限
|
|
||||||
httpSecurity.authorizeHttpRequests(c -> c
|
|
||||||
// 1. 静态资源,可匿名访问
|
|
||||||
.requestMatchers(HttpMethod.GET, "/*.html", "/*.html", "/*.css", "/*.js").permitAll()
|
|
||||||
// 2. 登录相关的接口,可匿名访问
|
|
||||||
.requestMatchers("/auth/login-by-code").permitAll()
|
|
||||||
.requestMatchers("/auth/refresh-token").permitAll()
|
|
||||||
.requestMatchers("/auth/logout").permitAll())
|
|
||||||
// 3. 兜底规则,必须认证
|
|
||||||
.authorizeHttpRequests(c -> c.anyRequest().authenticated());
|
|
||||||
|
|
||||||
// 添加 Token Filter
|
|
||||||
httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
|
||||||
return httpSecurity.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录用户信息
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class LoginUser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
/**
|
|
||||||
* 用户类型
|
|
||||||
*/
|
|
||||||
private Integer userType;
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*/
|
|
||||||
private Long tenantId;
|
|
||||||
/**
|
|
||||||
* 授权范围
|
|
||||||
*/
|
|
||||||
private List<String> scopes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌
|
|
||||||
*/
|
|
||||||
private String accessToken;
|
|
||||||
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.filter;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token 过滤器,验证 token 的有效性
|
|
||||||
* 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private OAuth2Client oauth2Client;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
// 1. 获得访问令牌
|
|
||||||
String token = SecurityUtils.obtainAuthorization(request, "Authorization");
|
|
||||||
if (StringUtils.hasText(token)) {
|
|
||||||
// 2. 基于 token 构建登录用户
|
|
||||||
LoginUser loginUser = buildLoginUserByToken(token);
|
|
||||||
// 3. 设置当前用户
|
|
||||||
if (loginUser != null) {
|
|
||||||
SecurityUtils.setLoginUser(loginUser, request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 继续过滤链
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoginUser buildLoginUserByToken(String token) {
|
|
||||||
try {
|
|
||||||
CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
|
|
||||||
OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
|
|
||||||
if (accessToken == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 构建登录用户
|
|
||||||
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
|
|
||||||
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
|
|
||||||
.setAccessToken(accessToken.getAccessToken());
|
|
||||||
} catch (Exception exception) {
|
|
||||||
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.handler;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
|
|
||||||
*
|
|
||||||
* 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@SuppressWarnings("JavadocReference")
|
|
||||||
@Slf4j
|
|
||||||
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
// 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
|
|
||||||
log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
|
|
||||||
SecurityUtils.getLoginUserId(), e);
|
|
||||||
// 返回 403
|
|
||||||
CommonResult<Object> result = new CommonResult<>();
|
|
||||||
result.setCode(HttpStatus.FORBIDDEN.value());
|
|
||||||
result.setMsg("没有该操作权限");
|
|
||||||
ServletUtils.writeJSON(response, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.handler;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
|
|
||||||
*
|
|
||||||
* 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
@SuppressWarnings("JavadocReference") // 忽略文档引用报错
|
|
||||||
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
|
|
||||||
log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
|
|
||||||
// 返回 401
|
|
||||||
CommonResult<Object> result = new CommonResult<>();
|
|
||||||
result.setCode(HttpStatus.UNAUTHORIZED.value());
|
|
||||||
result.setMsg("账号未登录");
|
|
||||||
ServletUtils.writeJSON(response, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.util;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安全服务工具类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class SecurityUtils {
|
|
||||||
|
|
||||||
public static final String AUTHORIZATION_BEARER = "Bearer";
|
|
||||||
|
|
||||||
private SecurityUtils() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从请求中,获得认证 Token
|
|
||||||
*
|
|
||||||
* @param request 请求
|
|
||||||
* @param header 认证 Token 对应的 Header 名字
|
|
||||||
* @return 认证 Token
|
|
||||||
*/
|
|
||||||
public static String obtainAuthorization(HttpServletRequest request, String header) {
|
|
||||||
String authorization = request.getHeader(header);
|
|
||||||
if (!StringUtils.hasText(authorization)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
|
|
||||||
if (index == -1) { // 未找到
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return authorization.substring(index + 7).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得当前认证信息
|
|
||||||
*
|
|
||||||
* @return 认证信息
|
|
||||||
*/
|
|
||||||
public static Authentication getAuthentication() {
|
|
||||||
SecurityContext context = SecurityContextHolder.getContext();
|
|
||||||
if (context == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return context.getAuthentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前用户
|
|
||||||
*
|
|
||||||
* @return 当前用户
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static LoginUser getLoginUser() {
|
|
||||||
Authentication authentication = getAuthentication();
|
|
||||||
if (authentication == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得当前用户的编号,从上下文中
|
|
||||||
*
|
|
||||||
* @return 用户编号
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Long getLoginUserId() {
|
|
||||||
LoginUser loginUser = getLoginUser();
|
|
||||||
return loginUser != null ? loginUser.getId() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前用户
|
|
||||||
*
|
|
||||||
* @param loginUser 登录用户
|
|
||||||
* @param request 请求
|
|
||||||
*/
|
|
||||||
public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
|
|
||||||
// 创建 Authentication,并设置到上下文
|
|
||||||
Authentication authentication = buildAuthentication(loginUser, request);
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
|
|
||||||
// 创建 UsernamePasswordAuthenticationToken 对象
|
|
||||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
|
||||||
loginUser, null, Collections.emptyList());
|
|
||||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
||||||
return authenticationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.util;
|
|
||||||
|
|
||||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
|
||||||
import cn.hutool.extra.servlet.ServletUtil;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端工具类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class ServletUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回 JSON 字符串
|
|
||||||
*
|
|
||||||
* @param response 响应
|
|
||||||
* @param object 对象,会序列化成 JSON 字符串
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
|
||||||
public static void writeJSON(HttpServletResponse response, Object object) {
|
|
||||||
String content = JSONUtil.toJsonStr(object);
|
|
||||||
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void write(HttpServletResponse response, String text, String contentType) {
|
|
||||||
JakartaServletUtil.write(response, text, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
server:
|
|
||||||
port: 18080
|
|
@ -1,61 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>SSO 授权后的回调页</title>
|
|
||||||
<!-- jQuery:操作 dom、发起请求等 -->
|
|
||||||
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
|
|
||||||
<!-- 工具类 -->
|
|
||||||
<script type="application/javascript">
|
|
||||||
(function ($) {
|
|
||||||
/**
|
|
||||||
* 获得 URL 的指定参数的值
|
|
||||||
*
|
|
||||||
* @param name 参数名
|
|
||||||
* @returns 参数值
|
|
||||||
*/
|
|
||||||
$.getUrlParam = function (name) {
|
|
||||||
const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
|
|
||||||
const r = window.location.search.substr(1).match(reg);
|
|
||||||
if (r != null) return unescape(r[2]); return null;
|
|
||||||
}
|
|
||||||
})(jQuery);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script type="application/javascript">
|
|
||||||
$(function () {
|
|
||||||
// 获得 code 授权码
|
|
||||||
const code = $.getUrlParam('code');
|
|
||||||
if (!code) {
|
|
||||||
alert('获取不到 code 参数,请排查!')
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交
|
|
||||||
const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/auth/login-by-code?code=" + code
|
|
||||||
+ '&redirectUri=' + redirectUri,
|
|
||||||
method: 'POST',
|
|
||||||
success: function( result ) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('获得访问令牌失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('获得访问令牌成功!点击确认,跳转回首页')
|
|
||||||
|
|
||||||
// 设置到 localStorage 中
|
|
||||||
localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
|
|
||||||
localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
|
|
||||||
|
|
||||||
// 跳转回首页
|
|
||||||
window.location.href = '/index.html';
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
正在使用 code 授权码,进行 accessToken 访问令牌的获取
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,159 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>首页</title>
|
|
||||||
<!-- jQuery:操作 dom、发起请求等 -->
|
|
||||||
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
|
|
||||||
|
|
||||||
<script type="application/javascript">
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳转单点登录
|
|
||||||
*/
|
|
||||||
function ssoLogin() {
|
|
||||||
const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId
|
|
||||||
const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址
|
|
||||||
const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token
|
|
||||||
window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId
|
|
||||||
+ '&redirect_uri=' + redirectUri
|
|
||||||
+ '&response_type=' + responseType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改昵称
|
|
||||||
*/
|
|
||||||
function updateNickname() {
|
|
||||||
const nickname = prompt("请输入新的昵称", "");
|
|
||||||
if (!nickname) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 更新用户的昵称
|
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('更新昵称失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('更新昵称成功!');
|
|
||||||
$('#nicknameSpan').html(nickname);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌
|
|
||||||
*/
|
|
||||||
function refreshToken() {
|
|
||||||
const refreshToken = localStorage.getItem('REFRESH-TOKEN');
|
|
||||||
if (!refreshToken) {
|
|
||||||
alert("获取不到刷新令牌");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken,
|
|
||||||
method: 'POST',
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('刷新访问令牌失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('更新访问令牌成功!');
|
|
||||||
$('#accessTokenSpan').html(result.data.access_token);
|
|
||||||
|
|
||||||
// 设置到 localStorage 中
|
|
||||||
localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
|
|
||||||
localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登出,删除访问令牌
|
|
||||||
*/
|
|
||||||
function logout() {
|
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
|
||||||
if (!accessToken) {
|
|
||||||
location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/auth/logout",
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('退出登录失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('退出登录成功!');
|
|
||||||
// 删除 localStorage 中
|
|
||||||
localStorage.removeItem('ACCESS-TOKEN');
|
|
||||||
localStorage.removeItem('REFRESH-TOKEN');
|
|
||||||
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
|
||||||
// 情况一:未登录
|
|
||||||
if (!accessToken) {
|
|
||||||
$('#noLoginDiv').css("display", "block");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 情况二:已登录
|
|
||||||
$('#yesLoginDiv').css("display", "block");
|
|
||||||
$('#accessTokenSpan').html(accessToken);
|
|
||||||
// 获得登录用户的信息
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/user/get",
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('获得个人信息失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$('#nicknameSpan').html(result.data.nickname);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
|
|
||||||
<div id="noLoginDiv" style="display: none">
|
|
||||||
您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
|
|
||||||
<div id="yesLoginDiv" style="display: none">
|
|
||||||
您已登录!<button onclick="logout()">退出登录</button> <br />
|
|
||||||
昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
|
|
||||||
访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br />
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
body { /** 页面居中 */
|
|
||||||
border-radius: 20px;
|
|
||||||
height: 350px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%,-50%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
@ -1,65 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent -->
|
|
||||||
|
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
|
||||||
<artifactId>yudao-sso-demo-by-password</artifactId>
|
|
||||||
<version>1.0.0-snapshot</version>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
|
||||||
<description>基于密码模式,如何实现 SSO 单点登录?</description>
|
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<!-- Maven 相关 -->
|
|
||||||
<maven.compiler.source>21</maven.compiler.source>
|
|
||||||
<maven.compiler.target>21</maven.compiler.target>
|
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
<!-- 统一依赖管理 -->
|
|
||||||
<spring.boot.version>3.2.0</spring.boot.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencyManagement>
|
|
||||||
<dependencies>
|
|
||||||
<!-- 统一依赖管理 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
|
||||||
<version>${spring.boot.version}</version>
|
|
||||||
<type>pom</type>
|
|
||||||
<scope>import</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</dependencyManagement>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<!-- Web 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.hutool</groupId>
|
|
||||||
<artifactId>hutool-all</artifactId>
|
|
||||||
<version>5.8.22</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
|
||||||
<artifactId>lombok</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
@ -1,13 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
public class SSODemoApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(SSODemoApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.Base64Utils;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* OAuth 2.0 客户端
|
|
||||||
*
|
|
||||||
* 对应调用 OAuth2OpenController 接口
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OAuth2Client {
|
|
||||||
|
|
||||||
private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*
|
|
||||||
* 默认使用 1;如果使用别的租户,可以调整
|
|
||||||
*/
|
|
||||||
public static final Long TENANT_ID = 1L;
|
|
||||||
|
|
||||||
private static final String CLIENT_ID = "yudao-sso-demo-by-password";
|
|
||||||
private static final String CLIENT_SECRET = "test";
|
|
||||||
|
|
||||||
|
|
||||||
// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验访问令牌,并返回它的基本信息
|
|
||||||
*
|
|
||||||
* @param token 访问令牌
|
|
||||||
* @return 访问令牌的基本信息
|
|
||||||
*/
|
|
||||||
public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("token", token);
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/check-token",
|
|
||||||
HttpMethod.POST,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用刷新令牌,获得(刷新)访问令牌
|
|
||||||
*
|
|
||||||
* @param refreshToken 刷新令牌
|
|
||||||
* @return 访问令牌
|
|
||||||
*/
|
|
||||||
public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("grant_type", "refresh_token");
|
|
||||||
body.add("refresh_token", refreshToken);
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/token",
|
|
||||||
HttpMethod.POST,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除访问令牌
|
|
||||||
*
|
|
||||||
* @param token 访问令牌
|
|
||||||
* @return 成功
|
|
||||||
*/
|
|
||||||
public CommonResult<Boolean> revokeToken(String token) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", TENANT_ID.toString());
|
|
||||||
addClientHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
body.add("token", token);
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/token",
|
|
||||||
HttpMethod.DELETE,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addClientHeader(HttpHeaders headers) {
|
|
||||||
// client 拼接,需要 BASE64 编码
|
|
||||||
String client = CLIENT_ID + ":" + CLIENT_SECRET;
|
|
||||||
client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8));
|
|
||||||
headers.add("Authorization", "Basic " + client);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.*;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
|
||||||
import org.springframework.util.MultiValueMap;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户 User 信息的客户端
|
|
||||||
*
|
|
||||||
* 对应调用 OAuth2UserController 接口
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class UserClient {
|
|
||||||
|
|
||||||
private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user";
|
|
||||||
|
|
||||||
// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
|
||||||
|
|
||||||
public CommonResult<UserInfoRespDTO> getUser() {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
|
||||||
headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
|
|
||||||
addTokenHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/get",
|
|
||||||
HttpMethod.GET,
|
|
||||||
new HttpEntity<>(body, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) {
|
|
||||||
// 1.1 构建请求头
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
||||||
headers.set("tenant-id", OAuth2Client.TENANT_ID.toString());
|
|
||||||
addTokenHeader(headers);
|
|
||||||
// 1.2 构建请求参数
|
|
||||||
// 使用 updateReqDTO 即可
|
|
||||||
|
|
||||||
// 2. 执行请求
|
|
||||||
ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange(
|
|
||||||
BASE_URL + "/update",
|
|
||||||
HttpMethod.PUT,
|
|
||||||
new HttpEntity<>(updateReqDTO, headers),
|
|
||||||
new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失
|
|
||||||
Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功");
|
|
||||||
return exchange.getBody();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void addTokenHeader(HttpHeaders headers) {
|
|
||||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
|
||||||
Assert.notNull(loginUser, "登录用户不能为空");
|
|
||||||
headers.add("Authorization", "Bearer " + loginUser.getAccessToken());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用返回
|
|
||||||
*
|
|
||||||
* @param <T> 数据泛型
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class CommonResult<T> implements Serializable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 错误码
|
|
||||||
*/
|
|
||||||
private Integer code;
|
|
||||||
/**
|
|
||||||
* 返回数据
|
|
||||||
*/
|
|
||||||
private T data;
|
|
||||||
/**
|
|
||||||
* 错误提示,用户可阅读
|
|
||||||
*/
|
|
||||||
private String msg;
|
|
||||||
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌 Response DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class OAuth2AccessTokenRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌
|
|
||||||
*/
|
|
||||||
@JsonProperty("access_token")
|
|
||||||
private String accessToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌
|
|
||||||
*/
|
|
||||||
@JsonProperty("refresh_token")
|
|
||||||
private String refreshToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌类型
|
|
||||||
*/
|
|
||||||
@JsonProperty("token_type")
|
|
||||||
private String tokenType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过期时间;单位:秒
|
|
||||||
*/
|
|
||||||
@JsonProperty("expires_in")
|
|
||||||
private Long expiresIn;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权范围;如果多个授权范围,使用空格分隔
|
|
||||||
*/
|
|
||||||
private String scope;
|
|
||||||
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.oauth2;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验令牌 Response DTO
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class OAuth2CheckTokenRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
@JsonProperty("user_id")
|
|
||||||
private Long userId;
|
|
||||||
/**
|
|
||||||
* 用户类型
|
|
||||||
*/
|
|
||||||
@JsonProperty("user_type")
|
|
||||||
private Integer userType;
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*/
|
|
||||||
@JsonProperty("tenant_id")
|
|
||||||
private Long tenantId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端编号
|
|
||||||
*/
|
|
||||||
@JsonProperty("client_id")
|
|
||||||
private String clientId;
|
|
||||||
/**
|
|
||||||
* 授权范围
|
|
||||||
*/
|
|
||||||
private List<String> scopes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌
|
|
||||||
*/
|
|
||||||
@JsonProperty("access_token")
|
|
||||||
private String accessToken;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 过期时间
|
|
||||||
*
|
|
||||||
* 时间戳 / 1000,即单位:秒
|
|
||||||
*/
|
|
||||||
private Long exp;
|
|
||||||
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.user;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得用户基本信息 Response dto
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class UserInfoRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户账号
|
|
||||||
*/
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户昵称
|
|
||||||
*/
|
|
||||||
private String nickname;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户邮箱
|
|
||||||
*/
|
|
||||||
private String email;
|
|
||||||
/**
|
|
||||||
* 手机号码
|
|
||||||
*/
|
|
||||||
private String mobile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户性别
|
|
||||||
*/
|
|
||||||
private Integer sex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户头像
|
|
||||||
*/
|
|
||||||
private String avatar;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所在部门
|
|
||||||
*/
|
|
||||||
private Dept dept;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 所属岗位数组
|
|
||||||
*/
|
|
||||||
private List<Post> posts;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Dept {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 部门名称
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Post {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位名称
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.client.dto.user;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户基本信息 Request DTO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class UserUpdateReqDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户昵称
|
|
||||||
*/
|
|
||||||
private String nickname;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户邮箱
|
|
||||||
*/
|
|
||||||
private String email;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 手机号码
|
|
||||||
*/
|
|
||||||
private String mobile;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户性别
|
|
||||||
*/
|
|
||||||
private Integer sex;
|
|
||||||
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.controller;
|
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/auth")
|
|
||||||
public class AuthController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private OAuth2Client oauth2Client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用刷新令牌,获得(刷新)访问令牌
|
|
||||||
*
|
|
||||||
* @param refreshToken 刷新令牌
|
|
||||||
* @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
|
|
||||||
*/
|
|
||||||
@PostMapping("/refresh-token")
|
|
||||||
public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) {
|
|
||||||
return oauth2Client.refreshToken(refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*
|
|
||||||
* @param request 请求
|
|
||||||
* @return 成功
|
|
||||||
*/
|
|
||||||
@PostMapping("/logout")
|
|
||||||
public CommonResult<Boolean> logout(HttpServletRequest request) {
|
|
||||||
String token = SecurityUtils.obtainAuthorization(request, "Authorization");
|
|
||||||
if (StrUtil.isNotBlank(token)) {
|
|
||||||
return oauth2Client.revokeToken(token);
|
|
||||||
}
|
|
||||||
// 返回成功
|
|
||||||
return new CommonResult<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.controller;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.UserClient;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/user")
|
|
||||||
public class UserController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private UserClient userClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得当前登录用户的基本信息
|
|
||||||
*
|
|
||||||
* @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段
|
|
||||||
*/
|
|
||||||
@GetMapping("/get")
|
|
||||||
public CommonResult<UserInfoRespDTO> getUser() {
|
|
||||||
return userClient.getUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新当前登录用户的昵称
|
|
||||||
*
|
|
||||||
* @param nickname 昵称
|
|
||||||
* @return 成功
|
|
||||||
*/
|
|
||||||
@PutMapping("/update")
|
|
||||||
public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) {
|
|
||||||
UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null);
|
|
||||||
return userClient.updateUser(updateReqDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.config;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.HttpMethod;
|
|
||||||
import org.springframework.security.config.Customizer;
|
|
||||||
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.configurers.AbstractHttpConfigurer;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
@EnableWebSecurity
|
|
||||||
public class SecurityConfiguration {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private TokenAuthenticationFilter tokenAuthenticationFilter;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private AccessDeniedHandlerImpl accessDeniedHandler;
|
|
||||||
@Resource
|
|
||||||
private AuthenticationEntryPoint authenticationEntryPoint;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
|
||||||
// 设置 URL 安全权限
|
|
||||||
httpSecurity
|
|
||||||
// 开启跨域
|
|
||||||
.cors(Customizer.withDefaults())
|
|
||||||
// CSRF 禁用,因为不使用 Session
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
|
||||||
// 一堆自定义的 Spring Security 处理器
|
|
||||||
.exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint)
|
|
||||||
.accessDeniedHandler(accessDeniedHandler));
|
|
||||||
|
|
||||||
// 设置每个请求的权限
|
|
||||||
httpSecurity.authorizeHttpRequests(c -> c
|
|
||||||
// 1. 静态资源,可匿名访问
|
|
||||||
.requestMatchers(HttpMethod.GET, "/*.html", "/*.html", "/*.css", "/*.js").permitAll()
|
|
||||||
// 2. 登录相关的接口,可匿名访问
|
|
||||||
.requestMatchers("/auth/login-by-code").permitAll()
|
|
||||||
.requestMatchers("/auth/refresh-token").permitAll()
|
|
||||||
.requestMatchers("/auth/logout").permitAll())
|
|
||||||
// 3. 兜底规则,必须认证
|
|
||||||
.authorizeHttpRequests(c -> c.anyRequest().authenticated());
|
|
||||||
|
|
||||||
// 添加 Token Filter
|
|
||||||
httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
|
||||||
return httpSecurity.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录用户信息
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class LoginUser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户编号
|
|
||||||
*/
|
|
||||||
private Long id;
|
|
||||||
/**
|
|
||||||
* 用户类型
|
|
||||||
*/
|
|
||||||
private Integer userType;
|
|
||||||
/**
|
|
||||||
* 租户编号
|
|
||||||
*/
|
|
||||||
private Long tenantId;
|
|
||||||
/**
|
|
||||||
* 授权范围
|
|
||||||
*/
|
|
||||||
private List<String> scopes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问令牌
|
|
||||||
*/
|
|
||||||
private String accessToken;
|
|
||||||
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.filter;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.OAuth2Client;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token 过滤器,验证 token 的有效性
|
|
||||||
* 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private OAuth2Client oauth2Client;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
|
||||||
FilterChain filterChain) throws ServletException, IOException {
|
|
||||||
// 1. 获得访问令牌
|
|
||||||
String token = SecurityUtils.obtainAuthorization(request, "Authorization");
|
|
||||||
if (StringUtils.hasText(token)) {
|
|
||||||
// 2. 基于 token 构建登录用户
|
|
||||||
LoginUser loginUser = buildLoginUserByToken(token);
|
|
||||||
// 3. 设置当前用户
|
|
||||||
if (loginUser != null) {
|
|
||||||
SecurityUtils.setLoginUser(loginUser, request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 继续过滤链
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoginUser buildLoginUserByToken(String token) {
|
|
||||||
try {
|
|
||||||
CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token);
|
|
||||||
OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData();
|
|
||||||
if (accessToken == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 构建登录用户
|
|
||||||
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
|
|
||||||
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes())
|
|
||||||
.setAccessToken(accessToken.getAccessToken());
|
|
||||||
} catch (Exception exception) {
|
|
||||||
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.handler;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。
|
|
||||||
*
|
|
||||||
* 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@SuppressWarnings("JavadocReference")
|
|
||||||
@Slf4j
|
|
||||||
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
// 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏
|
|
||||||
log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(),
|
|
||||||
SecurityUtils.getLoginUserId(), e);
|
|
||||||
// 返回 403
|
|
||||||
CommonResult<Object> result = new CommonResult<>();
|
|
||||||
result.setCode(HttpStatus.FORBIDDEN.value());
|
|
||||||
result.setMsg("没有该操作权限");
|
|
||||||
ServletUtils.writeJSON(response, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.handler;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.client.dto.CommonResult;
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页
|
|
||||||
*
|
|
||||||
* 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
@SuppressWarnings("JavadocReference") // 忽略文档引用报错
|
|
||||||
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
|
|
||||||
log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e);
|
|
||||||
// 返回 401
|
|
||||||
CommonResult<Object> result = new CommonResult<>();
|
|
||||||
result.setCode(HttpStatus.UNAUTHORIZED.value());
|
|
||||||
result.setMsg("账号未登录");
|
|
||||||
ServletUtils.writeJSON(response, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.util;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.ssodemo.framework.core.LoginUser;
|
|
||||||
import org.springframework.lang.Nullable;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安全服务工具类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class SecurityUtils {
|
|
||||||
|
|
||||||
public static final String AUTHORIZATION_BEARER = "Bearer";
|
|
||||||
|
|
||||||
private SecurityUtils() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从请求中,获得认证 Token
|
|
||||||
*
|
|
||||||
* @param request 请求
|
|
||||||
* @param header 认证 Token 对应的 Header 名字
|
|
||||||
* @return 认证 Token
|
|
||||||
*/
|
|
||||||
public static String obtainAuthorization(HttpServletRequest request, String header) {
|
|
||||||
String authorization = request.getHeader(header);
|
|
||||||
if (!StringUtils.hasText(authorization)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int index = authorization.indexOf(AUTHORIZATION_BEARER + " ");
|
|
||||||
if (index == -1) { // 未找到
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return authorization.substring(index + 7).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得当前认证信息
|
|
||||||
*
|
|
||||||
* @return 认证信息
|
|
||||||
*/
|
|
||||||
public static Authentication getAuthentication() {
|
|
||||||
SecurityContext context = SecurityContextHolder.getContext();
|
|
||||||
if (context == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return context.getAuthentication();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前用户
|
|
||||||
*
|
|
||||||
* @return 当前用户
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static LoginUser getLoginUser() {
|
|
||||||
Authentication authentication = getAuthentication();
|
|
||||||
if (authentication == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得当前用户的编号,从上下文中
|
|
||||||
*
|
|
||||||
* @return 用户编号
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Long getLoginUserId() {
|
|
||||||
LoginUser loginUser = getLoginUser();
|
|
||||||
return loginUser != null ? loginUser.getId() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置当前用户
|
|
||||||
*
|
|
||||||
* @param loginUser 登录用户
|
|
||||||
* @param request 请求
|
|
||||||
*/
|
|
||||||
public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
|
|
||||||
// 创建 Authentication,并设置到上下文
|
|
||||||
Authentication authentication = buildAuthentication(loginUser, request);
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
|
|
||||||
// 创建 UsernamePasswordAuthenticationToken 对象
|
|
||||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
|
||||||
loginUser, null, Collections.emptyList());
|
|
||||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
||||||
return authenticationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package cn.iocoder.yudao.ssodemo.framework.core.util;
|
|
||||||
|
|
||||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
|
||||||
import cn.hutool.extra.servlet.ServletUtil;
|
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 客户端工具类
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class ServletUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 返回 JSON 字符串
|
|
||||||
*
|
|
||||||
* @param response 响应
|
|
||||||
* @param object 对象,会序列化成 JSON 字符串
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
|
||||||
public static void writeJSON(HttpServletResponse response, Object object) {
|
|
||||||
String content = JSONUtil.toJsonStr(object);
|
|
||||||
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void write(HttpServletResponse response, String text, String contentType) {
|
|
||||||
JakartaServletUtil.write(response, text, contentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
server:
|
|
||||||
port: 18080
|
|
@ -1,154 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>首页</title>
|
|
||||||
<!-- jQuery:操作 dom、发起请求等 -->
|
|
||||||
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
|
|
||||||
|
|
||||||
<script type="application/javascript">
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 跳转单点登录
|
|
||||||
*/
|
|
||||||
function passwordLogin() {
|
|
||||||
window.location.href = '/login.html'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改昵称
|
|
||||||
*/
|
|
||||||
function updateNickname() {
|
|
||||||
const nickname = prompt("请输入新的昵称", "");
|
|
||||||
if (!nickname) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 更新用户的昵称
|
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/user/update?nickname=" + nickname,
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('更新昵称失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('更新昵称成功!');
|
|
||||||
$('#nicknameSpan').html(nickname);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 刷新令牌
|
|
||||||
*/
|
|
||||||
function refreshToken() {
|
|
||||||
const refreshToken = localStorage.getItem('REFRESH-TOKEN');
|
|
||||||
if (!refreshToken) {
|
|
||||||
alert("获取不到刷新令牌");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken,
|
|
||||||
method: 'POST',
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('刷新访问令牌失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('更新访问令牌成功!');
|
|
||||||
$('#accessTokenSpan').html(result.data.access_token);
|
|
||||||
|
|
||||||
// 设置到 localStorage 中
|
|
||||||
localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
|
|
||||||
localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登出,删除访问令牌
|
|
||||||
*/
|
|
||||||
function logout() {
|
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
|
||||||
if (!accessToken) {
|
|
||||||
location.reload();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/auth/logout",
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('退出登录失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
alert('退出登录成功!');
|
|
||||||
// 删除 localStorage 中
|
|
||||||
localStorage.removeItem('ACCESS-TOKEN');
|
|
||||||
localStorage.removeItem('REFRESH-TOKEN');
|
|
||||||
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(function () {
|
|
||||||
const accessToken = localStorage.getItem('ACCESS-TOKEN');
|
|
||||||
// 情况一:未登录
|
|
||||||
if (!accessToken) {
|
|
||||||
$('#noLoginDiv').css("display", "block");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 情况二:已登录
|
|
||||||
$('#yesLoginDiv').css("display", "block");
|
|
||||||
$('#accessTokenSpan').html(accessToken);
|
|
||||||
// 获得登录用户的信息
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:18080/user/get",
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + accessToken
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('获得个人信息失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$('#nicknameSpan').html(result.data.nickname);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 -->
|
|
||||||
<div id="noLoginDiv" style="display: none">
|
|
||||||
您未登录,点击 <a href="#" onclick="passwordLogin()">跳转 </a> 账号密码登录
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 -->
|
|
||||||
<div id="yesLoginDiv" style="display: none">
|
|
||||||
您已登录!<button onclick="logout()">退出登录</button> <br />
|
|
||||||
昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br />
|
|
||||||
访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br />
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
body { /** 页面居中 */
|
|
||||||
border-radius: 20px;
|
|
||||||
height: 350px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%,-50%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
@ -1,74 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>登录</title>
|
|
||||||
<!-- jQuery:操作 dom、发起请求等 -->
|
|
||||||
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script>
|
|
||||||
|
|
||||||
<script type="application/javascript">
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 账号密码登录
|
|
||||||
*/
|
|
||||||
function login() {
|
|
||||||
const clientId = 'yudao-sso-demo-by-password'; // 可以改写成,你的 clientId
|
|
||||||
const clientSecret = 'test'; // 可以改写成,你的 clientSecret
|
|
||||||
const grantType = 'password'; // 密码模式
|
|
||||||
|
|
||||||
// 账号 + 密码
|
|
||||||
const username = $('#username').val();
|
|
||||||
const password = $('#password').val();
|
|
||||||
if (username.length === 0 || password.length === 0) {
|
|
||||||
alert('账号或密码未输入');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发起请求
|
|
||||||
$.ajax({
|
|
||||||
url: "http://127.0.0.1:48080/admin-api/system/oauth2/token?"
|
|
||||||
// 客户端
|
|
||||||
+ "client_id=" + clientId
|
|
||||||
+ "&client_secret=" + clientSecret
|
|
||||||
// 密码模式的参数
|
|
||||||
+ "&grant_type=" + grantType
|
|
||||||
+ "&username=" + username
|
|
||||||
+ "&password=" + password
|
|
||||||
+ '&scope=user.read user.write',
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'tenant-id': '1', // 多租户编号,写死
|
|
||||||
},
|
|
||||||
success: function (result) {
|
|
||||||
if (result.code !== 0) {
|
|
||||||
alert('登录失败,原因:' + result.msg)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 设置到 localStorage 中
|
|
||||||
localStorage.setItem('ACCESS-TOKEN', result.data.access_token);
|
|
||||||
localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token);
|
|
||||||
|
|
||||||
// 提示登录成功
|
|
||||||
alert('登录成功!点击确认,跳转回首页');
|
|
||||||
window.location.href = '/index.html';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
账号:<input id="username" value="admin" /> <br />
|
|
||||||
密码:<input id="password" value="admin123" > <br />
|
|
||||||
<button style="float: right; margin-top: 5px;" onclick="login()">登录</button>
|
|
||||||
</body>
|
|
||||||
<style>
|
|
||||||
body { /** 页面居中 */
|
|
||||||
border-radius: 20px;
|
|
||||||
height: 350px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%,-50%);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</html>
|
|
@ -227,27 +227,27 @@ public class LocalDateTimeUtils {
|
|||||||
// 2. 循环,生成时间范围
|
// 2. 循环,生成时间范围
|
||||||
List<LocalDateTime[]> timeRanges = new ArrayList<>();
|
List<LocalDateTime[]> timeRanges = new ArrayList<>();
|
||||||
switch (intervalEnum) {
|
switch (intervalEnum) {
|
||||||
case DateIntervalEnum.DAY:
|
case DAY:
|
||||||
while (startTime.isBefore(endTime)) {
|
while (startTime.isBefore(endTime)) {
|
||||||
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
|
timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});
|
||||||
startTime = startTime.plusDays(1);
|
startTime = startTime.plusDays(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DateIntervalEnum.WEEK:
|
case WEEK:
|
||||||
while (startTime.isBefore(endTime)) {
|
while (startTime.isBefore(endTime)) {
|
||||||
LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
|
LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);
|
||||||
timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
|
timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});
|
||||||
startTime = endOfWeek.plusNanos(1);
|
startTime = endOfWeek.plusNanos(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DateIntervalEnum.MONTH:
|
case MONTH:
|
||||||
while (startTime.isBefore(endTime)) {
|
while (startTime.isBefore(endTime)) {
|
||||||
LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
|
LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);
|
||||||
timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
|
timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});
|
||||||
startTime = endOfMonth.plusNanos(1);
|
startTime = endOfMonth.plusNanos(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DateIntervalEnum.QUARTER:
|
case QUARTER:
|
||||||
while (startTime.isBefore(endTime)) {
|
while (startTime.isBefore(endTime)) {
|
||||||
int quarterOfYear = getQuarterOfYear(startTime);
|
int quarterOfYear = getQuarterOfYear(startTime);
|
||||||
LocalDateTime quarterEnd = quarterOfYear == 4
|
LocalDateTime quarterEnd = quarterOfYear == 4
|
||||||
@ -257,7 +257,7 @@ public class LocalDateTimeUtils {
|
|||||||
startTime = quarterEnd.plusNanos(1);
|
startTime = quarterEnd.plusNanos(1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case DateIntervalEnum.YEAR:
|
case YEAR:
|
||||||
while (startTime.isBefore(endTime)) {
|
while (startTime.isBefore(endTime)) {
|
||||||
LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
|
LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);
|
||||||
timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
|
timeRanges.add(new LocalDateTime[]{startTime, endOfYear});
|
||||||
@ -290,16 +290,16 @@ public class LocalDateTimeUtils {
|
|||||||
|
|
||||||
// 2. 循环,生成时间范围
|
// 2. 循环,生成时间范围
|
||||||
switch (intervalEnum) {
|
switch (intervalEnum) {
|
||||||
case DateIntervalEnum.DAY:
|
case DAY:
|
||||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);
|
||||||
case DateIntervalEnum.WEEK:
|
case WEEK:
|
||||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)
|
||||||
+ StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
|
+ StrUtil.format("(第 {} 周)", LocalDateTimeUtil.weekOfYear(startTime));
|
||||||
case DateIntervalEnum.MONTH:
|
case MONTH:
|
||||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);
|
||||||
case DateIntervalEnum.QUARTER:
|
case QUARTER:
|
||||||
return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
|
return StrUtil.format("{}-Q{}", startTime.getYear(), getQuarterOfYear(startTime));
|
||||||
case DateIntervalEnum.YEAR:
|
case YEAR:
|
||||||
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
|
return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Invalid interval: " + interval);
|
throw new IllegalArgumentException("Invalid interval: " + interval);
|
||||||
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.mybatis.config;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
|
import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
|
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
|
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
|
||||||
import com.baomidou.mybatisplus.extension.incrementer.*;
|
import com.baomidou.mybatisplus.extension.incrementer.*;
|
||||||
@ -20,7 +21,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
|||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 目的:先于 MyBatis Plus 自动配置,避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志
|
||||||
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class,
|
@MapperScan(value = "${yudao.info.base-package}", annotationClass = Mapper.class,
|
||||||
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
|
lazyInitialization = "${mybatis.lazy-initialization:false}") // Mapper 懒加载,目前仅用于单元测试
|
||||||
public class YudaoMybatisAutoConfiguration {
|
public class YudaoMybatisAutoConfiguration {
|
||||||
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.redis.config;
|
|||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||||
@ -12,7 +13,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
|||||||
/**
|
/**
|
||||||
* Redis 配置类
|
* Redis 配置类
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean
|
||||||
public class YudaoRedisAutoConfiguration {
|
public class YudaoRedisAutoConfiguration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,8 +10,10 @@ import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService
|
|||||||
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
|
||||||
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
|
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
|
||||||
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
@ -20,8 +22,6 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
|||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Security 自动配置类,主要用于相关组件的配置
|
* Spring Security 自动配置类,主要用于相关组件的配置
|
||||||
*
|
*
|
||||||
@ -31,6 +31,7 @@ import jakarta.annotation.Resource;
|
|||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
|
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
|
||||||
@EnableConfigurationProperties(SecurityProperties.class)
|
@EnableConfigurationProperties(SecurityProperties.class)
|
||||||
public class YudaoSecurityAutoConfiguration {
|
public class YudaoSecurityAutoConfiguration {
|
||||||
|
|
||||||
|
@ -5,14 +5,16 @@ import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter
|
|||||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||||
import com.google.common.collect.HashMultimap;
|
import com.google.common.collect.HashMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.annotation.security.PermitAll;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
@ -27,8 +29,6 @@ import org.springframework.web.method.HandlerMethod;
|
|||||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import jakarta.annotation.security.PermitAll;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -39,6 +39,7 @@ import java.util.Set;
|
|||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
|
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
|
||||||
@EnableMethodSecurity(securedEnabled = true)
|
@EnableMethodSecurity(securedEnabled = true)
|
||||||
public class YudaoWebSecurityConfigurerAdapter {
|
public class YudaoWebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ public class BaseDbAndRedisUnitTest {
|
|||||||
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
|
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
|
||||||
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
||||||
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||||
RedissonAutoConfiguration.class, // Redisson 自动高配置类
|
RedissonAutoConfiguration.class, // Redisson 自动配置类
|
||||||
})
|
})
|
||||||
public static class Application {
|
public static class Application {
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ public class BaseRedisUnitTest {
|
|||||||
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
|
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
|
||||||
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||||
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
||||||
RedissonAutoConfiguration.class, // Redisson 自动高配置类
|
RedissonAutoConfiguration.class, // Redisson 自动配置类
|
||||||
})
|
})
|
||||||
public static class Application {
|
public static class Application {
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -91,6 +92,7 @@ public class YudaoSwaggerAutoConfiguration {
|
|||||||
* 自定义 OpenAPI 处理器
|
* 自定义 OpenAPI 处理器
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
|
@Primary // 目的:以我们创建的 OpenAPIService Bean 为主,避免一键改包后,启动报错!
|
||||||
public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
|
public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,
|
||||||
SecurityService securityParser,
|
SecurityService securityParser,
|
||||||
SpringDocConfigProperties springDocConfigProperties,
|
SpringDocConfigProperties springDocConfigProperties,
|
||||||
@ -98,7 +100,6 @@ public class YudaoSwaggerAutoConfiguration {
|
|||||||
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
|
Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,
|
||||||
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
|
Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,
|
||||||
Optional<JavadocProvider> javadocProvider) {
|
Optional<JavadocProvider> javadocProvider) {
|
||||||
|
|
||||||
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties,
|
return new OpenAPIService(openAPI, securityParser, springDocConfigProperties,
|
||||||
propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
|
|||||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
|
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
@ -122,7 +123,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
|
|||||||
* @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}
|
* @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
|
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
|
||||||
return restTemplateBuilder.build();
|
return restTemplateBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01;
|
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo01;
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 示例联系人 DO
|
* 示例联系人 DO
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@TableName("infra_demo01_contact")
|
@TableName("yudao_demo01_contact")
|
||||||
@KeySequence("infra_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
@KeySequence("yudao_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02;
|
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo02;
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 示例分类 DO
|
* 示例分类 DO
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@TableName("infra_demo02_category")
|
@TableName("yudao_demo02_category")
|
||||||
@KeySequence("infra_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
@KeySequence("yudao_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
|
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学生课程 DO
|
* 学生课程 DO
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@TableName("infra_demo03_course")
|
@TableName("yudao_demo03_course")
|
||||||
@KeySequence("infra_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
@KeySequence("yudao_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
|
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学生班级 DO
|
* 学生班级 DO
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@TableName("infra_demo03_grade")
|
@TableName("yudao_demo03_grade")
|
||||||
@KeySequence("infra_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
@KeySequence("yudao_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
|
package cn.iocoder.yudao.module.infra.dal.dataobject.demo.demo03;
|
||||||
|
|
||||||
import lombok.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import com.baomidou.mybatisplus.annotation.*;
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 学生 DO
|
* 学生 DO
|
||||||
*
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@TableName("infra_demo03_student")
|
@TableName("yudao_demo03_student")
|
||||||
@KeySequence("infra_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
@KeySequence("yudao_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@ToString(callSuper = true)
|
@ToString(callSuper = true)
|
||||||
|
@ -24,12 +24,6 @@ public class ProductSpuRespDTO {
|
|||||||
* 商品名称
|
* 商品名称
|
||||||
*/
|
*/
|
||||||
private String name;
|
private String name;
|
||||||
/**
|
|
||||||
* 单位
|
|
||||||
*
|
|
||||||
* 对应 product_unit 数据字典
|
|
||||||
*/
|
|
||||||
private Integer unit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品分类编号
|
* 商品分类编号
|
||||||
|
@ -119,7 +119,7 @@ public class CouponServiceImpl implements CouponService {
|
|||||||
Integer status = LocalDateTimeUtils.beforeNow(coupon.getValidEndTime())
|
Integer status = LocalDateTimeUtils.beforeNow(coupon.getValidEndTime())
|
||||||
? CouponStatusEnum.EXPIRE.getStatus() // 退还时可能已经过期了
|
? CouponStatusEnum.EXPIRE.getStatus() // 退还时可能已经过期了
|
||||||
: CouponStatusEnum.UNUSED.getStatus();
|
: CouponStatusEnum.UNUSED.getStatus();
|
||||||
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
|
int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.USED.getStatus(),
|
||||||
new CouponDO().setStatus(status));
|
new CouponDO().setStatus(status));
|
||||||
if (updateCount == 0) {
|
if (updateCount == 0) {
|
||||||
throw exception(COUPON_STATUS_NOT_USED);
|
throw exception(COUPON_STATUS_NOT_USED);
|
||||||
|
@ -30,7 +30,7 @@ public class BaseDbAndRedisIntegrationTest {
|
|||||||
// Redis 配置类
|
// Redis 配置类
|
||||||
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||||
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
||||||
RedissonAutoConfiguration.class, // Redisson 自动高配置类
|
RedissonAutoConfiguration.class, // Redisson 自动配置类
|
||||||
})
|
})
|
||||||
public static class Application {
|
public static class Application {
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public class BaseRedisIntegrationTest {
|
|||||||
// Redis 配置类
|
// Redis 配置类
|
||||||
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||||
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
||||||
RedissonAutoConfiguration.class, // Redisson 自动高配置类
|
RedissonAutoConfiguration.class, // Redisson 自动配置类
|
||||||
})
|
})
|
||||||
public static class Application {
|
public static class Application {
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ public class SmsCallbackController {
|
|||||||
|
|
||||||
@PostMapping("/aliyun")
|
@PostMapping("/aliyun")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/document_detail/120998.html 文档")
|
@Operation(summary = "阿里云短信的回调", description = "参见 https://help.aliyun.com/zh/sms/developer-reference/configure-delivery-receipts-1 文档")
|
||||||
public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
|
public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {
|
||||||
String text = ServletUtils.getBody(request);
|
String text = ServletUtils.getBody(request);
|
||||||
smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);
|
smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);
|
||||||
@ -35,7 +35,7 @@ public class SmsCallbackController {
|
|||||||
|
|
||||||
@PostMapping("/tencent")
|
@PostMapping("/tencent")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/52077 文档")
|
@Operation(summary = "腾讯云短信的回调", description = "参见 https://cloud.tencent.com/document/product/382/59178 文档")
|
||||||
public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
|
public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {
|
||||||
String text = ServletUtils.getBody(request);
|
String text = ServletUtils.getBody(request);
|
||||||
smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);
|
smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);
|
||||||
|
@ -34,7 +34,8 @@ public class ProjectReactor {
|
|||||||
* 白名单文件,不进行重写,避免出问题
|
* 白名单文件,不进行重写,避免出问题
|
||||||
*/
|
*/
|
||||||
private static final Set<String> WHITE_FILE_TYPES = SetUtils.asSet("gif", "jpg", "svg", "png", // 图片
|
private static final Set<String> WHITE_FILE_TYPES = SetUtils.asSet("gif", "jpg", "svg", "png", // 图片
|
||||||
"eot", "woff2", "ttf", "woff"); // 字体
|
"eot", "woff2", "ttf", "woff", // 字体
|
||||||
|
"xdb"); // IP 库
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
Loading…
Reference in New Issue
Block a user