Commit 9cde6c45ab985532fe6b3c4bc569ac9677554729
Committed by
Gitee
!272 增加“基于授权码模式,实现 SSO 单点登录“示例
Merge pull request !272 from 芋道源码/feature/sso-example
Showing
30 changed files
with
1289 additions
and
47 deletions
pom.xml
yudao-example/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | |
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 5 | + <modelVersion>4.0.0</modelVersion> | |
| 6 | + | |
| 7 | + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent --> | |
| 8 | + | |
| 9 | + <groupId>cn.iocoder.boot</groupId> | |
| 10 | + <artifactId>yudao-example</artifactId> | |
| 11 | + <version>1.0.0-snapshot</version> | |
| 12 | + <packaging>pom</packaging> | |
| 13 | + <modules> | |
| 14 | + <module>yudao-sso-demo-by-code</module> | |
| 15 | + </modules> | |
| 16 | + | |
| 17 | + <name>${project.artifactId}</name> | |
| 18 | + <description>提供各种示例,例如说:SSO 单点登录</description> | |
| 19 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | |
| 20 | + | |
| 21 | +</project> | ... | ... |
yudao-example/yudao-sso-demo-by-code/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project xmlns="http://maven.apache.org/POM/4.0.0" | |
| 3 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| 4 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 5 | + <modelVersion>4.0.0</modelVersion> | |
| 6 | + | |
| 7 | + <!-- 由于方便大家拷贝,使用不使用 yudao 作为 Maven parent --> | |
| 8 | + | |
| 9 | + <groupId>cn.iocoder.boot</groupId> | |
| 10 | + <artifactId>yudao-sso-demo-by-code</artifactId> | |
| 11 | + <version>1.0.0-snapshot</version> | |
| 12 | + <packaging>jar</packaging> | |
| 13 | + | |
| 14 | + <name>${project.artifactId}</name> | |
| 15 | + <description>基于授权码模式,如何实现 SSO 单点登录?</description> | |
| 16 | + <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | |
| 17 | + | |
| 18 | + <properties> | |
| 19 | + <!-- Maven 相关 --> | |
| 20 | + <maven.compiler.source>8</maven.compiler.source> | |
| 21 | + <maven.compiler.target>8</maven.compiler.target> | |
| 22 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |
| 23 | + <!-- 统一依赖管理 --> | |
| 24 | + <spring.boot.version>2.6.10</spring.boot.version> | |
| 25 | + </properties> | |
| 26 | + | |
| 27 | + <dependencyManagement> | |
| 28 | + <dependencies> | |
| 29 | + <!-- 统一依赖管理 --> | |
| 30 | + <dependency> | |
| 31 | + <groupId>org.springframework.boot</groupId> | |
| 32 | + <artifactId>spring-boot-dependencies</artifactId> | |
| 33 | + <version>${spring.boot.version}</version> | |
| 34 | + <type>pom</type> | |
| 35 | + <scope>import</scope> | |
| 36 | + </dependency> | |
| 37 | + </dependencies> | |
| 38 | + </dependencyManagement> | |
| 39 | + | |
| 40 | + <dependencies> | |
| 41 | + <!-- Web 相关 --> | |
| 42 | + <dependency> | |
| 43 | + <groupId>org.springframework.boot</groupId> | |
| 44 | + <artifactId>spring-boot-starter-web</artifactId> | |
| 45 | + </dependency> | |
| 46 | + | |
| 47 | + <dependency> | |
| 48 | + <groupId>org.springframework.boot</groupId> | |
| 49 | + <artifactId>spring-boot-starter-security</artifactId> | |
| 50 | + </dependency> | |
| 51 | + | |
| 52 | + <dependency> | |
| 53 | + <groupId>cn.hutool</groupId> | |
| 54 | + <artifactId>hutool-all</artifactId> | |
| 55 | + <version>5.8.5</version> | |
| 56 | + </dependency> | |
| 57 | + | |
| 58 | + <dependency> | |
| 59 | + <groupId>org.projectlombok</groupId> | |
| 60 | + <artifactId>lombok</artifactId> | |
| 61 | + <optional>true</optional> | |
| 62 | + </dependency> | |
| 63 | + </dependencies> | |
| 64 | + | |
| 65 | +</project> | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/SSODemoApplication.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo; | |
| 2 | + | |
| 3 | +import org.springframework.boot.SpringApplication; | |
| 4 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | |
| 5 | + | |
| 6 | +@SpringBootApplication | |
| 7 | +public class SSODemoApplication { | |
| 8 | + | |
| 9 | + public static void main(String[] args) { | |
| 10 | + SpringApplication.run(SSODemoApplication.class, args); | |
| 11 | + } | |
| 12 | + | |
| 13 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/OAuth2Client.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | |
| 6 | +import org.springframework.core.ParameterizedTypeReference; | |
| 7 | +import org.springframework.http.*; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | +import org.springframework.util.Assert; | |
| 10 | +import org.springframework.util.Base64Utils; | |
| 11 | +import org.springframework.util.LinkedMultiValueMap; | |
| 12 | +import org.springframework.util.MultiValueMap; | |
| 13 | +import org.springframework.web.client.RestTemplate; | |
| 14 | + | |
| 15 | +import java.nio.charset.StandardCharsets; | |
| 16 | + | |
| 17 | +/** | |
| 18 | + * OAuth 2.0 客户端 | |
| 19 | + * | |
| 20 | + * 对应调用 OAuth2OpenController 接口 | |
| 21 | + */ | |
| 22 | +@Component | |
| 23 | +public class OAuth2Client { | |
| 24 | + | |
| 25 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api/system/oauth2"; | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 租户编号 | |
| 29 | + * | |
| 30 | + * 默认使用 1;如果使用别的租户,可以调整 | |
| 31 | + */ | |
| 32 | + public static final Long TENANT_ID = 1L; | |
| 33 | + | |
| 34 | + private static final String CLIENT_ID = "yudao-sso-demo-by-code"; | |
| 35 | + private static final String CLIENT_SECRET = "test"; | |
| 36 | + | |
| 37 | + | |
| 38 | +// @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | |
| 39 | + private final RestTemplate restTemplate = new RestTemplate(); | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 使用 code 授权码,获得访问令牌 | |
| 43 | + * | |
| 44 | + * @param code 授权码 | |
| 45 | + * @param redirectUri 重定向 URI | |
| 46 | + * @return 访问令牌 | |
| 47 | + */ | |
| 48 | + public CommonResult<OAuth2AccessTokenRespDTO> postAccessToken(String code, String redirectUri) { | |
| 49 | + // 1.1 构建请求头 | |
| 50 | + HttpHeaders headers = new HttpHeaders(); | |
| 51 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |
| 52 | + headers.set("tenant-id", TENANT_ID.toString()); | |
| 53 | + addClientHeader(headers); | |
| 54 | + // 1.2 构建请求参数 | |
| 55 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | |
| 56 | + body.add("grant_type", "authorization_code"); | |
| 57 | + body.add("code", code); | |
| 58 | + body.add("redirect_uri", redirectUri); | |
| 59 | +// body.add("state", ""); // 选填;填了会校验 | |
| 60 | + | |
| 61 | + // 2. 执行请求 | |
| 62 | + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | |
| 63 | + BASE_URL + "/token", | |
| 64 | + HttpMethod.POST, | |
| 65 | + new HttpEntity<>(body, headers), | |
| 66 | + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 67 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 68 | + return exchange.getBody(); | |
| 69 | + } | |
| 70 | + | |
| 71 | + /** | |
| 72 | + * 校验访问令牌,并返回它的基本信息 | |
| 73 | + * | |
| 74 | + * @param token 访问令牌 | |
| 75 | + * @return 访问令牌的基本信息 | |
| 76 | + */ | |
| 77 | + public CommonResult<OAuth2CheckTokenRespDTO> checkToken(String token) { | |
| 78 | + // 1.1 构建请求头 | |
| 79 | + HttpHeaders headers = new HttpHeaders(); | |
| 80 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |
| 81 | + headers.set("tenant-id", TENANT_ID.toString()); | |
| 82 | + addClientHeader(headers); | |
| 83 | + // 1.2 构建请求参数 | |
| 84 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | |
| 85 | + body.add("token", token); | |
| 86 | + | |
| 87 | + // 2. 执行请求 | |
| 88 | + ResponseEntity<CommonResult<OAuth2CheckTokenRespDTO>> exchange = restTemplate.exchange( | |
| 89 | + BASE_URL + "/check-token", | |
| 90 | + HttpMethod.POST, | |
| 91 | + new HttpEntity<>(body, headers), | |
| 92 | + new ParameterizedTypeReference<CommonResult<OAuth2CheckTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 93 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 94 | + return exchange.getBody(); | |
| 95 | + } | |
| 96 | + | |
| 97 | + /** | |
| 98 | + * 使用刷新令牌,获得(刷新)访问令牌 | |
| 99 | + * | |
| 100 | + * @param refreshToken 刷新令牌 | |
| 101 | + * @return 访问令牌 | |
| 102 | + */ | |
| 103 | + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(String refreshToken) { | |
| 104 | + // 1.1 构建请求头 | |
| 105 | + HttpHeaders headers = new HttpHeaders(); | |
| 106 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |
| 107 | + headers.set("tenant-id", TENANT_ID.toString()); | |
| 108 | + addClientHeader(headers); | |
| 109 | + // 1.2 构建请求参数 | |
| 110 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | |
| 111 | + body.add("grant_type", "refresh_token"); | |
| 112 | + body.add("refresh_token", refreshToken); | |
| 113 | + | |
| 114 | + // 2. 执行请求 | |
| 115 | + ResponseEntity<CommonResult<OAuth2AccessTokenRespDTO>> exchange = restTemplate.exchange( | |
| 116 | + BASE_URL + "/token", | |
| 117 | + HttpMethod.POST, | |
| 118 | + new HttpEntity<>(body, headers), | |
| 119 | + new ParameterizedTypeReference<CommonResult<OAuth2AccessTokenRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 120 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 121 | + return exchange.getBody(); | |
| 122 | + } | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * 删除访问令牌 | |
| 126 | + * | |
| 127 | + * @param token 访问令牌 | |
| 128 | + * @return 成功 | |
| 129 | + */ | |
| 130 | + public CommonResult<Boolean> revokeToken(String token) { | |
| 131 | + // 1.1 构建请求头 | |
| 132 | + HttpHeaders headers = new HttpHeaders(); | |
| 133 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |
| 134 | + headers.set("tenant-id", TENANT_ID.toString()); | |
| 135 | + addClientHeader(headers); | |
| 136 | + // 1.2 构建请求参数 | |
| 137 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | |
| 138 | + body.add("token", token); | |
| 139 | + | |
| 140 | + // 2. 执行请求 | |
| 141 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | |
| 142 | + BASE_URL + "/token", | |
| 143 | + HttpMethod.DELETE, | |
| 144 | + new HttpEntity<>(body, headers), | |
| 145 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 146 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 147 | + return exchange.getBody(); | |
| 148 | + } | |
| 149 | + | |
| 150 | + private static void addClientHeader(HttpHeaders headers) { | |
| 151 | + // client 拼接,需要 BASE64 编码 | |
| 152 | + String client = CLIENT_ID + ":" + CLIENT_SECRET; | |
| 153 | + client = Base64Utils.encodeToString(client.getBytes(StandardCharsets.UTF_8)); | |
| 154 | + headers.add("Authorization", "Basic " + client); | |
| 155 | + } | |
| 156 | + | |
| 157 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/UserClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | |
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | |
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | |
| 8 | +import org.springframework.core.ParameterizedTypeReference; | |
| 9 | +import org.springframework.http.*; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | +import org.springframework.util.Assert; | |
| 12 | +import org.springframework.util.LinkedMultiValueMap; | |
| 13 | +import org.springframework.util.MultiValueMap; | |
| 14 | +import org.springframework.web.client.RestTemplate; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 用户 User 信息的客户端 | |
| 18 | + * | |
| 19 | + * 对应调用 OAuth2UserController 接口 | |
| 20 | + */ | |
| 21 | +@Component | |
| 22 | +public class UserClient { | |
| 23 | + | |
| 24 | + private static final String BASE_URL = "http://127.0.0.1:48080/admin-api//system/oauth2/user"; | |
| 25 | + | |
| 26 | + // @Resource // 可优化,注册一个 RestTemplate Bean,然后注入 | |
| 27 | + private final RestTemplate restTemplate = new RestTemplate(); | |
| 28 | + | |
| 29 | + public CommonResult<UserInfoRespDTO> getUser() { | |
| 30 | + // 1.1 构建请求头 | |
| 31 | + HttpHeaders headers = new HttpHeaders(); | |
| 32 | + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); | |
| 33 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | |
| 34 | + addTokenHeader(headers); | |
| 35 | + // 1.2 构建请求参数 | |
| 36 | + MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); | |
| 37 | + | |
| 38 | + // 2. 执行请求 | |
| 39 | + ResponseEntity<CommonResult<UserInfoRespDTO>> exchange = restTemplate.exchange( | |
| 40 | + BASE_URL + "/get", | |
| 41 | + HttpMethod.GET, | |
| 42 | + new HttpEntity<>(body, headers), | |
| 43 | + new ParameterizedTypeReference<CommonResult<UserInfoRespDTO>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 44 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 45 | + return exchange.getBody(); | |
| 46 | + } | |
| 47 | + | |
| 48 | + public CommonResult<Boolean> updateUser(UserUpdateReqDTO updateReqDTO) { | |
| 49 | + // 1.1 构建请求头 | |
| 50 | + HttpHeaders headers = new HttpHeaders(); | |
| 51 | + headers.setContentType(MediaType.APPLICATION_JSON); | |
| 52 | + headers.set("tenant-id", OAuth2Client.TENANT_ID.toString()); | |
| 53 | + addTokenHeader(headers); | |
| 54 | + // 1.2 构建请求参数 | |
| 55 | + // 使用 updateReqDTO 即可 | |
| 56 | + | |
| 57 | + // 2. 执行请求 | |
| 58 | + ResponseEntity<CommonResult<Boolean>> exchange = restTemplate.exchange( | |
| 59 | + BASE_URL + "/update", | |
| 60 | + HttpMethod.PUT, | |
| 61 | + new HttpEntity<>(updateReqDTO, headers), | |
| 62 | + new ParameterizedTypeReference<CommonResult<Boolean>>() {}); // 解决 CommonResult 的泛型丢失 | |
| 63 | + Assert.isTrue(exchange.getStatusCode().is2xxSuccessful(), "响应必须是 200 成功"); | |
| 64 | + return exchange.getBody(); | |
| 65 | + } | |
| 66 | + | |
| 67 | + | |
| 68 | + private static void addTokenHeader(HttpHeaders headers) { | |
| 69 | + LoginUser loginUser = SecurityUtils.getLoginUser(); | |
| 70 | + Assert.notNull(loginUser, "登录用户不能为空"); | |
| 71 | + headers.add("Authorization", "Bearer " + loginUser.getAccessToken()); | |
| 72 | + } | |
| 73 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/CommonResult.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.io.Serializable; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 通用返回 | |
| 9 | + * | |
| 10 | + * @param <T> 数据泛型 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +public class CommonResult<T> implements Serializable { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 错误码 | |
| 17 | + */ | |
| 18 | + private Integer code; | |
| 19 | + /** | |
| 20 | + * 返回数据 | |
| 21 | + */ | |
| 22 | + private T data; | |
| 23 | + /** | |
| 24 | + * 错误提示,用户可阅读 | |
| 25 | + */ | |
| 26 | + private String msg; | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2AccessTokenRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.NoArgsConstructor; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 访问令牌 Response DTO | |
| 10 | + */ | |
| 11 | +@Data | |
| 12 | +@NoArgsConstructor | |
| 13 | +@AllArgsConstructor | |
| 14 | +public class OAuth2AccessTokenRespDTO { | |
| 15 | + | |
| 16 | + /** | |
| 17 | + * 访问令牌 | |
| 18 | + */ | |
| 19 | + @JsonProperty("access_token") | |
| 20 | + private String accessToken; | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 刷新令牌 | |
| 24 | + */ | |
| 25 | + @JsonProperty("refresh_token") | |
| 26 | + private String refreshToken; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 令牌类型 | |
| 30 | + */ | |
| 31 | + @JsonProperty("token_type") | |
| 32 | + private String tokenType; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 过期时间;单位:秒 | |
| 36 | + */ | |
| 37 | + @JsonProperty("expires_in") | |
| 38 | + private Long expiresIn; | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 授权范围;如果多个授权范围,使用空格分隔 | |
| 42 | + */ | |
| 43 | + private String scope; | |
| 44 | + | |
| 45 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/oauth2/OAuth2CheckTokenRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.oauth2; | |
| 2 | + | |
| 3 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
| 4 | +import lombok.AllArgsConstructor; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.NoArgsConstructor; | |
| 7 | + | |
| 8 | +import java.util.List; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 校验令牌 Response DTO | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Data | |
| 16 | +@NoArgsConstructor | |
| 17 | +@AllArgsConstructor | |
| 18 | +public class OAuth2CheckTokenRespDTO { | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 用户编号 | |
| 22 | + */ | |
| 23 | + @JsonProperty("user_id") | |
| 24 | + private Long userId; | |
| 25 | + /** | |
| 26 | + * 用户类型 | |
| 27 | + */ | |
| 28 | + @JsonProperty("user_type") | |
| 29 | + private Integer userType; | |
| 30 | + /** | |
| 31 | + * 租户编号 | |
| 32 | + */ | |
| 33 | + @JsonProperty("tenant_id") | |
| 34 | + private Long tenantId; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 客户端编号 | |
| 38 | + */ | |
| 39 | + @JsonProperty("client_id") | |
| 40 | + private String clientId; | |
| 41 | + /** | |
| 42 | + * 授权范围 | |
| 43 | + */ | |
| 44 | + private List<String> scopes; | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 访问令牌 | |
| 48 | + */ | |
| 49 | + @JsonProperty("access_token") | |
| 50 | + private String accessToken; | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 过期时间 | |
| 54 | + * | |
| 55 | + * 时间戳 / 1000,即单位:秒 | |
| 56 | + */ | |
| 57 | + private Long exp; | |
| 58 | + | |
| 59 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserInfoRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.user; | |
| 2 | + | |
| 3 | +import lombok.AllArgsConstructor; | |
| 4 | +import lombok.Data; | |
| 5 | +import lombok.NoArgsConstructor; | |
| 6 | + | |
| 7 | +import java.util.List; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 获得用户基本信息 Response dto | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +@NoArgsConstructor | |
| 14 | +@AllArgsConstructor | |
| 15 | +public class UserInfoRespDTO { | |
| 16 | + | |
| 17 | + /** | |
| 18 | + * 用户编号 | |
| 19 | + */ | |
| 20 | + private Long id; | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 用户账号 | |
| 24 | + */ | |
| 25 | + private String username; | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 用户昵称 | |
| 29 | + */ | |
| 30 | + private String nickname; | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 用户邮箱 | |
| 34 | + */ | |
| 35 | + private String email; | |
| 36 | + /** | |
| 37 | + * 手机号码 | |
| 38 | + */ | |
| 39 | + private String mobile; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 用户性别 | |
| 43 | + */ | |
| 44 | + private Integer sex; | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 用户头像 | |
| 48 | + */ | |
| 49 | + private String avatar; | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * 所在部门 | |
| 53 | + */ | |
| 54 | + private Dept dept; | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 所属岗位数组 | |
| 58 | + */ | |
| 59 | + private List<Post> posts; | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * 部门 | |
| 63 | + */ | |
| 64 | + @Data | |
| 65 | + public static class Dept { | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 部门编号 | |
| 69 | + */ | |
| 70 | + private Long id; | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * 部门名称 | |
| 74 | + */ | |
| 75 | + private String name; | |
| 76 | + | |
| 77 | + } | |
| 78 | + | |
| 79 | + /** | |
| 80 | + * 岗位 | |
| 81 | + */ | |
| 82 | + @Data | |
| 83 | + public static class Post { | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * 岗位编号 | |
| 87 | + */ | |
| 88 | + private Long id; | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 岗位名称 | |
| 92 | + */ | |
| 93 | + private String name; | |
| 94 | + | |
| 95 | + } | |
| 96 | + | |
| 97 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/client/dto/user/UserUpdateReqDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.client.dto.user; | |
| 2 | + | |
| 3 | +import lombok.AllArgsConstructor; | |
| 4 | +import lombok.Data; | |
| 5 | +import lombok.NoArgsConstructor; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 更新用户基本信息 Request DTO | |
| 9 | + */ | |
| 10 | +@Data | |
| 11 | +@NoArgsConstructor | |
| 12 | +@AllArgsConstructor | |
| 13 | +public class UserUpdateReqDTO { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 用户昵称 | |
| 17 | + */ | |
| 18 | + private String nickname; | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 用户邮箱 | |
| 22 | + */ | |
| 23 | + private String email; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 手机号码 | |
| 27 | + */ | |
| 28 | + private String mobile; | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * 用户性别 | |
| 32 | + */ | |
| 33 | + private Integer sex; | |
| 34 | + | |
| 35 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/AuthController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.controller; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 6 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2AccessTokenRespDTO; | |
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | |
| 8 | +import org.springframework.web.bind.annotation.PostMapping; | |
| 9 | +import org.springframework.web.bind.annotation.RequestMapping; | |
| 10 | +import org.springframework.web.bind.annotation.RequestParam; | |
| 11 | +import org.springframework.web.bind.annotation.RestController; | |
| 12 | + | |
| 13 | +import javax.annotation.Resource; | |
| 14 | +import javax.servlet.http.HttpServletRequest; | |
| 15 | + | |
| 16 | +@RestController | |
| 17 | +@RequestMapping("/auth") | |
| 18 | +public class AuthController { | |
| 19 | + | |
| 20 | + @Resource | |
| 21 | + private OAuth2Client oauth2Client; | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 使用 code 访问令牌,获得访问令牌 | |
| 25 | + * | |
| 26 | + * @param code 授权码 | |
| 27 | + * @param redirectUri 重定向 URI | |
| 28 | + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | |
| 29 | + */ | |
| 30 | + @PostMapping("/login-by-code") | |
| 31 | + public CommonResult<OAuth2AccessTokenRespDTO> loginByCode(@RequestParam("code") String code, | |
| 32 | + @RequestParam("redirectUri") String redirectUri) { | |
| 33 | + return oauth2Client.postAccessToken(code, redirectUri); | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 使用刷新令牌,获得(刷新)访问令牌 | |
| 38 | + * | |
| 39 | + * @param refreshToken 刷新令牌 | |
| 40 | + * @return 访问令牌;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | |
| 41 | + */ | |
| 42 | + @PostMapping("/refresh-token") | |
| 43 | + public CommonResult<OAuth2AccessTokenRespDTO> refreshToken(@RequestParam("refreshToken") String refreshToken) { | |
| 44 | + return oauth2Client.refreshToken(refreshToken); | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 退出登录 | |
| 49 | + * | |
| 50 | + * @param request 请求 | |
| 51 | + * @return 成功 | |
| 52 | + */ | |
| 53 | + @PostMapping("/logout") | |
| 54 | + public CommonResult<Boolean> logout(HttpServletRequest request) { | |
| 55 | + String token = SecurityUtils.obtainAuthorization(request, "Authentication"); | |
| 56 | + if (StrUtil.isNotBlank(token)) { | |
| 57 | + return oauth2Client.revokeToken(token); | |
| 58 | + } | |
| 59 | + // 返回成功 | |
| 60 | + return new CommonResult<>(); | |
| 61 | + } | |
| 62 | + | |
| 63 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/controller/UserController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.controller; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.UserClient; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserInfoRespDTO; | |
| 6 | +import cn.iocoder.yudao.ssodemo.client.dto.user.UserUpdateReqDTO; | |
| 7 | +import org.springframework.web.bind.annotation.*; | |
| 8 | + | |
| 9 | +import javax.annotation.Resource; | |
| 10 | + | |
| 11 | +@RestController | |
| 12 | +@RequestMapping("/user") | |
| 13 | +public class UserController { | |
| 14 | + | |
| 15 | + @Resource | |
| 16 | + private UserClient userClient; | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 获得当前登录用户的基本信息 | |
| 20 | + * | |
| 21 | + * @return 用户信息;注意,实际项目中,最好创建对应的 ResponseVO 类,只返回必要的字段 | |
| 22 | + */ | |
| 23 | + @GetMapping("/get") | |
| 24 | + public CommonResult<UserInfoRespDTO> getUser() { | |
| 25 | + return userClient.getUser(); | |
| 26 | + } | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 更新当前登录用户的昵称 | |
| 30 | + * | |
| 31 | + * @param nickname 昵称 | |
| 32 | + * @return 成功 | |
| 33 | + */ | |
| 34 | + @PutMapping("/update") | |
| 35 | + public CommonResult<Boolean> updateUser(@RequestParam("nickname") String nickname) { | |
| 36 | + UserUpdateReqDTO updateReqDTO = new UserUpdateReqDTO(nickname, null, null, null); | |
| 37 | + return userClient.updateUser(updateReqDTO); | |
| 38 | + } | |
| 39 | + | |
| 40 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/config/SecurityConfiguration.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.config; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.framework.core.filter.TokenAuthenticationFilter; | |
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.handler.AccessDeniedHandlerImpl; | |
| 5 | +import org.springframework.context.annotation.Configuration; | |
| 6 | +import org.springframework.http.HttpMethod; | |
| 7 | +import org.springframework.security.config.annotation.web.builders.HttpSecurity; | |
| 8 | +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; | |
| 9 | +import org.springframework.security.web.AuthenticationEntryPoint; | |
| 10 | +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | |
| 11 | + | |
| 12 | +import javax.annotation.Resource; | |
| 13 | + | |
| 14 | +@Configuration | |
| 15 | +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { | |
| 16 | + | |
| 17 | + @Resource | |
| 18 | + private TokenAuthenticationFilter tokenAuthenticationFilter; | |
| 19 | + | |
| 20 | + @Resource | |
| 21 | + private AccessDeniedHandlerImpl accessDeniedHandler; | |
| 22 | + @Resource | |
| 23 | + private AuthenticationEntryPoint authenticationEntryPoint; | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + protected void configure(HttpSecurity httpSecurity) throws Exception { | |
| 27 | + // 设置 URL 安全权限 | |
| 28 | + httpSecurity.csrf().disable() // 禁用 CSRF 保护 | |
| 29 | + .authorizeRequests() | |
| 30 | + // 1. 静态资源,可匿名访问 | |
| 31 | + .antMatchers(HttpMethod.GET, "/*.html", "/**/*.html", "/**/*.css", "/**/*.js").permitAll() | |
| 32 | + // 2. 登录相关的接口,可匿名访问 | |
| 33 | + .antMatchers("/auth/login-by-code").permitAll() | |
| 34 | + .antMatchers("/auth/refresh-token").permitAll() | |
| 35 | + .antMatchers("/auth/logout").permitAll() | |
| 36 | + // last. 兜底规则,必须认证 | |
| 37 | + .and().authorizeRequests() | |
| 38 | + .anyRequest().authenticated(); | |
| 39 | + | |
| 40 | + // 设置处理器 | |
| 41 | + httpSecurity.exceptionHandling().accessDeniedHandler(accessDeniedHandler) | |
| 42 | + .authenticationEntryPoint(authenticationEntryPoint); | |
| 43 | + | |
| 44 | + // 添加 Token Filter | |
| 45 | + httpSecurity.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); | |
| 46 | + } | |
| 47 | + | |
| 48 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/LoginUser.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.util.List; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 登录用户信息 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +public class LoginUser { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 用户编号 | |
| 17 | + */ | |
| 18 | + private Long id; | |
| 19 | + /** | |
| 20 | + * 用户类型 | |
| 21 | + */ | |
| 22 | + private Integer userType; | |
| 23 | + /** | |
| 24 | + * 租户编号 | |
| 25 | + */ | |
| 26 | + private Long tenantId; | |
| 27 | + /** | |
| 28 | + * 授权范围 | |
| 29 | + */ | |
| 30 | + private List<String> scopes; | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 访问令牌 | |
| 34 | + */ | |
| 35 | + private String accessToken; | |
| 36 | + | |
| 37 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/filter/TokenAuthenticationFilter.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.filter; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.OAuth2Client; | |
| 4 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 5 | +import cn.iocoder.yudao.ssodemo.client.dto.oauth2.OAuth2CheckTokenRespDTO; | |
| 6 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | |
| 7 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | +import org.springframework.util.StringUtils; | |
| 10 | +import org.springframework.web.filter.OncePerRequestFilter; | |
| 11 | + | |
| 12 | +import javax.annotation.Resource; | |
| 13 | +import javax.servlet.FilterChain; | |
| 14 | +import javax.servlet.ServletException; | |
| 15 | +import javax.servlet.http.HttpServletRequest; | |
| 16 | +import javax.servlet.http.HttpServletResponse; | |
| 17 | +import java.io.IOException; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * Token 过滤器,验证 token 的有效性 | |
| 21 | + * 验证通过后,获得 {@link LoginUser} 信息,并加入到 Spring Security 上下文 | |
| 22 | + * | |
| 23 | + * @author 芋道源码 | |
| 24 | + */ | |
| 25 | +@Component | |
| 26 | +public class TokenAuthenticationFilter extends OncePerRequestFilter { | |
| 27 | + | |
| 28 | + @Resource | |
| 29 | + private OAuth2Client oauth2Client; | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, | |
| 33 | + FilterChain filterChain) throws ServletException, IOException { | |
| 34 | + // 1. 获得访问令牌 | |
| 35 | + String token = SecurityUtils.obtainAuthorization(request, "Authentication"); | |
| 36 | + if (StringUtils.hasText(token)) { | |
| 37 | + // 2. 基于 token 构建登录用户 | |
| 38 | + LoginUser loginUser = buildLoginUserByToken(token); | |
| 39 | + // 3. 设置当前用户 | |
| 40 | + if (loginUser != null) { | |
| 41 | + SecurityUtils.setLoginUser(loginUser, request); | |
| 42 | + } | |
| 43 | + } | |
| 44 | + | |
| 45 | + // 继续过滤链 | |
| 46 | + filterChain.doFilter(request, response); | |
| 47 | + } | |
| 48 | + | |
| 49 | + private LoginUser buildLoginUserByToken(String token) { | |
| 50 | + try { | |
| 51 | + CommonResult<OAuth2CheckTokenRespDTO> accessTokenResult = oauth2Client.checkToken(token); | |
| 52 | + OAuth2CheckTokenRespDTO accessToken = accessTokenResult.getData(); | |
| 53 | + if (accessToken == null) { | |
| 54 | + return null; | |
| 55 | + } | |
| 56 | + // 构建登录用户 | |
| 57 | + return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) | |
| 58 | + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes()) | |
| 59 | + .setAccessToken(accessToken.getAccessToken()); | |
| 60 | + } catch (Exception exception) { | |
| 61 | + // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可 | |
| 62 | + return null; | |
| 63 | + } | |
| 64 | + } | |
| 65 | + | |
| 66 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AccessDeniedHandlerImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.handler; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.util.SecurityUtils; | |
| 5 | +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils; | |
| 6 | +import lombok.extern.slf4j.Slf4j; | |
| 7 | +import org.springframework.http.HttpStatus; | |
| 8 | +import org.springframework.security.access.AccessDeniedException; | |
| 9 | +import org.springframework.security.web.access.AccessDeniedHandler; | |
| 10 | +import org.springframework.security.web.access.ExceptionTranslationFilter; | |
| 11 | +import org.springframework.stereotype.Component; | |
| 12 | + | |
| 13 | +import javax.servlet.FilterChain; | |
| 14 | +import javax.servlet.ServletException; | |
| 15 | +import javax.servlet.http.HttpServletRequest; | |
| 16 | +import javax.servlet.http.HttpServletResponse; | |
| 17 | +import java.io.IOException; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * 访问一个需要认证的 URL 资源,已经认证(登录)但是没有权限的情况下,返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。 | |
| 21 | + * | |
| 22 | + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法,调用当前类 | |
| 23 | + * | |
| 24 | + * @author 芋道源码 | |
| 25 | + */ | |
| 26 | +@Component | |
| 27 | +@SuppressWarnings("JavadocReference") | |
| 28 | +@Slf4j | |
| 29 | +public class AccessDeniedHandlerImpl implements AccessDeniedHandler { | |
| 30 | + | |
| 31 | + @Override | |
| 32 | + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) | |
| 33 | + throws IOException, ServletException { | |
| 34 | + // 打印 warn 的原因是,不定期合并 warn,看看有没恶意破坏 | |
| 35 | + log.warn("[commence][访问 URL({}) 时,用户({}) 权限不够]", request.getRequestURI(), | |
| 36 | + SecurityUtils.getLoginUserId(), e); | |
| 37 | + // 返回 403 | |
| 38 | + CommonResult<Object> result = new CommonResult<>(); | |
| 39 | + result.setCode(HttpStatus.FORBIDDEN.value()); | |
| 40 | + result.setMsg("没有该操作权限"); | |
| 41 | + ServletUtils.writeJSON(response, result); | |
| 42 | + } | |
| 43 | + | |
| 44 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/handler/AuthenticationEntryPointImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.handler; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.client.dto.CommonResult; | |
| 4 | +import cn.iocoder.yudao.ssodemo.framework.core.util.ServletUtils; | |
| 5 | +import lombok.extern.slf4j.Slf4j; | |
| 6 | +import org.springframework.http.HttpStatus; | |
| 7 | +import org.springframework.security.core.AuthenticationException; | |
| 8 | +import org.springframework.security.web.AuthenticationEntryPoint; | |
| 9 | +import org.springframework.security.web.access.ExceptionTranslationFilter; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +import javax.servlet.FilterChain; | |
| 13 | +import javax.servlet.http.HttpServletRequest; | |
| 14 | +import javax.servlet.http.HttpServletResponse; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 访问一个需要认证的 URL 资源,但是此时自己尚未认证(登录)的情况下,返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码,从而使前端重定向到登录页 | |
| 18 | + * | |
| 19 | + * 补充:Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法,调用当前类 | |
| 20 | + */ | |
| 21 | +@Component | |
| 22 | +@Slf4j | |
| 23 | +@SuppressWarnings("JavadocReference") // 忽略文档引用报错 | |
| 24 | +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { | |
| 25 | + | |
| 26 | + @Override | |
| 27 | + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) { | |
| 28 | + log.debug("[commence][访问 URL({}) 时,没有登录]", request.getRequestURI(), e); | |
| 29 | + // 返回 401 | |
| 30 | + CommonResult<Object> result = new CommonResult<>(); | |
| 31 | + result.setCode(HttpStatus.UNAUTHORIZED.value()); | |
| 32 | + result.setMsg("账号未登录"); | |
| 33 | + ServletUtils.writeJSON(response, result); | |
| 34 | + } | |
| 35 | + | |
| 36 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/SecurityUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.util; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.ssodemo.framework.core.LoginUser; | |
| 4 | +import org.springframework.lang.Nullable; | |
| 5 | +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |
| 6 | +import org.springframework.security.core.Authentication; | |
| 7 | +import org.springframework.security.core.context.SecurityContext; | |
| 8 | +import org.springframework.security.core.context.SecurityContextHolder; | |
| 9 | +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; | |
| 10 | +import org.springframework.util.StringUtils; | |
| 11 | + | |
| 12 | +import javax.servlet.http.HttpServletRequest; | |
| 13 | +import java.util.Collections; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * 安全服务工具类 | |
| 17 | + * | |
| 18 | + * @author 芋道源码 | |
| 19 | + */ | |
| 20 | +public class SecurityUtils { | |
| 21 | + | |
| 22 | + public static final String AUTHORIZATION_BEARER = "Bearer"; | |
| 23 | + | |
| 24 | + private SecurityUtils() {} | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 从请求中,获得认证 Token | |
| 28 | + * | |
| 29 | + * @param request 请求 | |
| 30 | + * @param header 认证 Token 对应的 Header 名字 | |
| 31 | + * @return 认证 Token | |
| 32 | + */ | |
| 33 | + public static String obtainAuthorization(HttpServletRequest request, String header) { | |
| 34 | + String authorization = request.getHeader(header); | |
| 35 | + if (!StringUtils.hasText(authorization)) { | |
| 36 | + return null; | |
| 37 | + } | |
| 38 | + int index = authorization.indexOf(AUTHORIZATION_BEARER + " "); | |
| 39 | + if (index == -1) { // 未找到 | |
| 40 | + return null; | |
| 41 | + } | |
| 42 | + return authorization.substring(index + 7).trim(); | |
| 43 | + } | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * 获得当前认证信息 | |
| 47 | + * | |
| 48 | + * @return 认证信息 | |
| 49 | + */ | |
| 50 | + public static Authentication getAuthentication() { | |
| 51 | + SecurityContext context = SecurityContextHolder.getContext(); | |
| 52 | + if (context == null) { | |
| 53 | + return null; | |
| 54 | + } | |
| 55 | + return context.getAuthentication(); | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * 获取当前用户 | |
| 60 | + * | |
| 61 | + * @return 当前用户 | |
| 62 | + */ | |
| 63 | + @Nullable | |
| 64 | + public static LoginUser getLoginUser() { | |
| 65 | + Authentication authentication = getAuthentication(); | |
| 66 | + if (authentication == null) { | |
| 67 | + return null; | |
| 68 | + } | |
| 69 | + return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null; | |
| 70 | + } | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * 获得当前用户的编号,从上下文中 | |
| 74 | + * | |
| 75 | + * @return 用户编号 | |
| 76 | + */ | |
| 77 | + @Nullable | |
| 78 | + public static Long getLoginUserId() { | |
| 79 | + LoginUser loginUser = getLoginUser(); | |
| 80 | + return loginUser != null ? loginUser.getId() : null; | |
| 81 | + } | |
| 82 | + | |
| 83 | + /** | |
| 84 | + * 设置当前用户 | |
| 85 | + * | |
| 86 | + * @param loginUser 登录用户 | |
| 87 | + * @param request 请求 | |
| 88 | + */ | |
| 89 | + public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) { | |
| 90 | + // 创建 Authentication,并设置到上下文 | |
| 91 | + Authentication authentication = buildAuthentication(loginUser, request); | |
| 92 | + SecurityContextHolder.getContext().setAuthentication(authentication); | |
| 93 | + } | |
| 94 | + | |
| 95 | + private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { | |
| 96 | + // 创建 UsernamePasswordAuthenticationToken 对象 | |
| 97 | + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( | |
| 98 | + loginUser, null, Collections.emptyList()); | |
| 99 | + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | |
| 100 | + return authenticationToken; | |
| 101 | + } | |
| 102 | + | |
| 103 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/java/cn/iocoder/yudao/ssodemo/framework/core/util/ServletUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.ssodemo.framework.core.util; | |
| 2 | + | |
| 3 | +import cn.hutool.extra.servlet.ServletUtil; | |
| 4 | +import cn.hutool.json.JSONUtil; | |
| 5 | +import org.springframework.http.MediaType; | |
| 6 | + | |
| 7 | +import javax.servlet.http.HttpServletResponse; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 客户端工具类 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +public class ServletUtils { | |
| 15 | + | |
| 16 | + /** | |
| 17 | + * 返回 JSON 字符串 | |
| 18 | + * | |
| 19 | + * @param response 响应 | |
| 20 | + * @param object 对象,会序列化成 JSON 字符串 | |
| 21 | + */ | |
| 22 | + @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码 | |
| 23 | + public static void writeJSON(HttpServletResponse response, Object object) { | |
| 24 | + String content = JSONUtil.toJsonStr(object); | |
| 25 | + ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE); | |
| 26 | + } | |
| 27 | + | |
| 28 | +} | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/resources/application.yaml
0 → 100644
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/callback.html
0 → 100644
| 1 | +<!DOCTYPE html> | |
| 2 | +<html lang="en"> | |
| 3 | +<head> | |
| 4 | + <meta charset="UTF-8"> | |
| 5 | + <title>SSO 授权后的回调页</title> | |
| 6 | + <!-- jQuery:操作 dom、发起请求等 --> | |
| 7 | + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | |
| 8 | + <!-- 工具类 --> | |
| 9 | + <script type="application/javascript"> | |
| 10 | + (function ($) { | |
| 11 | + /** | |
| 12 | + * 获得 URL 的指定参数的值 | |
| 13 | + * | |
| 14 | + * @param name 参数名 | |
| 15 | + * @returns 参数值 | |
| 16 | + */ | |
| 17 | + $.getUrlParam = function (name) { | |
| 18 | + const reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); | |
| 19 | + const r = window.location.search.substr(1).match(reg); | |
| 20 | + if (r != null) return unescape(r[2]); return null; | |
| 21 | + } | |
| 22 | + })(jQuery); | |
| 23 | + </script> | |
| 24 | + | |
| 25 | + <script type="application/javascript"> | |
| 26 | + $(function () { | |
| 27 | + // 获得 code 授权码 | |
| 28 | + const code = $.getUrlParam('code'); | |
| 29 | + if (!code) { | |
| 30 | + alert('获取不到 code 参数,请排查!') | |
| 31 | + return; | |
| 32 | + } | |
| 33 | + | |
| 34 | + // 提交 | |
| 35 | + const redirectUri = 'http://127.0.0.1:18080/callback.html'; // 需要修改成,你回调的地址,就是在 index.html 拼接的 redirectUri | |
| 36 | + $.ajax({ | |
| 37 | + url: "http://127.0.0.1:18080/auth/login-by-code?code=" + code | |
| 38 | + + '&redirectUri=' + redirectUri, | |
| 39 | + method: 'POST', | |
| 40 | + success: function( result ) { | |
| 41 | + if (result.code !== 0) { | |
| 42 | + alert('获得访问令牌失败,原因:' + result.msg) | |
| 43 | + return; | |
| 44 | + } | |
| 45 | + alert('获得访问令牌成功!点击确认,跳转回首页') | |
| 46 | + | |
| 47 | + // 设置到 localStorage 中 | |
| 48 | + localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | |
| 49 | + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | |
| 50 | + | |
| 51 | + // 跳转回首页 | |
| 52 | + window.location.href = '/index.html'; | |
| 53 | + } | |
| 54 | + }) | |
| 55 | + }) | |
| 56 | + </script> | |
| 57 | +</head> | |
| 58 | +<body> | |
| 59 | +正在使用 code 授权码,进行 accessToken 访问令牌的获取 | |
| 60 | +</body> | |
| 61 | +</html> | ... | ... |
yudao-example/yudao-sso-demo-by-code/src/main/resources/static/index.html
0 → 100644
| 1 | +<!DOCTYPE html> | |
| 2 | +<html lang="en"> | |
| 3 | +<head> | |
| 4 | + <meta charset="UTF-8"> | |
| 5 | + <title>首页</title> | |
| 6 | + <!-- jQuery:操作 dom、发起请求等 --> | |
| 7 | + <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/2.1.2/jquery.min.js" type="application/javascript"></script> | |
| 8 | + | |
| 9 | + <script type="application/javascript"> | |
| 10 | + | |
| 11 | + /** | |
| 12 | + * 跳转单点登录 | |
| 13 | + */ | |
| 14 | + function ssoLogin() { | |
| 15 | + const clientId = 'yudao-sso-demo-by-code'; // 可以改写成,你的 clientId | |
| 16 | + const redirectUri = encodeURIComponent('http://127.0.0.1:18080/callback.html'); // 注意,需要使用 encodeURIComponent 编码地址 | |
| 17 | + const responseType = 'code'; // 1)授权码模式,对应 code;2)简化模式,对应 token | |
| 18 | + window.location.href = 'http://127.0.0.1:1024/sso?client_id=' + clientId | |
| 19 | + + '&redirect_uri=' + redirectUri | |
| 20 | + + '&response_type=' + responseType; | |
| 21 | + } | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 修改昵称 | |
| 25 | + */ | |
| 26 | + function updateNickname() { | |
| 27 | + const nickname = prompt("请输入新的昵称", ""); | |
| 28 | + if (!nickname) { | |
| 29 | + return; | |
| 30 | + } | |
| 31 | + // 更新用户的昵称 | |
| 32 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | |
| 33 | + $.ajax({ | |
| 34 | + url: "http://127.0.0.1:18080/user/update?nickname=" + nickname, | |
| 35 | + method: 'PUT', | |
| 36 | + headers: { | |
| 37 | + 'Authentication': 'Bearer ' + accessToken | |
| 38 | + }, | |
| 39 | + success: function (result) { | |
| 40 | + if (result.code !== 0) { | |
| 41 | + alert('更新昵称失败,原因:' + result.msg) | |
| 42 | + return; | |
| 43 | + } | |
| 44 | + alert('更新昵称成功!'); | |
| 45 | + $('#nicknameSpan').html(nickname); | |
| 46 | + } | |
| 47 | + }); | |
| 48 | + } | |
| 49 | + | |
| 50 | + /** | |
| 51 | + * 刷新令牌 | |
| 52 | + */ | |
| 53 | + function refreshToken() { | |
| 54 | + const refreshToken = localStorage.getItem('REFRESH-TOKEN'); | |
| 55 | + if (!refreshToken) { | |
| 56 | + alert("获取不到刷新令牌"); | |
| 57 | + return; | |
| 58 | + } | |
| 59 | + $.ajax({ | |
| 60 | + url: "http://127.0.0.1:18080/auth/refresh-token?refreshToken=" + refreshToken, | |
| 61 | + method: 'POST', | |
| 62 | + success: function (result) { | |
| 63 | + if (result.code !== 0) { | |
| 64 | + alert('刷新访问令牌失败,原因:' + result.msg) | |
| 65 | + return; | |
| 66 | + } | |
| 67 | + alert('更新访问令牌成功!'); | |
| 68 | + $('#accessTokenSpan').html(result.data.access_token); | |
| 69 | + | |
| 70 | + // 设置到 localStorage 中 | |
| 71 | + localStorage.setItem('ACCESS-TOKEN', result.data.access_token); | |
| 72 | + localStorage.setItem('REFRESH-TOKEN', result.data.refresh_token); | |
| 73 | + } | |
| 74 | + }); | |
| 75 | + } | |
| 76 | + | |
| 77 | + /** | |
| 78 | + * 刷新令牌 | |
| 79 | + */ | |
| 80 | + function logout() { | |
| 81 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | |
| 82 | + if (!accessToken) { | |
| 83 | + location.reload(); | |
| 84 | + return; | |
| 85 | + } | |
| 86 | + $.ajax({ | |
| 87 | + url: "http://127.0.0.1:18080/auth/logout", | |
| 88 | + method: 'POST', | |
| 89 | + headers: { | |
| 90 | + 'Authentication': 'Bearer ' + accessToken | |
| 91 | + }, | |
| 92 | + success: function (result) { | |
| 93 | + if (result.code !== 0) { | |
| 94 | + alert('退出登录失败,原因:' + result.msg) | |
| 95 | + return; | |
| 96 | + } | |
| 97 | + alert('退出登录成功!'); | |
| 98 | + // 删除 localStorage 中 | |
| 99 | + localStorage.removeItem('ACCESS-TOKEN'); | |
| 100 | + localStorage.removeItem('REFRESH-TOKEN'); | |
| 101 | + | |
| 102 | + location.reload(); | |
| 103 | + } | |
| 104 | + }); | |
| 105 | + } | |
| 106 | + | |
| 107 | + $(function () { | |
| 108 | + const accessToken = localStorage.getItem('ACCESS-TOKEN'); | |
| 109 | + // 情况一:未登录 | |
| 110 | + if (!accessToken) { | |
| 111 | + $('#noLoginDiv').css("display", "block"); | |
| 112 | + return; | |
| 113 | + } | |
| 114 | + | |
| 115 | + // 情况二:已登录 | |
| 116 | + $('#yesLoginDiv').css("display", "block"); | |
| 117 | + $('#accessTokenSpan').html(accessToken); | |
| 118 | + // 获得登录用户的信息 | |
| 119 | + $.ajax({ | |
| 120 | + url: "http://127.0.0.1:18080/user/get", | |
| 121 | + method: 'GET', | |
| 122 | + headers: { | |
| 123 | + 'Authentication': 'Bearer ' + accessToken | |
| 124 | + }, | |
| 125 | + success: function (result) { | |
| 126 | + if (result.code !== 0) { | |
| 127 | + alert('获得个人信息失败,原因:' + result.msg) | |
| 128 | + return; | |
| 129 | + } | |
| 130 | + $('#nicknameSpan').html(result.data.nickname); | |
| 131 | + } | |
| 132 | + }); | |
| 133 | + }) | |
| 134 | + </script> | |
| 135 | +</head> | |
| 136 | +<body> | |
| 137 | + <!-- 情况一:未登录:1)跳转 ruoyi-vue-pro 的 SSO 登录页 --> | |
| 138 | + <div id="noLoginDiv" style="display: none"> | |
| 139 | + 您未登录,点击 <a href="#" onclick="ssoLogin()">跳转 </a> SSO 单点登录 | |
| 140 | + </div> | |
| 141 | + | |
| 142 | + <!-- 情况二:已登录:1)展示用户信息;2)刷新访问令牌;3)退出登录 --> | |
| 143 | + <div id="yesLoginDiv" style="display: none"> | |
| 144 | + 您已登录!<button onclick="logout()">退出登录</button> <br /> | |
| 145 | + 昵称:<span id="nicknameSpan"> 加载中... </span> <button onclick="updateNickname()">修改昵称</button> <br /> | |
| 146 | + 访问令牌:<span id="accessTokenSpan"> 加载中... </span> <button onclick="refreshToken()">刷新令牌</button> <br /> | |
| 147 | + </div> | |
| 148 | +</body> | |
| 149 | +<style> | |
| 150 | + body { /** 页面居中 */ | |
| 151 | + border-radius: 20px; | |
| 152 | + height: 350px; | |
| 153 | + position: absolute; | |
| 154 | + left: 50%; | |
| 155 | + top: 50%; | |
| 156 | + transform: translate(-50%,-50%); | |
| 157 | + } | |
| 158 | +</style> | |
| 159 | +</html> | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java
| ... | ... | @@ -26,6 +26,7 @@ public class OAuth2OpenCheckTokenRespVO { |
| 26 | 26 | private Long tenantId; |
| 27 | 27 | |
| 28 | 28 | @ApiModelProperty(value = "客户端编号", required = true, example = "car") |
| 29 | + @JsonProperty("client_id") | |
| 29 | 30 | private String clientId; |
| 30 | 31 | @ApiModelProperty(value = "授权范围", required = true, example = "user_info") |
| 31 | 32 | private List<String> scopes; | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java
| ... | ... | @@ -17,7 +17,7 @@ public class OAuth2UserInfoRespVO { |
| 17 | 17 | @ApiModelProperty(value = "用户编号", required = true, example = "1") |
| 18 | 18 | private Long id; |
| 19 | 19 | |
| 20 | - @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿") | |
| 20 | + @ApiModelProperty(value = "用户账号", required = true, example = "芋艿") | |
| 21 | 21 | private String username; |
| 22 | 22 | |
| 23 | 23 | @ApiModelProperty(value = "用户昵称", required = true, example = "芋道") | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/tenant/TenantPackageDO.java
yudao-ui-admin/src/permission.js
| ... | ... | @@ -48,7 +48,8 @@ router.beforeEach((to, from, next) => { |
| 48 | 48 | // 在免登录白名单,直接进入 |
| 49 | 49 | next() |
| 50 | 50 | } else { |
| 51 | - next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 | |
| 51 | + const redirect = encodeURIComponent(to.fullPath) // 编码 URI,保证参数跳转回去后,可以继续带上 | |
| 52 | + next(`/login?redirect=${redirect}`) // 否则全部重定向到登录页 | |
| 52 | 53 | NProgress.done() |
| 53 | 54 | } |
| 54 | 55 | } | ... | ... |
yudao-ui-admin/src/views/login.vue
| ... | ... | @@ -190,7 +190,7 @@ export default { |
| 190 | 190 | // 验证码开关 |
| 191 | 191 | this.captchaEnable = getCaptchaEnable(); |
| 192 | 192 | // 重定向地址 |
| 193 | - this.redirect = this.$route.query.redirect; | |
| 193 | + this.redirect = this.$route.query.redirect ? decodeURIComponent(this.$route.query.redirect) : undefined; | |
| 194 | 194 | this.getCookie(); |
| 195 | 195 | }, |
| 196 | 196 | methods: { | ... | ... |
yudao-ui-admin/src/views/socialLogin.vue
| ... | ... | @@ -117,7 +117,7 @@ export default { |
| 117 | 117 | // 验证码开关 |
| 118 | 118 | this.captchaEnable = getCaptchaEnable(); |
| 119 | 119 | // 重定向地址 |
| 120 | - this.redirect = this.$route.query.redirect; | |
| 120 | + this.redirect = this.$route.query.redirect ? decodeURIComponent(this.$route.query.redirect) : undefined; | |
| 121 | 121 | // 社交登录相关 |
| 122 | 122 | this.type = this.$route.query.type; |
| 123 | 123 | this.code = this.$route.query.code; | ... | ... |
yudao-ui-admin/src/views/sso.vue
| ... | ... | @@ -19,12 +19,7 @@ |
| 19 | 19 | </el-tab-pane> |
| 20 | 20 | </el-tabs> |
| 21 | 21 | <div> |
| 22 | - <el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form"> | |
| 23 | - <el-form-item prop="tenantName" v-if="tenantEnable"> | |
| 24 | - <el-input v-model="loginForm.tenantName" type="text" auto-complete="off" placeholder='租户'> | |
| 25 | - <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/> | |
| 26 | - </el-input> | |
| 27 | - </el-form-item> | |
| 22 | + <el-form ref="loginForm" :model="loginForm" class="login-form"> | |
| 28 | 23 | <!-- 授权范围的选择 --> |
| 29 | 24 | 此第三方应用请求获得以下权限: |
| 30 | 25 | <el-form-item prop="scopes"> |
| ... | ... | @@ -56,10 +51,7 @@ |
| 56 | 51 | </template> |
| 57 | 52 | |
| 58 | 53 | <script> |
| 59 | -import {getTenantIdByName} from "@/api/system/tenant"; | |
| 60 | -import {getTenantEnable} from "@/utils/ruoyi"; | |
| 61 | 54 | import {authorize, getAuthorize} from "@/api/login"; |
| 62 | -import {getTenantName, setTenantId} from "@/utils/auth"; | |
| 63 | 55 | |
| 64 | 56 | export default { |
| 65 | 57 | name: "Login", |
| ... | ... | @@ -67,7 +59,6 @@ export default { |
| 67 | 59 | return { |
| 68 | 60 | tenantEnable: true, |
| 69 | 61 | loginForm: { |
| 70 | - tenantName: "芋道源码", | |
| 71 | 62 | scopes: [], // 已选中的 scope 数组 |
| 72 | 63 | }, |
| 73 | 64 | params: { // URL 上的 client_id、scope 等参数 |
| ... | ... | @@ -81,35 +72,10 @@ export default { |
| 81 | 72 | name: '', |
| 82 | 73 | logo: '', |
| 83 | 74 | }, |
| 84 | - LoginRules: { | |
| 85 | - tenantName: [ | |
| 86 | - {required: true, trigger: "blur", message: "租户不能为空"}, | |
| 87 | - { | |
| 88 | - validator: (rule, value, callback) => { | |
| 89 | - // debugger | |
| 90 | - getTenantIdByName(value).then(res => { | |
| 91 | - const tenantId = res.data; | |
| 92 | - if (tenantId && tenantId >= 0) { | |
| 93 | - // 设置租户 | |
| 94 | - setTenantId(tenantId) | |
| 95 | - callback(); | |
| 96 | - } else { | |
| 97 | - callback('租户不存在'); | |
| 98 | - } | |
| 99 | - }); | |
| 100 | - }, | |
| 101 | - trigger: 'blur' | |
| 102 | - } | |
| 103 | - ] | |
| 104 | - }, | |
| 105 | 75 | loading: false |
| 106 | 76 | }; |
| 107 | 77 | }, |
| 108 | 78 | created() { |
| 109 | - // 租户开关 | |
| 110 | - this.tenantEnable = getTenantEnable(); | |
| 111 | - this.getCookie(); | |
| 112 | - | |
| 113 | 79 | // 解析参数 |
| 114 | 80 | // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write |
| 115 | 81 | // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read |
| ... | ... | @@ -162,13 +128,6 @@ export default { |
| 162 | 128 | }) |
| 163 | 129 | }, |
| 164 | 130 | methods: { |
| 165 | - getCookie() { | |
| 166 | - const tenantName = getTenantName(); | |
| 167 | - this.loginForm = { | |
| 168 | - ...this.loginForm, | |
| 169 | - tenantName: tenantName ? tenantName : this.loginForm.tenantName, | |
| 170 | - }; | |
| 171 | - }, | |
| 172 | 131 | handleAuthorize(approved) { |
| 173 | 132 | this.$refs.loginForm.validate(valid => { |
| 174 | 133 | if (!valid) { | ... | ... |