Commit 668551350f3d06b9d4bb28ee5fca1f14b4574fb3

Authored by YunaiV
1 parent d1846386

完成 SSO 登录的功能

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/oauth2/OAuth2ApproveMapper.java
... ... @@ -14,7 +14,8 @@ public interface OAuth2ApproveMapper extends BaseMapperX<OAuth2ApproveDO> {
14 14 return update(updateObj, new LambdaQueryWrapperX<OAuth2ApproveDO>()
15 15 .eq(OAuth2ApproveDO::getUserId, updateObj.getUserId())
16 16 .eq(OAuth2ApproveDO::getUserType, updateObj.getUserType())
17   - .eq(OAuth2ApproveDO::getClientId, updateObj.getClientId()));
  17 + .eq(OAuth2ApproveDO::getClientId, updateObj.getClientId())
  18 + .eq(OAuth2ApproveDO::getScope, updateObj.getScope()));
18 19 }
19 20  
20 21 default List<OAuth2ApproveDO> selectListByUserIdAndUserTypeAndClientId(Long userId, Integer userType, String clientId) {
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImpl.java
... ... @@ -26,7 +26,7 @@ public class OAuth2ApproveServiceImpl implements OAuth2ApproveService {
26 26 /**
27 27 * 批准的过期时间,默认 30 天
28 28 */
29   - private static final Integer TIMEOUT = 30 * 24 * 60; // 单位:秒
  29 + private static final Integer TIMEOUT = 30 * 24 * 60 * 60; // 单位:秒
30 30  
31 31 @Resource
32 32 private OAuth2ClientService oauth2ClientService;
... ...
yudao-ui-admin/src/views/sso.vue
... ... @@ -15,7 +15,7 @@
15 15 <!-- 表单 -->
16 16 <div class="form-cont">
17 17 <el-tabs class="form" style=" float:none;" value="uname">
18   - <el-tab-pane label="三方授权" name="uname">
  18 + <el-tab-pane :label="'三方授权(' + client.name + ')'" name="uname">
19 19 </el-tab-pane>
20 20 </el-tabs>
21 21 <div>
... ... @@ -25,35 +25,23 @@
25 25 <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/>
26 26 </el-input>
27 27 </el-form-item>
28   - <!-- 账号密码登录 -->
29   - <div v-if="loginForm.loginType === 'uname'">
30   - <el-form-item prop="username">
31   - <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
32   - <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/>
33   - </el-input>
34   - </el-form-item>
35   - <el-form-item prop="password">
36   - <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码"
37   - @keyup.enter.native="handleLogin">
38   - <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
39   - </el-input>
40   - </el-form-item>
41   - <el-form-item prop="code" v-if="captchaEnable">
42   - <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%"
43   - @keyup.enter.native="handleLogin">
44   - <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
45   - </el-input>
46   - </el-form-item>
47   - </div>
48   -
  28 + <!-- 授权范围的选择 -->
  29 + 此第三方应用请求获得以下权限:
  30 + <el-form-item prop="scopes">
  31 + <el-checkbox-group v-model="loginForm.scopes">
  32 + <el-checkbox v-for="scope in params.scopes" :label="scope" :key="scope"
  33 + style="display: block; margin-bottom: -10px;">{{formatScope(scope)}}</el-checkbox>
  34 + </el-checkbox-group>
  35 + </el-form-item>
49 36 <!-- 下方的登录按钮 -->
50 37 <el-form-item style="width:100%;">
51 38 <el-button :loading="loading" size="medium" type="primary" style="width:60%;"
52   - @click.native.prevent="handleLogin">
53   - <span v-if="!loading">同意授权</span>
54   - <span v-else>登 录 中...</span>
  39 + @click.native.prevent="handleAuthorize(true)">
  40 + <span v-if="!loading">统一授权</span>
  41 + <span v-else>授 权 中...</span>
55 42 </el-button>
56   - <el-button size="medium" style="width:36%">拒绝</el-button>
  43 + <el-button size="medium" style="width:36%"
  44 + @click.native.prevent="handleAuthorize(false)">拒绝</el-button>
57 45 </el-form-item>
58 46 </el-form>
59 47 </div>
... ... @@ -80,6 +68,7 @@ export default {
80 68 tenantEnable: true,
81 69 loginForm: {
82 70 tenantName: "芋道源码",
  71 + scopes: [], // 已选中的 scope 数组
83 72 },
84 73 params: { // URL 上的 client_id、scope 等参数
85 74 responseType: undefined,
... ... @@ -92,7 +81,6 @@ export default {
92 81 name: '',
93 82 logo: '',
94 83 },
95   - checkedScopes: [], // 已选中的 scope 数组
96 84 LoginRules: {
97 85 tenantName: [
98 86 {required: true, trigger: "blur", message: "租户不能为空"},
... ... @@ -114,8 +102,7 @@ export default {
114 102 }
115 103 ]
116 104 },
117   - loading: false,
118   - //
  105 + loading: false
119 106 };
120 107 },
121 108 created() {
... ... @@ -169,7 +156,7 @@ export default {
169 156 // 生成已选中的 checkedScopes
170 157 for (const scope of scopes) {
171 158 if (scope.value) {
172   - this.checkedScopes.push(scope.key)
  159 + this.loginForm.scopes.push(scope.key)
173 160 }
174 161 }
175 162 })
... ... @@ -178,21 +165,50 @@ export default {
178 165 getCookie() {
179 166 const tenantName = getTenantName();
180 167 this.loginForm = {
  168 + ...this.loginForm,
181 169 tenantName: tenantName ? tenantName : this.loginForm.tenantName,
182 170 };
183 171 },
184   - handleLogin() {
  172 + handleAuthorize(approved) {
185 173 this.$refs.loginForm.validate(valid => {
186 174 if (!valid) {
187 175 return
188 176 }
189   -
190   -
  177 + this.loading = true
  178 + // 计算 checkedScopes + uncheckedScopes
  179 + let checkedScopes;
  180 + let uncheckedScopes;
  181 + if (approved) { // 同意授权,按照用户的选择
  182 + checkedScopes = this.loginForm.scopes
  183 + uncheckedScopes = this.params.scopes.filter(item => checkedScopes.indexOf(item) === -1)
  184 + } else { // 拒绝,则都是取消
  185 + checkedScopes = []
  186 + uncheckedScopes = this.params.scopes
  187 + }
  188 + // 提交授权的请求
  189 + this.doAuthorize(false, checkedScopes, uncheckedScopes).then(res => {
  190 + const href = res.data
  191 + if (!href) {
  192 + return;
  193 + }
  194 + location.href = href
  195 + }).finally(() => {
  196 + this.loading = false
  197 + })
191 198 })
192 199 },
193 200 doAuthorize(autoApprove, checkedScopes, uncheckedScopes) {
194 201 return authorize(this.params.responseType, this.params.clientId, this.params.redirectUri, this.params.state,
195 202 autoApprove, checkedScopes, uncheckedScopes)
  203 + },
  204 + formatScope(scope) {
  205 + // 格式化 scope 授权范围,方便用户理解。
  206 + // 这里仅仅是一个 demo,可以考虑录入到字典数据中,例如说字典类型 "system_oauth2_scope",它的每个 scope 都是一条字典数据。
  207 + switch (scope) {
  208 + case 'user.read': return '访问你的个人信息'
  209 + case 'user.write': return '修改你的个人信息'
  210 + default: return scope
  211 + }
196 212 }
197 213 }
198 214 };
... ...