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

pull/2/head
YunaiV 2022-05-15 21:30:12 +08:00
parent 65ee56c811
commit feff5aba07
12 changed files with 246 additions and 5 deletions

View File

@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -30,6 +31,10 @@ public class LoginUser {
*
*/
private Long tenantId;
/**
*
*/
private List<String> scopes;
// ========== 上下文 ==========
/**

View File

@ -79,7 +79,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
}
// 构建登录用户
return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
.setTenantId(accessToken.getTenantId());
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
} catch (ServiceException serviceException) {
// 校验 Token 不通过时,考虑到一些接口是无需登录的,所以直接返回 null 即可
return null;

View File

@ -41,4 +41,19 @@ public interface SecurityFrameworkService {
*/
boolean hasAnyRoles(String... roles);
/**
*
*
* @param scope
* @return
*/
boolean hasScope(String scope);
/**
*
*
* @param scope
* @return
*/
boolean hasAnyScopes(String... scope);
}

View File

@ -1,8 +1,13 @@
package cn.iocoder.yudao.framework.security.core.service;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import lombok.AllArgsConstructor;
import java.util.Arrays;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
@ -35,4 +40,18 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
return permissionApi.hasAnyRoles(getLoginUserId(), roles);
}
@Override
public boolean hasScope(String scope) {
return hasAnyScopes(scope);
}
@Override
public boolean hasAnyScopes(String... scope) {
LoginUser user = SecurityFrameworkUtils.getLoginUser();
if (user == null) {
return false;
}
return CollUtil.containsAny(user.getScopes(), Arrays.asList(scope));
}
}

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.auth.dto;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* OAuth2.0 访 Response DTO
@ -24,5 +25,9 @@ public class OAuth2AccessTokenCheckRespDTO implements Serializable {
*
*/
private Long tenantId;
/**
*
*/
private List<String> scopes;
}

View File

@ -28,7 +28,7 @@ Content-Type: application/x-www-form-urlencoded
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenentId}}
grant_type=password&username=admin&password=admin123&scope=user_info
grant_type=password&username=admin&password=admin123&scope=user.read
### 请求 /system/oauth2/token + refresh_token 接口 => 成功
POST {{baseUrl}}/system/oauth2/token
@ -47,3 +47,18 @@ tenant-id: {{adminTenentId}}
POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
tenant-id: {{adminTenentId}}
### 请求 /system/oauth2/user/get 接口 => 成功
GET {{baseUrl}}/system/oauth2/user/get
Authorization: Bearer 9502bd7a768a4ade920b90f41e2efd5c
tenant-id: {{adminTenentId}}
### 请求 /system/oauth2/user/update 接口 => 成功
PUT {{baseUrl}}/system/oauth2/user/update
Content-Type: application/json
Authorization: Bearer 9502bd7a768a4ade920b90f41e2efd5c
tenant-id: {{adminTenentId}}
{
"nickname": "芋道源码"
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.controller.admin.oauth2;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
@ -11,25 +12,35 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
import cn.iocoder.yudao.module.system.convert.oauth2.OAuth2OpenConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2ClientDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.dept.PostService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService;
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
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.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -40,7 +51,19 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Api(tags = "管理后台 - OAuth2.0 授权") // 提供给外部应用调用为主
/**
*
*
* /system-api/* 使访 RBAC
* OpenAPI Controller open /open-api/* scope
* client_id access token 访 API client_id
*
* access token 访 /system-api/* scope
* scope 使 getUserInfo updateUserInfo @PreAuthorize("@ss.hasScope('user.read')") @PreAuthorize("@ss.hasScope('user.write')")
*
* @author
*/
@Api(tags = "管理后台 - OAuth2.0 授权")
@RestController
@RequestMapping("/system/oauth2")
@Validated
@ -291,4 +314,43 @@ public class OAuth2OpenController {
return clientIdAndSecret;
}
// ============ 用户操作的示例,展示 scope 的使用 ============
@Resource
private AdminUserService userService;
@Resource
private DeptService deptService;
@Resource
private PostService postService;
@GetMapping("/user/get")
@ApiOperation("获得用户基本信息")
@PreAuthorize("@ss.hasScope('user.read')")
public CommonResult<OAuth2OpenUserInfoRespVO> getUserInfo() {
// 获得用户基本信息
AdminUserDO user = userService.getUser(getLoginUserId());
OAuth2OpenUserInfoRespVO resp = OAuth2OpenConvert.INSTANCE.convert(user);
// 获得部门信息
if (user.getDeptId() != null) {
DeptDO dept = deptService.getDept(user.getDeptId());
resp.setDept(OAuth2OpenConvert.INSTANCE.convert(dept));
}
// 获得岗位信息
if (CollUtil.isNotEmpty(user.getPostIds())) {
List<PostDO> posts = postService.getPosts(user.getPostIds());
resp.setPosts(OAuth2OpenConvert.INSTANCE.convertList(posts));
}
return success(resp);
}
@PutMapping("/user/update")
@ApiOperation("更新用户基本信息")
@PreAuthorize("@ss.hasScope('user.write')")
public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody UserProfileUpdateReqVO reqVO) {
// 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象,实现接口的复用。
// 主要是AdminUserService 没有自己的 BO 对象,所以复用只能这么做
userService.updateUserProfile(getLoginUserId(), OAuth2OpenConvert.INSTANCE.convert(reqVO));
return success(true);
}
}

