Commit feff5aba07a1d6110e0d63b98a75cb560336da4a

Authored by YunaiV
1 parent 65ee56c8

增加 oauth2 的 scope 的校验方法,与使用示例

Showing 12 changed files with 246 additions and 5 deletions
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/LoginUser.java
... ... @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
6 6 import lombok.Data;
7 7  
8 8 import java.util.HashMap;
  9 +import java.util.List;
9 10 import java.util.Map;
10 11  
11 12 /**
... ... @@ -30,6 +31,10 @@ public class LoginUser {
30 31 * 租户编号
31 32 */
32 33 private Long tenantId;
  34 + /**
  35 + * 授权范围
  36 + */
  37 + private List<String> scopes;
33 38  
34 39 // ========== 上下文 ==========
35 40 /**
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/filter/TokenAuthenticationFilter.java
... ... @@ -79,7 +79,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
79 79 }
80 80 // 构建登录用户
81 81 return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
82   - .setTenantId(accessToken.getTenantId());
  82 + .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
83 83 } catch (ServiceException serviceException) {
84 84 // 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
85 85 return null;
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkService.java
... ... @@ -41,4 +41,19 @@ public interface SecurityFrameworkService {
41 41 */
42 42 boolean hasAnyRoles(String... roles);
43 43  
  44 + /**
  45 + * 判断是否有授权
  46 + *
  47 + * @param scope 授权
  48 + * @return 是否
  49 + */
  50 + boolean hasScope(String scope);
  51 +
  52 + /**
  53 + * 判断是否有授权范围,任一一个即可
  54 + *
  55 + * @param scope 授权范围数组
  56 + * @return 是否
  57 + */
  58 + boolean hasAnyScopes(String... scope);
44 59 }
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/service/SecurityFrameworkServiceImpl.java
1 1 package cn.iocoder.yudao.framework.security.core.service;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.security.core.LoginUser;
  5 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
3 6 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
4 7 import lombok.AllArgsConstructor;
5 8  
  9 +import java.util.Arrays;
  10 +
6 11 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
7 12  
8 13 /**
... ... @@ -35,4 +40,18 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
35 40 return permissionApi.hasAnyRoles(getLoginUserId(), roles);
36 41 }
37 42  
  43 + @Override
  44 + public boolean hasScope(String scope) {
  45 + return hasAnyScopes(scope);
  46 + }
  47 +
  48 + @Override
  49 + public boolean hasAnyScopes(String... scope) {
  50 + LoginUser user = SecurityFrameworkUtils.getLoginUser();
  51 + if (user == null) {
  52 + return false;
  53 + }
  54 + return CollUtil.containsAny(user.getScopes(), Arrays.asList(scope));
  55 + }
  56 +
38 57 }
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/auth/dto/OAuth2AccessTokenCheckRespDTO.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.auth.dto;
3 3 import lombok.Data;
4 4  
5 5 import java.io.Serializable;
  6 +import java.util.List;
6 7  
7 8 /**
8 9 * OAuth2.0 访问令牌的校验 Response DTO
... ... @@ -24,5 +25,9 @@ public class OAuth2AccessTokenCheckRespDTO implements Serializable {
24 25 * 租户编号
25 26 */
26 27 private Long tenantId;
  28 + /**
  29 + * 授权范围的数组
  30 + */
  31 + private List<String> scopes;
27 32  
28 33 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http
... ... @@ -28,7 +28,7 @@ Content-Type: application/x-www-form-urlencoded
28 28 Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
29 29 tenant-id: {{adminTenentId}}
30 30  
31   -grant_type=password&username=admin&password=admin123&scope=user_info
  31 +grant_type=password&username=admin&password=admin123&scope=user.read
