mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	修改 OAuth2ClientDO 实体,支持自动授权的范围的设置
This commit is contained in:
		| @ -1,11 +1,22 @@ | ||||
| package cn.iocoder.yudao.module.system.controller.admin.auth; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||||
| import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiImplicitParams; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestParam; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; | ||||
|  | ||||
| @Api(tags = "管理后台 - OAuth2.0 授权") | ||||
| @RestController | ||||
| @RequestMapping("/system/oauth2") | ||||
| @ -21,4 +32,37 @@ public class OAuth2Controller { | ||||
|  | ||||
| //    GET  oauth/authorize AuthorizationEndpoint | ||||
|  | ||||
|     @PostMapping("/authorize") | ||||
|     @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被调用") | ||||
|     @ApiImplicitParams({ | ||||
|             @ApiImplicitParam(name = "response_type", required = true, value = "响应类型", example = "code", dataTypeClass = String.class), | ||||
|             @ApiImplicitParam(name = "client_id", required = true, value = "客户端编号", example = "tudou", dataTypeClass = String.class), | ||||
|             @ApiImplicitParam(name = "scope", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class), // 多个使用逗号分隔 | ||||
|             @ApiImplicitParam(name = "redirect_uri", required = true, value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class), | ||||
|             @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class) | ||||
|     }) | ||||
|     @OperateLog(enable = false) // 避免 Post 请求被记录操作日志 | ||||
|     // 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理 | ||||
|     public CommonResult<String> authorize(@RequestParam("response_type") String responseType, | ||||
|                                           @RequestParam("client_id") String clientId, | ||||
|                                           @RequestParam(value = "scope", required = false) String scope, | ||||
|                                           @RequestParam("redirect_uri") String redirectUri, | ||||
|                                           @RequestParam(value = "state", required = false) String state) { | ||||
|         // 0. 校验用户已经登录。通过 Spring Security 实现 | ||||
|  | ||||
|         // 1.1 校验 responseType 是否满足 code 或者 token 值 | ||||
|         if (!StrUtil.equalsAny(responseType, "code", "token")) { | ||||
|             throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token"); | ||||
|         } | ||||
|         // 1.2 校验 redirectUri 重定向域名是否合法 | ||||
|  | ||||
|         // 1.3 校验 scope 是否在 Client 授权范围内 | ||||
|  | ||||
|         // 2.1 如果是 code 授权码模式,则发放 code 授权码,并重定向 | ||||
|  | ||||
|         // 2.2 如果是 token 则是 implicit 简化模式,则发送 accessToken 访问令牌,并重定向 | ||||
|         // TODO 需要确认,是否要 refreshToken 生成 | ||||
|         return CommonResult.success(""); | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -55,10 +55,6 @@ public class OAuth2ClientBaseVO { | ||||
|     private List<@NotEmpty(message = "重定向的 URI 不能为空") | ||||
|         @URL(message = "重定向的 URI 格式不正确") String> redirectUris; | ||||
|  | ||||
|     @ApiModelProperty(value = "是否自动授权", required = true, example = "true") | ||||
|     @NotNull(message = "是否自动授权不能为空") | ||||
|     private Boolean autoApprove; | ||||
|  | ||||
|     @ApiModelProperty(value = "授权类型", required = true, example = "password", notes = "参见 OAuth2GrantTypeEnum 枚举") | ||||
|     @NotNull(message = "授权类型不能为空") | ||||
|     private List<String> authorizedGrantTypes; | ||||
| @ -66,6 +62,9 @@ public class OAuth2ClientBaseVO { | ||||
|     @ApiModelProperty(value = "授权范围", example = "user_info") | ||||
|     private List<String> scopes; | ||||
|  | ||||
|     @ApiModelProperty(value = "自动通过的授权范围", example = "user_info") | ||||
|     private List<String> autoApproveScopes; | ||||
|  | ||||
|     @ApiModelProperty(value = "权限", example = "system:user:query") | ||||
|     private List<String> authorities; | ||||
|  | ||||
|  | ||||
| @ -70,10 +70,6 @@ public class OAuth2ClientDO extends BaseDO { | ||||
|      */ | ||||
|     @TableField(typeHandler = JacksonTypeHandler.class) | ||||
|     private List<String> redirectUris; | ||||
|     /** | ||||
|      * 是否自动授权 | ||||
|      */ | ||||
|     private Boolean autoApprove; | ||||
|     /** | ||||
|      * 授权类型(模式) | ||||
|      * | ||||
| @ -86,6 +82,13 @@ public class OAuth2ClientDO extends BaseDO { | ||||
|      */ | ||||
|     @TableField(typeHandler = JacksonTypeHandler.class) | ||||
|     private List<String> scopes; | ||||
|     /** | ||||
|      * 自动授权的 Scope | ||||
|      * | ||||
|      * code 授权时,如果 scope 在这个范围内,则自动通过 | ||||
|      */ | ||||
|     @TableField(typeHandler = JacksonTypeHandler.class) | ||||
|     private List<String> autoApproveScopes; | ||||
|     /** | ||||
|      * 权限 | ||||
|      */ | ||||
|  | ||||
| @ -109,3 +109,20 @@ export function refreshToken() { | ||||
|     method: 'post' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| // ========== OAUTH 2.0 相关 ========== | ||||
| export function authorize() { | ||||
|   return service({ | ||||
|     url: '/system/oauth2/authorize', | ||||
|     headers:{ | ||||
|       'Content-type': 'application/x-www-form-urlencoded', | ||||
|       "Access-Control-Allow-Origin": "*" | ||||
|     }, | ||||
|     params: { | ||||
|       response_type: 'code', | ||||
|       client_id: 'test', | ||||
|       redirect_uri: 'https://www.iocoder.cn', | ||||
|     }, | ||||
|     method: 'post' | ||||
|   }) | ||||
| } | ||||
|  | ||||
| @ -42,6 +42,11 @@ export const constantRoutes = [ | ||||
|     component: (resolve) => require(['@/views/login'], resolve), | ||||
|     hidden: true | ||||
|   }, | ||||
|   { | ||||
|     path: '/authorize', | ||||
|     component: (resolve) => require(['@/views/authorize'], resolve), | ||||
|     hidden: true | ||||
|   }, | ||||
|   { | ||||
|     path: '/social-login', | ||||
|     component: (resolve) => require(['@/views/socialLogin'], resolve), | ||||
|  | ||||
							
								
								
									
										169
									
								
								yudao-ui-admin/src/views/authorize.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								yudao-ui-admin/src/views/authorize.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,169 @@ | ||||
| <template> | ||||
|   <div class="container"> | ||||
|     <div class="logo"></div> | ||||
|     <!-- 登录区域 --> | ||||
|     <div class="content"> | ||||
|       <!-- 配图 --> | ||||
|       <div class="pic"></div> | ||||
|       <!-- 表单 --> | ||||
|       <div class="field"> | ||||
|         <!-- [移动端]标题 --> | ||||
|         <h2 class="mobile-title"> | ||||
|           <h3 class="title">芋道后台管理系统</h3> | ||||
|         </h2> | ||||
|  | ||||
|         <!-- 表单 --> | ||||
|         <div class="form-cont"> | ||||
|           <el-tabs class="form" style=" float:none;"> | ||||
|             <el-tab-pane label="三方授权" name="uname"> | ||||
|             </el-tab-pane> | ||||
|           </el-tabs> | ||||
|           <div> | ||||
|             <el-form ref="loginForm" :model="loginForm" :rules="LoginRules" class="login-form"> | ||||
|               <el-form-item prop="tenantName" v-if="tenantEnable"> | ||||
|                 <el-input v-model="loginForm.tenantName" type="text" auto-complete="off" placeholder='租户'> | ||||
|                   <svg-icon slot="prefix" icon-class="tree" class="el-input__icon input-icon"/> | ||||
|                 </el-input> | ||||
|               </el-form-item> | ||||
|               <!-- 账号密码登录 --> | ||||
|               <div v-if="loginForm.loginType === 'uname'"> | ||||
|                 <el-form-item prop="username"> | ||||
|                   <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号"> | ||||
|                     <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/> | ||||
|                   </el-input> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item prop="password"> | ||||
|                   <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" | ||||
|                             @keyup.enter.native="handleLogin"> | ||||
|                     <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/> | ||||
|                   </el-input> | ||||
|                 </el-form-item> | ||||
|                 <el-form-item prop="code" v-if="captchaEnable"> | ||||
|                   <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" | ||||
|                             @keyup.enter.native="handleLogin"> | ||||
|                     <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/> | ||||
|                   </el-input> | ||||
|                 </el-form-item> | ||||
|               </div> | ||||
|  | ||||
|               <!-- 下方的登录按钮 --> | ||||
|               <el-form-item style="width:100%;"> | ||||
|                 <el-button :loading="loading" size="medium" type="primary" style="width:60%;" | ||||
|                            @click.native.prevent="handleLogin"> | ||||
|                   <span v-if="!loading">同意授权</span> | ||||
|                   <span v-else>登 录 中...</span> | ||||
|                 </el-button> | ||||
|                 <el-button size="medium" style="width:37%">拒绝</el-button> | ||||
|               </el-form-item> | ||||
|             </el-form> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <!-- footer --> | ||||
|     <div class="footer"> | ||||
|       Copyright © 2020-2022 iocoder.cn All Rights Reserved. | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import {getTenantIdByName} from "@/api/system/tenant"; | ||||
| import Cookies from "js-cookie"; | ||||
| import {SystemUserSocialTypeEnum} from "@/utils/constants"; | ||||
| import {getTenantEnable} from "@/utils/ruoyi"; | ||||
| import {authorize} from "@/api/login"; | ||||
|  | ||||
| export default { | ||||
|   name: "Login", | ||||
|   data() { | ||||
|     return { | ||||
|       tenantEnable: true, | ||||
|       loginForm: { | ||||
|         tenantName: "芋道源码", | ||||
|       }, | ||||
|       LoginRules: { | ||||
|         tenantName: [ | ||||
|           {required: true, trigger: "blur", message: "租户不能为空"}, | ||||
|           { | ||||
|             validator: (rule, value, callback) => { | ||||
|               // debugger | ||||
|               getTenantIdByName(value).then(res => { | ||||
|                 const tenantId = res.data; | ||||
|                 if (tenantId && tenantId >= 0) { | ||||
|                   // 设置租户 | ||||
|                   Cookies.set("tenantId", tenantId); | ||||
|                   callback(); | ||||
|                 } else { | ||||
|                   callback('租户不存在'); | ||||
|                 } | ||||
|               }); | ||||
|             }, | ||||
|             trigger: 'blur' | ||||
|           } | ||||
|         ] | ||||
|       }, | ||||
|       loading: false, | ||||
|       redirect: undefined, | ||||
|       // 枚举 | ||||
|       SysUserSocialTypeEnum: SystemUserSocialTypeEnum, | ||||
|     }; | ||||
|   }, | ||||
|   created() { | ||||
|     // 租户开关 | ||||
|     this.tenantEnable = getTenantEnable(); | ||||
|     // 重定向地址 | ||||
|     this.redirect = this.$route.query.redirect; | ||||
|     this.getCookie(); | ||||
|   }, | ||||
|   methods: { | ||||
|     getCookie() { | ||||
|       const tenantName = Cookies.get('tenantName'); | ||||
|       this.loginForm = { | ||||
|         tenantName: tenantName === undefined ? this.loginForm.tenantName : tenantName | ||||
|       }; | ||||
|     }, | ||||
|     handleLogin() { | ||||
|       if (true) { | ||||
|         authorize() | ||||
|         return; | ||||
|       } | ||||
|       this.$refs.loginForm.validate(valid => { | ||||
|         if (valid) { | ||||
|           this.loading = true; | ||||
|           // 发起登陆 | ||||
|           console.log("发起登录", this.loginForm); | ||||
|           this.$store.dispatch(this.loginForm.loginType === "sms" ? "SmsLogin" : "Login", this.loginForm).then(() => { | ||||
|             this.$router.push({path: this.redirect || "/"}).catch(() => { | ||||
|             }); | ||||
|           }).catch(() => { | ||||
|             this.loading = false; | ||||
|             this.getCode(); | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| }; | ||||
| </script> | ||||
| <style lang="scss" scoped> | ||||
| @import "~@/assets/styles/login.scss"; | ||||
| .oauth-login { | ||||
|   display: flex; | ||||
|   align-items: cen; | ||||
|   cursor:pointer; | ||||
| } | ||||
| .oauth-login-item { | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
|   margin-right: 10px; | ||||
| } | ||||
| .oauth-login-item img { | ||||
|   height: 25px; | ||||
|   width: 25px; | ||||
| } | ||||
| .oauth-login-item span:hover { | ||||
|   text-decoration: underline red; | ||||
|   color: red; | ||||
| } | ||||
| </style> | ||||
| @ -108,23 +108,16 @@ | ||||
|             <el-option v-for="redirectUri in form.redirectUris" :key="redirectUri" :label="redirectUri" :value="redirectUri"/> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="是否自动授权" prop="autoApprove"> | ||||
|           <el-radio-group v-model="form.autoApprove"> | ||||
|             <el-radio :key="true" :label="true">自动登录</el-radio> | ||||
|             <el-radio :key="false" :label="false">手动登录</el-radio> | ||||
|           </el-radio-group> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="授权类型" prop="authorizedGrantTypes"> | ||||
|           <el-select v-model="form.authorizedGrantTypes" multiple filterable placeholder="请输入授权类型" style="width: 500px" > | ||||
|             <el-option v-for="dict in this.getDictDatas(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)" | ||||
|                        :key="dict.value" :label="dict.label" :value="dict.value"/> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="授权范围" prop="scopes"> | ||||
|           <el-select v-model="form.scopes" multiple filterable allow-create placeholder="请输入授权范围" style="width: 500px" > | ||||
|             <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="自动授权" prop="autoApproveScopes"> | ||||
|           <el-select v-model="form.autoApproveScopes" multiple filterable placeholder="请输入授权范围" style="width: 500px" > | ||||
|             <el-option v-for="scope in form.scopes" :key="scope" :label="scope" :value="scope"/> | ||||
|           </el-select> | ||||
|         </el-form-item> | ||||
|         <el-form-item label="权限" prop="authorities"> | ||||
|           <el-select v-model="form.authorities" multiple filterable allow-create placeholder="请输入权限" style="width: 500px" > | ||||
|             <el-option v-for="authority in form.authorities" :key="authority" :label="authority" :value="authority"/> | ||||
| @ -196,7 +189,6 @@ export default { | ||||
|         accessTokenValiditySeconds: [{ required: true, message: "访问令牌的有效期不能为空", trigger: "blur" }], | ||||
|         refreshTokenValiditySeconds: [{ required: true, message: "刷新令牌的有效期不能为空", trigger: "blur" }], | ||||
|         redirectUris: [{ required: true, message: "可重定向的 URI 地址不能为空", trigger: "blur" }], | ||||
|         autoApprove: [{ required: true, message: "是否自动授权不能为空", trigger: "blur" }], | ||||
|         authorizedGrantTypes: [{ required: true, message: "授权类型不能为空", trigger: "blur" }], | ||||
|       } | ||||
|     }; | ||||
| @ -235,9 +227,9 @@ export default { | ||||
|         accessTokenValiditySeconds: 30 * 60, | ||||
|         refreshTokenValiditySeconds: 30 * 24 * 60, | ||||
|         redirectUris: [], | ||||
|         autoApprove: true, | ||||
|         authorizedGrantTypes: [], | ||||
|         scopes: [], | ||||
|         autoApproveScopes: [], | ||||
|         authorities: [], | ||||
|         resourceIds: [], | ||||
|         additionalInformation: undefined, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV