Commit d18463866ef0ede008eb8de1208a3feedea5396d

Authored by YunaiV
1 parent ed847ac2

新增 sso 页面

Showing 13 changed files with 215 additions and 95 deletions
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http
  1 +### 请求 /system/oauth2/authorize 接口 => 成功
  2 +GET {{baseUrl}}/system/oauth2/authorize?clientId=default
  3 +Authorization: Bearer {{token}}
  4 +tenant-id: {{adminTenentId}}
  5 +
1 6 ### 请求 /system/oauth2/authorize + token 接口 => 成功
2 7 POST {{baseUrl}}/system/oauth2/authorize
3 8 Content-Type: application/x-www-form-urlencoded
4 9 Authorization: Bearer {{token}}
5 10 tenant-id: {{adminTenentId}}
6 11  
7   -response_type=token&client_id=default&scope={"user_info": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true
  12 +response_type=token&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true
8 13  
9 14 ### 请求 /system/oauth2/authorize + code 接口 => 成功
10 15 POST {{baseUrl}}/system/oauth2/authorize
... ... @@ -12,7 +17,7 @@ Content-Type: application/x-www-form-urlencoded
12 17 Authorization: Bearer {{token}}
13 18 tenant-id: {{adminTenentId}}
14 19  
15   -response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true
  20 +response_type=code&client_id=default&scope={"user.read": true}&redirect_uri=https://www.iocoder.cn&auto_approve=false
16 21  
17 22 ### 请求 /system/oauth2/token + code 接口 => 成功
18 23 POST {{baseUrl}}/system/oauth2/token
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java
... ... @@ -11,14 +11,16 @@ import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
11 11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
12 12 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
13 13 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
  14 +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;
14 15 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
15 16 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO;
16   -import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
  17 +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserUpdateReqVO;
17 18 import cn.iocoder.yudao.module.system.convert.oauth2.OAuth2OpenConvert;
18   -import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
19   -import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
20 19 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
21 20 import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
  21 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
  22 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;
  23 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
22 24 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
23 25 import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
24 26 import cn.iocoder.yudao.module.system.service.dept.DeptService;
... ... @@ -91,7 +93,7 @@ public class OAuth2OpenController {
91 93 * 注意,默认需要传递 client_id + client_secret 参数
92 94 */
93 95 @PostMapping("/token")
94   - @ApiOperation(value = "获得访问令牌", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【获取】调用")
  96 + @ApiOperation(value = "获得访问令牌", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用")
95 97 @ApiImplicitParams({
96 98 @ApiImplicitParam(name = "grant_type", required = true, value = "授权类型", example = "code", dataTypeClass = String.class),
97 99 @ApiImplicitParam(name = "code", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class),
... ... @@ -173,7 +175,7 @@ public class OAuth2OpenController {
173 175 @RequestParam("token") String token) {
174 176 // 校验客户端
175 177 String[] clientIdAndSecret = obtainBasicAuthorization(request);
176   - OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],
  178 + oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],
177 179 null, null, null);
178 180  
179 181 // 校验令牌
... ... @@ -182,46 +184,36 @@ public class OAuth2OpenController {
182 184 return success(OAuth2OpenConvert.INSTANCE.convert2(accessTokenDO));
183 185 }
184 186  
185   - // GET oauth/authorize AuthorizationEndpoint TODO
  187 + /**
  188 + * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 authorize 方法
  189 + */
186 190 @GetMapping("/authorize")
187   - @ApiOperation(value = "获得授权信息", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【获取】调用")
188   - @ApiImplicitParams({
189   - @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class),
190   - @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class),
191   - @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 多个使用空格分隔
192   - @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class),
193   - @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class)
194   - })
195   - public CommonResult<Object> authorize(@RequestParam("response_type") String responseType,
196   - @RequestParam("client_id") String clientId,
197   - @RequestParam(value = "scope", required = false) String scope,
198   - @RequestParam("redirect_uri") String redirectUri,
199   - @RequestParam(value = "state", required = false) String state) {
200   - List<String> scopes = StrUtil.split(scope, ' ');
  191 + @ApiOperation(value = "获得授权信息", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【获取】调用")
  192 + @ApiImplicitParam(name = "clientId", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class)
  193 + public CommonResult<OAuth2OpenAuthorizeInfoRespVO> authorize(@RequestParam("clientId") String clientId) {
201 194 // 0. 校验用户已经登录。通过 Spring Security 实现
202 195  
203   - // 1.1 校验 responseType 是否满足 code 或者 token 值
204   - OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);
205   - // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内
206   - oauth2ClientService.validOAuthClientFromCache(clientId, null,
207   - grantTypeEnum.getGrantType(), scopes, redirectUri);
208   -
209   - // 3. 不满足自动授权,则返回授权相关的展示信息
210   - return null;
  196 + // 1. 获得 Client 客户端的信息
  197 + OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null,
  198 + null, null, null);
  199 + // 2. 获得用户已经授权的信息
  200 + List<OAuth2ApproveDO> approves = oauth2ApproveService.getApproveList(getLoginUserId(), getUserType(), clientId);
  201 + // 拼接返回
  202 + return success(OAuth2OpenConvert.INSTANCE.convert(client, approves));
211 203 }
212 204  
213 205 /**
214 206 * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 approveOrDeny 方法
215 207 *
216 208 * 场景一:【自动授权 autoApprove = true】
217   - * 刚进入 authorize.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 scope 的自动授权
  209 + * 刚进入 sso.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 scope 的自动授权
218 210 * 场景二:【手动授权 autoApprove = false】
219   - * 在 authorize.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved 为 true 或者 false
  211 + * 在 sso.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved 为 true 或者 false
220 212 *
221 213 * 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理
222 214 */
223 215 @PostMapping("/authorize")
224   - @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【提交】调用")
  216 + @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 sso.vue 单点登录界面被【提交】调用")
225 217 @ApiImplicitParams({
226 218 @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class),
227 219 @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class),
... ... @@ -346,7 +338,7 @@ public class OAuth2OpenController {
346 338 @PutMapping("/user/update")
347 339 @ApiOperation("更新用户基本信息")
348 340 @PreAuthorize("@ss.hasScope('user.write')")
349   - public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody UserProfileUpdateReqVO reqVO) {
  341 + public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody OAuth2OpenUserUpdateReqVO reqVO) {
350 342 // 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象,实现接口的复用。
351 343 // 主要是,AdminUserService 没有自己的 BO 对象,所以复用只能这么做
352 344 userService.updateUserProfile(getLoginUserId(), OAuth2OpenConvert.INSTANCE.convert(reqVO));
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientBaseVO.java
... ... @@ -18,39 +18,39 @@ import java.util.List;
18 18 @Data
19 19 public class OAuth2ClientBaseVO {
20 20  
21   - @ApiModelProperty(value = "客户端编号", required = true)
  21 + @ApiModelProperty(value = "客户端编号", required = true, example = "tudou")
22 22 @NotNull(message = "客户端编号不能为空")
23 23 private String clientId;
24 24  
25   - @ApiModelProperty(value = "客户端密钥", required = true)
  25 + @ApiModelProperty(value = "客户端密钥", required = true, example = "fan")
26 26 @NotNull(message = "客户端密钥不能为空")
27 27 private String secret;
28 28  
29   - @ApiModelProperty(value = "应用名", required = true)
  29 + @ApiModelProperty(value = "应用名", required = true, example = "土豆")
30 30 @NotNull(message = "应用名不能为空")
31 31 private String name;
32 32  
33   - @ApiModelProperty(value = "应用图标", required = true)
  33 + @ApiModelProperty(value = "应用图标", required = true, example = "https://www.iocoder.cn/xx.png")
34 34 @NotNull(message = "应用图标不能为空")
35 35 @URL(message = "应用图标的地址不正确")
36 36 private String logo;
37 37  
38   - @ApiModelProperty(value = "应用描述")
  38 + @ApiModelProperty(value = "应用描述", example = "我是一个应用")
39 39 private String description;
40 40  
41   - @ApiModelProperty(value = "状态", required = true)
  41 + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
42 42 @NotNull(message = "状态不能为空")
43 43 private Integer status;
44 44  
45   - @ApiModelProperty(value = "访问令牌的有效期", required = true)
  45 + @ApiModelProperty(value = "访问令牌的有效期", required = true, example = "8640")
46 46 @NotNull(message = "访问令牌的有效期不能为空")
47 47 private Integer accessTokenValiditySeconds;
48 48  
49   - @ApiModelProperty(value = "刷新令牌的有效期", required = true)
  49 + @ApiModelProperty(value = "刷新令牌的有效期", required = true, example = "8640000")
50 50 @NotNull(message = "刷新令牌的有效期不能为空")
51 51 private Integer refreshTokenValiditySeconds;
52 52  
53   - @ApiModelProperty(value = "可重定向的 URI 地址", required = true)
  53 + @ApiModelProperty(value = "可重定向的 URI 地址", required = true, example = "https://www.iocoder.cn")
54 54 @NotNull(message = "可重定向的 URI 地址不能为空")
55 55 private List<@NotEmpty(message = "重定向的 URI 不能为空")
56 56 @URL(message = "重定向的 URI 格式不正确") String> redirectUris;
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientPageReqVO.java
... ... @@ -10,10 +10,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
10 10 @ToString(callSuper = true)
11 11 public class OAuth2ClientPageReqVO extends PageParam {
12 12  
13   - @ApiModelProperty(value = "应用名")
  13 + @ApiModelProperty(value = "应用名", example = "土豆", notes = "模糊匹配")
14 14 private String name;
15 15  
16   - @ApiModelProperty(value = "状态")
  16 + @ApiModelProperty(value = "状态", example = "1", notes = "参见 CommonStatusEnum 枚举")
17 17 private Integer status;
18 18  
19 19 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientRespVO.java
... ... @@ -14,7 +14,7 @@ import java.util.Date;
14 14 @ToString(callSuper = true)
15 15 public class OAuth2ClientRespVO extends OAuth2ClientBaseVO {
16 16  
17   - @ApiModelProperty(value = "编号", required = true)
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
18 18 private Long id;
19 19  
20 20 @ApiModelProperty(value = "创建时间", required = true)
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/client/OAuth2ClientUpdateReqVO.java
... ... @@ -14,7 +14,7 @@ import javax.validation.constraints.NotNull;
14 14 @ToString(callSuper = true)
15 15 public class OAuth2ClientUpdateReqVO extends OAuth2ClientBaseVO {
16 16  
17   - @ApiModelProperty(value = "编号", required = true)
  17 + @ApiModelProperty(value = "编号", required = true, example = "1024")
18 18 @NotNull(message = "编号不能为空")
19 19 private Long id;
20 20  
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAuthorizeInfoRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.AllArgsConstructor;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import java.util.List;
  11 +
  12 +@ApiModel("管理后台 - 授权页的信息 Response VO")
  13 +@Data
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class OAuth2OpenAuthorizeInfoRespVO {
  17 +
  18 + /**
  19 + * 客户端
  20 + */
  21 + private Client client;
  22 +
  23 + @ApiModelProperty(value = "scope 的选中信息", required = true, notes = "使用 List 保证有序性,Key 是 scope,Value 为是否选中")
  24 + private List<KeyValue<String, Boolean>> scopes;
  25 +
  26 + @Data
  27 + @NoArgsConstructor
  28 + @AllArgsConstructor
  29 + public static class Client {
  30 +
  31 + @ApiModelProperty(value = "应用名", required = true, example = "土豆")
  32 + private String name;
  33 +
  34 + @ApiModelProperty(value = "应用图标", required = true, example = "https://www.iocoder.cn/xx.png")
  35 + private String logo;
  36 +
  37 + }
  38 +
  39 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/oauth2/OAuth2OpenConvert.java
1 1 package cn.iocoder.yudao.module.system.convert.oauth2;
2 2  
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
3 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  5 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 6 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
5 7 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
  8 +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;
6 9 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
7 10 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO;
  11 +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserUpdateReqVO;
8 12 import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
9   -import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
10 13 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
11 14 import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
  15 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
  16 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;
  17 +import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
12 18 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
13 19 import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
14 20 import org.mapstruct.Mapper;
15 21 import org.mapstruct.factory.Mappers;
16 22  
  23 +import java.util.ArrayList;
17 24 import java.util.List;
  25 +import java.util.Map;
18 26  
19 27 @Mapper
20 28 public interface OAuth2OpenConvert {
... ... @@ -44,6 +52,19 @@ public interface OAuth2OpenConvert {
44 52 OAuth2OpenUserInfoRespVO.Dept convert(DeptDO dept);
45 53 List<OAuth2OpenUserInfoRespVO.Post> convertList(List<PostDO> list);
46 54  
47   - UserProfileUpdateReqVO convert(UserProfileUpdateReqVO bean);
  55 + UserProfileUpdateReqVO convert(OAuth2OpenUserUpdateReqVO bean);
  56 +
  57 + default OAuth2OpenAuthorizeInfoRespVO convert(OAuth2ClientDO client, List<OAuth2ApproveDO> approves) {
  58 + // 构建 scopes
  59 + List<KeyValue<String, Boolean>> scopes = new ArrayList<>(client.getScopes().size());
  60 + Map<String, OAuth2ApproveDO> approveMap = CollectionUtils.convertMap(approves, OAuth2ApproveDO::getScope);
  61 + client.getScopes().forEach(scope -> {
  62 + OAuth2ApproveDO approve = approveMap.get(scope);
  63 + scopes.add(new KeyValue<>(scope, approve != null ? approve.getApproved() : false));
  64 + });
  65 + // 拼接返回
  66 + return new OAuth2OpenAuthorizeInfoRespVO(
  67 + new OAuth2OpenAuthorizeInfoRespVO.Client(client.getName(), client.getLogo()), scopes);
  68 + }
48 69  
49 70 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2ApproveDO.java
... ... @@ -13,7 +13,7 @@ import java.util.Date;
13 13 /**
14 14 * OAuth2 批准 DO
15 15 *
16   - * 用户在 authorize.vue 界面时,记录接受的 scope 列表
  16 + * 用户在 sso.vue 界面时,记录接受的 scope 列表
17 17 *
18 18 * @author 芋道源码
19 19 */
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ClientServiceImpl.java
... ... @@ -181,7 +181,7 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
181 181 // 校验客户端存在、且开启
182 182 OAuth2ClientDO client = clientCache.get(clientId);
183 183 if (client == null) {
184   - throw exception(OAUTH2_CLIENT_EXISTS);
  184 + throw exception(OAUTH2_CLIENT_NOT_EXISTS);
185 185 }
186 186 if (ObjectUtil.notEqual(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
187 187 throw exception(OAUTH2_CLIENT_DISABLE);
... ...
yudao-ui-admin/src/api/login.js
... ... @@ -111,25 +111,37 @@ export function refreshToken() {
111 111 }
112 112  
113 113 // ========== OAUTH 2.0 相关 ==========
114   -export function authorize() {
  114 +
  115 +export function getAuthorize(clientId) {
  116 + return request({
  117 + url: '/system/oauth2/authorize?clientId=' + clientId,
  118 + method: 'get'
  119 + })
  120 +}
  121 +
  122 +export function authorize(responseType, clientId, redirectUri, state,
  123 + autoApprove, checkedScopes, uncheckedScopes) {
  124 + // 构建 scopes
  125 + const scopes = {};
  126 + for (const scope of checkedScopes) {
  127 + scopes[scope] = true;
  128 + }
  129 + for (const scope of uncheckedScopes) {
  130 + scopes[scope] = false;
  131 + }
  132 + // 发起请求
115 133 return service({
116 134 url: '/system/oauth2/authorize',
117 135 headers:{
118 136 'Content-type': 'application/x-www-form-urlencoded',
119   - "Access-Control-Allow-Origin": "*"
120 137 },
121 138 params: {
122   - response_type: 'code',
123   - client_id: 'test',
124   - redirect_uri: 'https://www.iocoder.cn',
125   - // scopes: {
126   - // read: true,
127   - // write: false
128   - // }
129   - scope: {
130   - read: true,
131   - write: false
132   - }
  139 + response_type: responseType,
  140 + client_id: clientId,
  141 + redirect_uri: redirectUri,
  142 + state: state,
  143 + auto_approve: autoApprove,
  144 + scope: JSON.stringify(scopes)
133 145 },
134 146 method: 'post'
135 147 })
... ...
yudao-ui-admin/src/router/index.js
... ... @@ -43,8 +43,8 @@ export const constantRoutes = [
43 43 hidden: true
44 44 },
45 45 {
46   - path: '/authorize',
47   - component: (resolve) => require(['@/views/authorize'], resolve),
  46 + path: '/sso',
  47 + component: (resolve) => require(['@/views/sso'], resolve),
48 48 hidden: true
49 49 },
50 50 {
... ...
yudao-ui-admin/src/views/authorize.vue renamed to yudao-ui-admin/src/views/sso.vue
... ... @@ -14,7 +14,7 @@
14 14  
15 15 <!-- 表单 -->
16 16 <div class="form-cont">
17   - <el-tabs class="form" style=" float:none;">
  17 + <el-tabs class="form" style=" float:none;" value="uname">
18 18 <el-tab-pane label="三方授权" name="uname">
19 19 </el-tab-pane>
20 20 </el-tabs>
... ... @@ -53,7 +53,7 @@
53 53 <span v-if="!loading">同意授权</span>
54 54 <span v-else>登 录 中...</span>
55 55 </el-button>
56   - <el-button size="medium" style="width:37%">拒绝</el-button>
  56 + <el-button size="medium" style="width:36%">拒绝</el-button>
57 57 </el-form-item>
58 58 </el-form>
59 59 </div>
... ... @@ -69,10 +69,9 @@
69 69  
70 70 <script>
71 71 import {getTenantIdByName} from "@/api/system/tenant";
72   -import Cookies from "js-cookie";
73   -import {SystemUserSocialTypeEnum} from "@/utils/constants";
74 72 import {getTenantEnable} from "@/utils/ruoyi";
75   -import {authorize} from "@/api/login";
  73 +import {authorize, getAuthorize} from "@/api/login";
  74 +import {getTenantName, setTenantId} from "@/utils/auth";
76 75  
77 76 export default {
78 77 name: "Login",
... ... @@ -82,6 +81,18 @@ export default {
82 81 loginForm: {
83 82 tenantName: "芋道源码",
84 83 },
  84 + params: { // URL 上的 client_id、scope 等参数
  85 + responseType: undefined,
  86 + clientId: undefined,
  87 + redirectUri: undefined,
  88 + state: undefined,
  89 + scopes: [], // 优先从 query 参数获取;如果未传递,从后端获取
  90 + },
  91 + client: { // 客户端信息
  92 + name: '',
  93 + logo: '',
  94 + },
  95 + checkedScopes: [], // 已选中的 scope 数组
85 96 LoginRules: {
86 97 tenantName: [
87 98 {required: true, trigger: "blur", message: "租户不能为空"},
... ... @@ -92,7 +103,7 @@ export default {
92 103 const tenantId = res.data;
93 104 if (tenantId && tenantId >= 0) {
94 105 // 设置租户
95   - Cookies.set("tenantId", tenantId);
  106 + setTenantId(tenantId)
96 107 callback();
97 108 } else {
98 109 callback('租户不存在');
... ... @@ -104,44 +115,84 @@ export default {
104 115 ]
105 116 },
106 117 loading: false,
107   - redirect: undefined,
108   - // 枚举
109   - SysUserSocialTypeEnum: SystemUserSocialTypeEnum,
  118 + //
110 119 };
111 120 },
112 121 created() {
113 122 // 租户开关
114 123 this.tenantEnable = getTenantEnable();
115   - // 重定向地址
116   - this.redirect = this.$route.query.redirect;
117 124 this.getCookie();
  125 +
  126 + // 解析参数
  127 + // 例如说【自动授权不通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read%20user.write
  128 + // 例如说【自动授权通过】:client_id=default&redirect_uri=https%3A%2F%2Fwww.iocoder.cn&response_type=code&scope=user.read
  129 + this.params.responseType = this.$route.query.response_type
  130 + this.params.clientId = this.$route.query.client_id
  131 + this.params.redirectUri = this.$route.query.redirect_uri
  132 + this.params.state = this.$route.query.state
  133 + if (this.$route.query.scope) {
  134 + this.params.scopes = this.$route.query.scope.split(' ')
  135 + }
  136 +
  137 + // 如果有 scope 参数,先执行一次自动授权,看看是否之前都授权过了。
  138 + if (this.params.scopes.length > 0) {
  139 + this.doAuthorize(true, this.params.scopes, []).then(res => {
  140 + const href = res.data
  141 + if (!href) {
  142 + console.log('自动授权未通过!')
  143 + return;
  144 + }
  145 + location.href = href
  146 + })
  147 + }
  148 +
  149 + // 获取授权页的基本信息
  150 + getAuthorize(this.params.clientId).then(res => {
  151 + this.client = res.data.client
  152 + // 解析 scope
  153 + let scopes
  154 + // 1.1 如果 params.scope 非空,则过滤下返回的 scopes
  155 + if (this.params.scopes.length > 0) {
  156 + scopes = []
  157 + for (const scope of res.data.scopes) {
  158 + if (this.params.scopes.indexOf(scope.key) >= 0) {
  159 + scopes.push(scope)
  160 + }
  161 + }
  162 + // 1.2 如果 params.scope 为空,则使用返回的 scopes 设置它
  163 + } else {
  164 + scopes = res.data.scopes
  165 + for (const scope of scopes) {
  166 + this.params.scopes.push(scope.key)
  167 + }
  168 + }
  169 + // 生成已选中的 checkedScopes
  170 + for (const scope of scopes) {
  171 + if (scope.value) {
  172 + this.checkedScopes.push(scope.key)
  173 + }
  174 + }
  175 + })
118 176 },
119 177 methods: {
120 178 getCookie() {
121   - const tenantName = Cookies.get('tenantName');
  179 + const tenantName = getTenantName();
122 180 this.loginForm = {
123   - tenantName: tenantName === undefined ? this.loginForm.tenantName : tenantName
  181 + tenantName: tenantName ? tenantName : this.loginForm.tenantName,
124 182 };
125 183 },
126 184 handleLogin() {
127   - if (true) {
128   - authorize()
129   - return;
130   - }
131 185 this.$refs.loginForm.validate(valid => {
132   - if (valid) {
133   - this.loading = true;
134   - // 发起登陆
135   - console.log("发起登录", this.loginForm);
136   - this.$store.dispatch(this.loginForm.loginType === "sms" ? "SmsLogin" : "Login", this.loginForm).then(() => {
137   - this.$router.push({path: this.redirect || "/"}).catch(() => {
138   - });
139   - }).catch(() => {
140   - this.loading = false;
141   - this.getCode();
142   - });
  186 + if (!valid) {
  187 + return
143 188 }
144   - });
  189 +
  190 +
  191 + })
  192 + },
  193 + doAuthorize(autoApprove, checkedScopes, uncheckedScopes) {
  194 + return authorize(this.params.responseType, this.params.clientId, this.params.redirectUri, this.params.state,
  195 + autoApprove, checkedScopes, uncheckedScopes)
145 196 }
146 197 }
147 198 };
... ...