32 32  
33 33 ### 请求 /system/oauth2/token + refresh_token 接口 => 成功
34 34 POST {{baseUrl}}/system/oauth2/token
... ... @@ -47,3 +47,18 @@ tenant-id: {{adminTenentId}}
47 47 POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106
48 48 Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
49 49 tenant-id: {{adminTenentId}}
  50 +
  51 +### 请求 /system/oauth2/user/get 接口 => 成功
  52 +GET {{baseUrl}}/system/oauth2/user/get
  53 +Authorization: Bearer 9502bd7a768a4ade920b90f41e2efd5c
  54 +tenant-id: {{adminTenentId}}
  55 +
  56 +### 请求 /system/oauth2/user/update 接口 => 成功
  57 +PUT {{baseUrl}}/system/oauth2/user/update
  58 +Content-Type: application/json
  59 +Authorization: Bearer 9502bd7a768a4ade920b90f41e2efd5c
  60 +tenant-id: {{adminTenentId}}
  61 +
  62 +{
  63 + "nickname": "芋道源码"
  64 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java
1 1 package cn.iocoder.yudao.module.system.controller.admin.oauth2;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import cn.hutool.core.util.ArrayUtil;
5 6 import cn.hutool.core.util.ObjectUtil;
... ... @@ -11,25 +12,35 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
11 12 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
12 13 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
13 14 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
  15 +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;
14 17 import cn.iocoder.yudao.module.system.convert.oauth2.OAuth2OpenConvert;
15 18 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
16 19 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
  20 +import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
  21 +import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
  22 +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
17 23 import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
  24 +import cn.iocoder.yudao.module.system.service.dept.DeptService;
  25 +import cn.iocoder.yudao.module.system.service.dept.PostService;
18 26 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService;
19 27 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
20 28 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService;
21 29 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
  30 +import cn.iocoder.yudao.module.system.service.user.AdminUserService;
22 31 import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
23 32 import io.swagger.annotations.Api;
24 33 import io.swagger.annotations.ApiImplicitParam;
25 34 import io.swagger.annotations.ApiImplicitParams;
26 35 import io.swagger.annotations.ApiOperation;
27 36 import lombok.extern.slf4j.Slf4j;
  37 +import org.springframework.security.access.prepost.PreAuthorize;
28 38 import org.springframework.validation.annotation.Validated;
29 39 import org.springframework.web.bind.annotation.*;
30 40  
31 41 import javax.annotation.Resource;
32 42 import javax.servlet.http.HttpServletRequest;
  43 +import javax.validation.Valid;
33 44 import java.util.Collections;
34 45 import java.util.List;
35 46 import java.util.Map;
... ... @@ -40,7 +51,19 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
40 51 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
41 52 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
42 53  
43   -@Api(tags = "管理后台 - OAuth2.0 授权") // 提供给外部应用调用为主
  54 +/**
  55 + * 提供给外部应用调用为主
  56 + *
  57 + * 一般来说,管理后台的 /system-api/* 是不直接提供给外部应用使用,主要是外部应用能够访问的数据与接口是有限的,而管理后台的 RBAC 无法很好的控制。
  58 + * 参考大量的开放平台,都是独立的一套 OpenAPI,对应到【本系统】就是在 Controller 下新建 open 包,实现 /open-api/* 接口,然后通过 scope 进行控制。
  59 + * 另外,一个公司如果有多个管理后台,它们 client_id 产生的 access token 相互之间是无法互通的,即无法访问它们系统的 API 接口,直到两个 client_id 产生信任授权。
  60 + *
  61 + * 考虑到【本系统】暂时不想做的过于复杂,默认只有获取到 access token 之后,可以访问【本系统】管理后台的 /system-api/* 所有接口,除非手动添加 scope 控制。
  62 + * scope 的使用示例,可见当前类的 getUserInfo 和 updateUserInfo 方法,上面有 @PreAuthorize("@ss.hasScope('user.read')") 和 @PreAuthorize("@ss.hasScope('user.write')") 注解
  63 + *
  64 + * @author 芋道源码
  65 + */
  66 +@Api(tags = "管理后台 - OAuth2.0 授权")
44 67 @RestController
45 68 @RequestMapping("/system/oauth2")
46 69 @Validated
... ... @@ -291,4 +314,43 @@ public class OAuth2OpenController {
291 314 return clientIdAndSecret;
292 315 }
293 316  
  317 + // ============ 用户操作的示例,展示 scope 的使用 ============
  318 +
  319 + @Resource
  320 + private AdminUserService userService;
  321 + @Resource
  322 + private DeptService deptService;
  323 + @Resource
  324 + private PostService postService;
  325 +
  326 + @GetMapping("/user/get")
  327 + @ApiOperation("获得用户基本信息")
  328 + @PreAuthorize("@ss.hasScope('user.read')")
  329 + public CommonResult<OAuth2OpenUserInfoRespVO> getUserInfo() {
  330 + // 获得用户基本信息
  331 + AdminUserDO user = userService.getUser(getLoginUserId());
  332 + OAuth2OpenUserInfoRespVO resp = OAuth2OpenConvert.INSTANCE.convert(user);
  333 + // 获得部门信息
  334 + if (user.getDeptId() != null) {
  335 + DeptDO dept = deptService.getDept(user.getDeptId());
  336 + resp.setDept(OAuth2OpenConvert.INSTANCE.convert(dept));
  337 + }
  338 + // 获得岗位信息
  339 + if (CollUtil.isNotEmpty(user.getPostIds())) {
  340 + List<PostDO> posts = postService.getPosts(user.getPostIds());
  341 + resp.setPosts(OAuth2OpenConvert.INSTANCE.convertList(posts));
  342 + }
  343 + return success(resp);
  344 + }
  345 +
  346 + @PutMapping("/user/update")
  347 + @ApiOperation("更新用户基本信息")
  348 + @PreAuthorize("@ss.hasScope('user.write')")
  349 + public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody UserProfileUpdateReqVO reqVO) {
  350 + // 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象,实现接口的复用。
  351 + // 主要是,AdminUserService 没有自己的 BO 对象,所以复用只能这么做
  352 + userService.updateUserProfile(getLoginUserId(), OAuth2OpenConvert.INSTANCE.convert(reqVO));
  353 + return success(true);
  354 + }
  355 +
