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,7 +14,8 @@ public interface OAuth2ApproveMapper extends BaseMapperX<OAuth2ApproveDO> {
14 return update(updateObj, new LambdaQueryWrapperX<OAuth2ApproveDO>() 14 return update(updateObj, new LambdaQueryWrapperX<OAuth2ApproveDO>()
15 .eq(OAuth2ApproveDO::getUserId, updateObj.getUserId()) 15 .eq(OAuth2ApproveDO::getUserId, updateObj.getUserId())
16 .eq(OAuth2ApproveDO::getUserType, updateObj.getUserType()) 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 default List<OAuth2ApproveDO> selectListByUserIdAndUserTypeAndClientId(Long userId, Integer userType, String clientId) { 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,7 +26,7 @@ public class OAuth2ApproveServiceImpl implements OAuth2ApproveService {
26 /** 26 /**
27 * 批准的过期时间,默认 30 天 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 @Resource 31 @Resource
32 private OAuth2ClientService oauth2ClientService; 32 private OAuth2ClientService oauth2ClientService;
yudao-ui-admin/src/views/sso.vue
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 <!-- 表单 --> 15 <!-- 表单 -->
16 <div class="form-cont"> 16 <div class="form-cont">
17 <el-tabs class="form" style=" float:none;" value="uname"> 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 </el-tab-pane> 19 </el-tab-pane>
20 </el-tabs> 20 </el-tabs>
21 <div> 21 <div>
@@ -25,35 +25,23 @@ @@ -25,35 +25,23 @@
25 <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/> 25 <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/>
26 </el-input> 26 </el-input>
27 </el-form-item> 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 <el-form-item style="width:100%;"> 37 <el-form-item style="width:100%;">
51 <el-button :loading="loading" size="medium" type="primary" style="width:60%;" 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 </el-button> 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 </el-form-item> 45 </el-form-item>
58 </el-form> 46 </el-form>
59 </div> 47 </div>
@@ -80,6 +68,7 @@ export default { @@ -80,6 +68,7 @@ export default {
80 tenantEnable: true, 68 tenantEnable: true,
81 loginForm: { 69 loginForm: {
82 tenantName: "芋道源码", 70 tenantName: "芋道源码",
  71 + scopes: [], // 已选中的 scope 数组
83 }, 72 },
84 params: { // URL 上的 client_id、scope 等参数 73 params: { // URL 上的 client_id、scope 等参数
85 responseType: undefined, 74 responseType: undefined,
@@ -92,7 +81,6 @@ export default { @@ -92,7 +81,6 @@ export default {
92 name: '', 81 name: '',
93 logo: '', 82 logo: '',
94 }, 83 },
95 - checkedScopes: [], // 已选中的 scope 数组  
96 LoginRules: { 84 LoginRules: {
97 tenantName: [ 85 tenantName: [
98 {required: true, trigger: "blur", message: "租户不能为空"}, 86 {required: true, trigger: "blur", message: "租户不能为空"},
@@ -114,8 +102,7 @@ export default { @@ -114,8 +102,7 @@ export default {
114 } 102 }
115 ] 103 ]
116 }, 104 },
117 - loading: false,  
118 - // 105 + loading: false
119 }; 106 };
120 }, 107 },
121 created() { 108 created() {
@@ -169,7 +156,7 @@ export default { @@ -169,7 +156,7 @@ export default {
169 // 生成已选中的 checkedScopes 156 // 生成已选中的 checkedScopes
170 for (const scope of scopes) { 157 for (const scope of scopes) {
171 if (scope.value) { 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,21 +165,50 @@ export default {
178 getCookie() { 165 getCookie() {
179 const tenantName = getTenantName(); 166 const tenantName = getTenantName();
180 this.loginForm = { 167 this.loginForm = {
  168 + ...this.loginForm,
181 tenantName: tenantName ? tenantName : this.loginForm.tenantName, 169 tenantName: tenantName ? tenantName : this.loginForm.tenantName,
182 }; 170 };
183 }, 171 },
184 - handleLogin() { 172 + handleAuthorize(approved) {
185 this.$refs.loginForm.validate(valid => { 173 this.$refs.loginForm.validate(valid => {
186 if (!valid) { 174 if (!valid) {
187 return 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 doAuthorize(autoApprove, checkedScopes, uncheckedScopes) { 200 doAuthorize(autoApprove, checkedScopes, uncheckedScopes) {
194 return authorize(this.params.responseType, this.params.clientId, this.params.redirectUri, this.params.state, 201 return authorize(this.params.responseType, this.params.clientId, this.params.redirectUri, this.params.state,
195 autoApprove, checkedScopes, uncheckedScopes) 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 };