View File

@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@ApiModel("管理后台 - 【开放接口】获得用户基本信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2OpenUserInfoRespVO {
@ApiModelProperty(value = "用户编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
private String username;
@ApiModelProperty(value = "用户昵称", required = true, example = "芋道")
private String nickname;
@ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn")
private String email;
@ApiModelProperty(value = "手机号码", example = "15601691300")
private String mobile;
@ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类")
private Integer sex;
@ApiModelProperty(value = "用户头像", example = "https://www.iocoder.cn/xxx.png")
private String avatar;
/**
*
*/
private Dept dept;
/**
*
*/
private List<Post> posts;
@ApiModel("部门")
@Data
public static class Dept {
@ApiModelProperty(value = "部门编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "部门名称", required = true, example = "研发部")
private String name;
}
@ApiModel("岗位")
@Data
public static class Post {
@ApiModelProperty(value = "岗位编号", required = true, example = "1")
private Long id;
@ApiModelProperty(value = "岗位名称", required = true, example = "开发")
private String name;
}
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Email;
import javax.validation.constraints.Size;
@ApiModel("管理后台 - 【开放接口】更新用户基本信息 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OAuth2OpenUserUpdateReqVO {
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
@Size(max = 30, message = "用户昵称长度不能超过 30 个字符")
private String nickname;
@ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn")
@Email(message = "邮箱格式不正确")
@Size(max = 50, message = "邮箱长度不能超过 50 个字符")
private String email;
@ApiModelProperty(value = "手机号码", example = "15601691300")
@Length(min = 11, max = 11, message = "手机号长度必须 11 位")
private String mobile;
@ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SexEnum 枚举类")
private Integer sex;
}

View File

@ -46,7 +46,6 @@ public class UserProfileController {
private AdminUserService userService;
@Resource
private DeptService deptService;
@Resource
private PostService postService;
@Resource

View File

@ -13,7 +13,7 @@ import javax.validation.constraints.Size;
public class UserProfileUpdateReqVO {
@ApiModelProperty(value = "用户昵称", required = true, example = "芋艿")
@Size(max = 30, message = "用户昵称长度不能超过30个字符")
@Size(max = 30, message = "用户昵称长度不能超过 30 个字符")
private String nickname;
@ApiModelProperty(value = "用户邮箱", example = "yudao@iocoder.cn")

View File

@ -4,11 +4,18 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.user.OAuth2OpenUserInfoRespVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
import cn.iocoder.yudao.module.system.dal.dataobject.dept.PostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface OAuth2OpenConvert {
@ -31,4 +38,12 @@ public interface OAuth2OpenConvert {
}
OAuth2OpenCheckTokenRespVO convert3(OAuth2AccessTokenDO bean);
// ============ 用户操作的示例 ============
OAuth2OpenUserInfoRespVO convert(AdminUserDO bean);
OAuth2OpenUserInfoRespVO.Dept convert(DeptDO dept);
List<OAuth2OpenUserInfoRespVO.Post> convertList(List<PostDO> list);
UserProfileUpdateReqVO convert(UserProfileUpdateReqVO bean);
}