294 356 }
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/user/OAuth2OpenUserInfoRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +import java.util.List;
  10 +
  11 +@ApiModel("管理后台 - 【开放接口】获得用户基本信息 Response VO")
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class OAuth2OpenUserInfoRespVO {
  16 +
  17 + @ApiModelProperty(value = "用户编号", required = true, example = "1")
  18 + private Long id;
  19 +
  20 + @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
  21 + private String username;
  22 +
  23 + @ApiModelProperty(value = "用户昵称", required = true, example = "芋道")
  24 + private String nickname;
  25 +
  26 + @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn")
  27 + private String email;
  28 + @ApiModelProperty(value = "手机号码", example = "15601691300")
  29 + private String mobile;
  30 +
  31 + @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类")
  32 + private Integer sex;
  33 +
  34 + @ApiModelProperty(value = "用户头像", example = "https://www.iocoder.cn/xxx.png")
  35 + private String avatar;
  36 +
  37 + /**
  38 + * 所在部门
  39 + */
  40 + private Dept dept;
  41 +
  42 + /**
  43 + * 所属岗位数组
  44 + */
  45 + private List<Post> posts;
  46 +
  47 + @ApiModel("部门")
  48 + @Data
  49 + public static class Dept {
  50 +
  51 + @ApiModelProperty(value = "部门编号", required = true, example = "1")
  52 + private Long id;
  53 +
  54 + @ApiModelProperty(value = "部门名称", required = true, example = "研发部")
  55 + private String name;
  56 +
  57 + }
  58 +
  59 + @ApiModel("岗位")
  60 + @Data
  61 + public static class Post {
  62 +
  63 + @ApiModelProperty(value = "岗位编号", required = true, example = "1")
  64 + private Long id;
  65 +
  66 + @ApiModelProperty(value = "岗位名称", required = true, example = "开发")
  67 + private String name;
  68 +
  69 + }
  70 +
  71 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/user/OAuth2OpenUserUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +import org.hibernate.validator.constraints.Length;
  9 +
  10 +import javax.validation.constraints.Email;
  11 +import javax.validation.constraints.Size;
  12 +
  13 +@ApiModel("管理后台 - 【开放接口】更新用户基本信息 Request VO")
  14 +@Data
  15 +@NoArgsConstructor
  16 +@AllArgsConstructor
  17 +public class OAuth2OpenUserUpdateReqVO {
  18 +
  19 + @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
  20 + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符")
  21 + private String nickname;
  22 +
  23 + @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn")
  24 + @Email(message = "邮箱格式不正确")
  25 + @Size(max = 50, message = "邮箱长度不能超过 50 个字符")
  26 + private String email;
  27 +
  28 + @ApiModelProperty(value = "手机号码", example = "15601691300")
  29 + @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
  30 + private String mobile;
  31 +
  32 + @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类")
  33 + private Integer sex;
  34 +
  35 +}
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java
... ... @@ -46,7 +46,6 @@ public class UserProfileController {
46 46 private AdminUserService userService;
47 47 @Resource
48 48 private DeptService deptService;
49   -
50 49 @Resource
51 50 private PostService postService;
52 51 @Resource
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java
... ... @@ -13,7 +13,7 @@ import javax.validation.constraints.Size;
13 13 public class UserProfileUpdateReqVO {
14 14  
15 15 @ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
16   - @Size(max = 30, message = "用户昵称长度不能超过30个字符")
  16 + @Size(max = 30, message = "用户昵称长度不能超过 30 个字符")
17 17 private String nickname;
18 18  
19 19 @ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn")
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/convert/oauth2/OAuth2OpenConvert.java
... ... @@ -4,11 +4,18 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
5 5 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
6 6 import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
  7 +import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO;
  8 +import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
7 9 import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
  10 +import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
  11 +import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
  12 +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
8 13 import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
9 14 import org.mapstruct.Mapper;
10 15 import org.mapstruct.factory.Mappers;
11 16  
  17 +import java.util.List;
  18 +
12 19 @Mapper
13 20 public interface OAuth2OpenConvert {
14 21  
... ... @@ -31,4 +38,12 @@ public interface OAuth2OpenConvert {
31 38 }
32 39 OAuth2OpenCheckTokenRespVO convert3(OAuth2AccessTokenDO bean);
33 40  
  41 + // ============ 用户操作的示例 ============
  42 +
  43 + OAuth2OpenUserInfoRespVO convert(AdminUserDO bean);
  44 + OAuth2OpenUserInfoRespVO.Dept convert(DeptDO dept);
  45 + List<OAuth2OpenUserInfoRespVO.Post> convertList(List<PostDO> list);
  46 +
  47 + UserProfileUpdateReqVO convert(UserProfileUpdateReqVO bean);
  48 +
34 49 }
... ...