mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	完成 oauth2 code 授权码模式的实现
This commit is contained in:
		@@ -13,3 +13,11 @@ Authorization: Bearer {{token}}
 | 
			
		||||
tenant-id: {{adminTenentId}}
 | 
			
		||||
 | 
			
		||||
response_type=code&client_id=default&scope={"user_info": true}&redirect_uri=https://www.iocoder.cn&auto_approve=true
 | 
			
		||||
 | 
			
		||||
### 请求 /system/oauth2/token + code 接口 => 成功
 | 
			
		||||
POST {{baseUrl}}/system/oauth2/token
 | 
			
		||||
Content-Type: application/x-www-form-urlencoded
 | 
			
		||||
Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw==
 | 
			
		||||
tenant-id: {{adminTenentId}}
 | 
			
		||||
 | 
			
		||||
grant_type=authorization_code&redirect_uri=https://www.iocoder.cn
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,16 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.controller.admin.oauth2;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.lang.Assert;
 | 
			
		||||
import cn.hutool.core.util.ArrayUtil;
 | 
			
		||||
import cn.hutool.core.util.ObjectUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
 | 
			
		||||
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.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.enums.auth.OAuth2GrantTypeEnum;
 | 
			
		||||
@@ -23,6 +27,7 @@ import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import javax.servlet.http.HttpServletRequest;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
@@ -33,21 +38,17 @@ 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 授权")
 | 
			
		||||
@Api(tags = "管理后台 - OAuth2.0 授权") // 提供给外部应用调用为主
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/system/oauth2")
 | 
			
		||||
@Validated
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class OAuth2Controller {
 | 
			
		||||
 | 
			
		||||
//    POST oauth/token TokenEndpoint:Password、Implicit、Code、Refresh Token
 | 
			
		||||
public class OAuth2OpenController {
 | 
			
		||||
 | 
			
		||||
//    POST oauth/check_token CheckTokenEndpoint
 | 
			
		||||
 | 
			
		||||
//    DELETE oauth/token ConsumerTokenServices#revokeToken
 | 
			
		||||
 | 
			
		||||
//    GET  oauth/authorize AuthorizationEndpoint
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private OAuth2GrantService oauth2GrantService;
 | 
			
		||||
    @Resource
 | 
			
		||||
@@ -55,6 +56,56 @@ public class OAuth2Controller {
 | 
			
		||||
    @Resource
 | 
			
		||||
    private OAuth2ApproveService oauth2ApproveService;
 | 
			
		||||
 | 
			
		||||
    @PostMapping("/token")
 | 
			
		||||
    @ApiOperation(value = "获得访问令牌", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【获取】调用")
 | 
			
		||||
    @ApiImplicitParams({
 | 
			
		||||
            @ApiImplicitParam(name = "grant_type", required = true, value = "授权类型", example = "code", dataTypeClass = String.class),
 | 
			
		||||
            @ApiImplicitParam(name = "code", value = "授权范围", example = "userinfo.read", dataTypeClass = String.class),
 | 
			
		||||
            @ApiImplicitParam(name = "redirect_uri", value = "重定向 URI", example = "https://www.iocoder.cn", dataTypeClass = String.class),
 | 
			
		||||
            @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class)
 | 
			
		||||
    })
 | 
			
		||||
    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
 | 
			
		||||
    public CommonResult<OAuth2OpenAccessTokenRespVO> postAccessToken(HttpServletRequest request,
 | 
			
		||||
                                                                     @RequestParam("grant_type") String grantType,
 | 
			
		||||
                                                                     @RequestParam(value = "code", required = false) String code, // 授权码模式
 | 
			
		||||
                                                                     @RequestParam(value = "redirect_uri", required = false) String redirectUri, // 授权码模式
 | 
			
		||||
                                                                     @RequestParam(value = "state", required = false) String state) { // 授权码模式
 | 
			
		||||
        // 授权类型
 | 
			
		||||
        OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType);
 | 
			
		||||
        if (grantTypeEnum == null) {
 | 
			
		||||
            throw exception0(BAD_REQUEST.getCode(), StrUtil.format("未知授权类型({})", grantType));
 | 
			
		||||
        }
 | 
			
		||||
        if (grantTypeEnum == OAuth2GrantTypeEnum.IMPLICIT) {
 | 
			
		||||
            throw exception0(BAD_REQUEST.getCode(), "Token 接口不支持 implicit 授权模式");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验客户端
 | 
			
		||||
        String[] clientIdAndSecret = HttpUtils.obtainBasicAuthorization(request);
 | 
			
		||||
        if (ArrayUtil.isEmpty(clientIdAndSecret) || clientIdAndSecret.length != 2) {
 | 
			
		||||
            throw exception0(BAD_REQUEST.getCode(), "client_id 或 client_secret 未正确传递");
 | 
			
		||||
        }
 | 
			
		||||
        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1], grantType, null, null);
 | 
			
		||||
 | 
			
		||||
        // 根据授权模式,获取访问令牌
 | 
			
		||||
        OAuth2AccessTokenDO accessTokenDO = null;
 | 
			
		||||
        switch (grantTypeEnum) {
 | 
			
		||||
            case AUTHORIZATION_CODE:
 | 
			
		||||
                accessTokenDO = oauth2GrantService.grantAuthorizationCodeForAccessToken(client.getClientId(), code, redirectUri, state);
 | 
			
		||||
                break;
 | 
			
		||||
            case PASSWORD:
 | 
			
		||||
                break;
 | 
			
		||||
            case CLIENT_CREDENTIALS:
 | 
			
		||||
                break;
 | 
			
		||||
            case REFRESH_TOKEN:
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new IllegalArgumentException("未知授权类型:" + grantType);
 | 
			
		||||
        }
 | 
			
		||||
        Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查
 | 
			
		||||
        return success(OAuth2OpenConvert.INSTANCE.convert(accessTokenDO));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //    GET  oauth/authorize AuthorizationEndpoint TODO
 | 
			
		||||
    @GetMapping("/authorize")
 | 
			
		||||
    @ApiOperation(value = "获得授权信息", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【获取】调用")
 | 
			
		||||
    @ApiImplicitParams({
 | 
			
		||||
@@ -75,12 +126,23 @@ public class OAuth2Controller {
 | 
			
		||||
        // 1.1 校验 responseType 是否满足 code 或者 token 值
 | 
			
		||||
        OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);
 | 
			
		||||
        // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内
 | 
			
		||||
        oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes, redirectUri);
 | 
			
		||||
        oauth2ClientService.validOAuthClientFromCache(clientId, null,
 | 
			
		||||
                grantTypeEnum.getGrantType(), scopes, redirectUri);
 | 
			
		||||
 | 
			
		||||
        // 3. 不满足自动授权,则返回授权相关的展示信息
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 approveOrDeny 方法
 | 
			
		||||
     *
 | 
			
		||||
     * 场景一:【自动授权 autoApprove = true】
 | 
			
		||||
     *      刚进入 authorize.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 scope 的自动授权
 | 
			
		||||
     * 场景二:【手动授权 autoApprove = false】
 | 
			
		||||
     *      在 authorize.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved 为 true 或者 false
 | 
			
		||||
     *
 | 
			
		||||
     * 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理
 | 
			
		||||
     */
 | 
			
		||||
    @PostMapping("/authorize")
 | 
			
		||||
    @ApiOperation(value = "申请授权", notes = "适合 code 授权码模式,或者 implicit 简化模式;在 authorize.vue 单点登录界面被【提交】调用")
 | 
			
		||||
    @ApiImplicitParams({
 | 
			
		||||
@@ -92,9 +154,6 @@ public class OAuth2Controller {
 | 
			
		||||
            @ApiImplicitParam(name = "state", example = "123321", dataTypeClass = String.class)
 | 
			
		||||
    })
 | 
			
		||||
    @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
 | 
			
		||||
    // 场景一:【自动授权 autoApprove = true】刚进入 authorize.vue 界面,调用该接口,用户历史已经给该应用做过对应的授权,或者 OAuth2Client 支持该 scope 的自动授权
 | 
			
		||||
    // 场景二:【手动授权 autoApprove = false】在 authorize.vue 界面,用户选择好 scope 授权范围,调用该接口,进行授权。此时,approved 为 true 或者 false
 | 
			
		||||
    // 因为前后端分离,Axios 无法很好的处理 302 重定向,所以和 Spring Security OAuth 略有不同,返回结果是重定向的 URL,剩余交给前端处理
 | 
			
		||||
    public CommonResult<String> approveOrDeny(@RequestParam("response_type") String responseType,
 | 
			
		||||
                                              @RequestParam("client_id") String clientId,
 | 
			
		||||
                                              @RequestParam(value = "scope", required = false) String scope,
 | 
			
		||||
@@ -110,7 +169,8 @@ public class OAuth2Controller {
 | 
			
		||||
        // 1.1 校验 responseType 是否满足 code 或者 token 值
 | 
			
		||||
        OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);
 | 
			
		||||
        // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内
 | 
			
		||||
        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri);
 | 
			
		||||
        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null,
 | 
			
		||||
                grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri);
 | 
			
		||||
 | 
			
		||||
        // 2.1 假设 approved 为 null,说明是场景一
 | 
			
		||||
        if (Boolean.TRUE.equals(autoApprove)) {
 | 
			
		||||
@@ -159,7 +219,7 @@ public class OAuth2Controller {
 | 
			
		||||
    private String getAuthorizationCodeRedirect(Long userId, OAuth2ClientDO client,
 | 
			
		||||
                                                List<String> scopes, String redirectUri, String state) {
 | 
			
		||||
        // 1. 创建 code 授权码
 | 
			
		||||
        String authorizationCode = oauth2GrantService.grantAuthorizationCode(userId,getUserType(), client.getClientId(), scopes,
 | 
			
		||||
        String authorizationCode = oauth2GrantService.grantAuthorizationCodeForCode(userId,getUserType(), client.getClientId(), scopes,
 | 
			
		||||
                redirectUri, state);
 | 
			
		||||
        // 2. 拼接重定向的 URL
 | 
			
		||||
        return OAuth2Utils.buildAuthorizationCodeRedirectUri(redirectUri, authorizationCode, state);
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonProperty;
 | 
			
		||||
import io.swagger.annotations.ApiModel;
 | 
			
		||||
import io.swagger.annotations.ApiModelProperty;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
 | 
			
		||||
@ApiModel("管理后台 - 访问令牌 Response VO")
 | 
			
		||||
@Data
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class OAuth2OpenAccessTokenRespVO {
 | 
			
		||||
 | 
			
		||||
    @ApiModelProperty(value = "访问令牌", required = true, example = "tudou")
 | 
			
		||||
    @JsonProperty("access_token")
 | 
			
		||||
    private String accessToken;
 | 
			
		||||
 | 
			
		||||
    @ApiModelProperty(value = "刷新令牌", required = true, example = "nice")
 | 
			
		||||
    @JsonProperty("refresh_token")
 | 
			
		||||
    private String refreshToken;
 | 
			
		||||
 | 
			
		||||
    @ApiModelProperty(value = "令牌类型", required = true, example = "bearer")
 | 
			
		||||
    @JsonProperty("token_type")
 | 
			
		||||
    private String tokenType;
 | 
			
		||||
 | 
			
		||||
    @ApiModelProperty(value = "过期时间", required = true, example = "42430", notes = "单位:秒")
 | 
			
		||||
    @JsonProperty("expires_in")
 | 
			
		||||
    private Long expiresIn;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,24 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.convert.oauth2;
 | 
			
		||||
 | 
			
		||||
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.dal.dataobject.auth.OAuth2AccessTokenDO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils;
 | 
			
		||||
import org.mapstruct.Mapper;
 | 
			
		||||
import org.mapstruct.factory.Mappers;
 | 
			
		||||
 | 
			
		||||
@Mapper
 | 
			
		||||
public interface OAuth2OpenConvert {
 | 
			
		||||
 | 
			
		||||
    OAuth2OpenConvert INSTANCE = Mappers.getMapper(OAuth2OpenConvert.class);
 | 
			
		||||
 | 
			
		||||
    default OAuth2OpenAccessTokenRespVO convert(OAuth2AccessTokenDO bean) {
 | 
			
		||||
        OAuth2OpenAccessTokenRespVO respVO = convert0(bean);
 | 
			
		||||
        respVO.setTokenType(SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase());
 | 
			
		||||
        respVO.setExpiresIn(OAuth2Utils.getExpiresIn(bean.getExpiresTime()));
 | 
			
		||||
        return respVO;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    OAuth2OpenAccessTokenRespVO convert0(OAuth2AccessTokenDO bean);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -35,6 +35,8 @@ public class SecurityConfiguration {
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/tenant/get-id-by-name")).permitAll();
 | 
			
		||||
                // 短信回调 API
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/sms/callback/**")).permitAll();
 | 
			
		||||
                // OAuth2 API
 | 
			
		||||
                registry.antMatchers(buildAdminApi("/system/oauth2/token")).permitAll();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        };
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ public interface OAuth2ClientService {
 | 
			
		||||
     * @return 客户端
 | 
			
		||||
     */
 | 
			
		||||
    default OAuth2ClientDO validOAuthClientFromCache(String clientId) {
 | 
			
		||||
        return validOAuthClientFromCache(clientId, null, null, null);
 | 
			
		||||
        return validOAuthClientFromCache(clientId, null, null, null, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -76,12 +76,13 @@ public interface OAuth2ClientService {
 | 
			
		||||
     * 非空时,进行校验
 | 
			
		||||
     *
 | 
			
		||||
     * @param clientId 客户端编号
 | 
			
		||||
     * @param clientSecret 客户端密钥
 | 
			
		||||
     * @param authorizedGrantType 授权方式
 | 
			
		||||
     * @param scopes 授权范围
 | 
			
		||||
     * @param redirectUri 重定向地址
 | 
			
		||||
     * @return 客户端
 | 
			
		||||
     */
 | 
			
		||||
    OAuth2ClientDO validOAuthClientFromCache(String clientId, String authorizedGrantType,
 | 
			
		||||
                                             Collection<String> scopes, String redirectUri);
 | 
			
		||||
    OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
 | 
			
		||||
                                             String authorizedGrantType, Collection<String> scopes, String redirectUri);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -176,7 +176,8 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public OAuth2ClientDO validOAuthClientFromCache(String clientId, String authorizedGrantType, Collection<String> scopes, String redirectUri) {
 | 
			
		||||
    public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
 | 
			
		||||
                                                    String authorizedGrantType, Collection<String> scopes, String redirectUri) {
 | 
			
		||||
        // 校验客户端存在、且开启
 | 
			
		||||
        OAuth2ClientDO client = clientCache.get(clientId);
 | 
			
		||||
        if (client == null) {
 | 
			
		||||
@@ -186,6 +187,10 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
 | 
			
		||||
            throw exception(OAUTH2_CLIENT_DISABLE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 校验客户端密钥
 | 
			
		||||
        if (StrUtil.isNotEmpty(clientSecret) && ObjectUtil.notEqual(client.getSecret(), clientSecret)) {
 | 
			
		||||
            throw exception(OAUTH2_CLIENT_CLIENT_SECRET_ERROR, clientSecret);
 | 
			
		||||
        }
 | 
			
		||||
        // 校验授权方式
 | 
			
		||||
        if (StrUtil.isNotEmpty(authorizedGrantType) && !CollUtil.contains(client.getAuthorizedGrantTypes(), authorizedGrantType)) {
 | 
			
		||||
            throw exception(OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
 | 
			
		||||
@@ -196,7 +201,7 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
 | 
			
		||||
        }
 | 
			
		||||
        // 校验回调地址
 | 
			
		||||
        if (StrUtil.isNotEmpty(redirectUri) && !StrUtils.startWithAny(redirectUri, client.getRedirectUris())) {
 | 
			
		||||
            throw exception(OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH);
 | 
			
		||||
            throw exception(OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, redirectUri);
 | 
			
		||||
        }
 | 
			
		||||
        return client;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,35 @@ public interface OAuth2GrantService {
 | 
			
		||||
    OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType,
 | 
			
		||||
                                      String clientId, List<String> scopes);
 | 
			
		||||
 | 
			
		||||
    // AuthorizationCodeTokenGranter
 | 
			
		||||
    String grantAuthorizationCode(Long userId, Integer userType,
 | 
			
		||||
                                  String clientId, List<String> scopes,
 | 
			
		||||
                                  String redirectUri, String state);
 | 
			
		||||
    /**
 | 
			
		||||
     * 授权码模式,第一阶段,获得 code 授权码
 | 
			
		||||
     *
 | 
			
		||||
     * 对应 Spring Security OAuth2 的 AuthorizationEndpoint 的 generateCode 方法
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId 用户编号
 | 
			
		||||
     * @param userType 用户类型
 | 
			
		||||
     * @param clientId 客户端编号
 | 
			
		||||
     * @param scopes 授权范围
 | 
			
		||||
     * @param redirectUri 重定向 URI
 | 
			
		||||
     * @param state 状态
 | 
			
		||||
     * @return 授权码
 | 
			
		||||
     */
 | 
			
		||||
    String grantAuthorizationCodeForCode(Long userId, Integer userType,
 | 
			
		||||
                                         String clientId, List<String> scopes,
 | 
			
		||||
                                         String redirectUri, String state);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 授权码模式,第二阶段,获得 accessToken 访问令牌
 | 
			
		||||
     *
 | 
			
		||||
     * 对应 Spring Security OAuth2 的 AuthorizationCodeTokenGranter 功能
 | 
			
		||||
     *
 | 
			
		||||
     * @param clientId 客户端编号
 | 
			
		||||
     * @param code 授权码
 | 
			
		||||
     * @param redirectUri 重定向 URI
 | 
			
		||||
     * @param state 状态
 | 
			
		||||
     * @return 访问令牌
 | 
			
		||||
     */
 | 
			
		||||
    OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code,
 | 
			
		||||
                                                             String redirectUri, String state);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,18 @@
 | 
			
		||||
package cn.iocoder.yudao.module.system.service.oauth2;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2AccessTokenDO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.dal.dataobject.auth.OAuth2CodeDO;
 | 
			
		||||
import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 | 
			
		||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.OAUTH2_GRANT_CODE_NOT_EXISTS;
 | 
			
		||||
import static java.util.Collections.singletonList;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * OAuth2 授予 Service 实现类
 | 
			
		||||
 *
 | 
			
		||||
@@ -24,10 +31,38 @@ public class OAuth2GrantServiceImpl implements OAuth2GrantService {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String grantAuthorizationCode(Long userId, Integer userType,
 | 
			
		||||
                                         String clientId, List<String> scopes,
 | 
			
		||||
                                         String redirectUri, String state) {
 | 
			
		||||
    public String grantAuthorizationCodeForCode(Long userId, Integer userType,
 | 
			
		||||
                                                String clientId, List<String> scopes,
 | 
			
		||||
                                                String redirectUri, String state) {
 | 
			
		||||
        return "test";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code,
 | 
			
		||||
                                                                    String redirectUri, String state) {
 | 
			
		||||
        // TODO 消费 code
 | 
			
		||||
        OAuth2CodeDO codeDO = new OAuth2CodeDO().setClientId("default").setRedirectUri("https://www.iocoder.cn").setState("")
 | 
			
		||||
                .setUserId(1L).setUserType(2).setScopes(singletonList("user_info"));
 | 
			
		||||
        if (codeDO == null) {
 | 
			
		||||
            throw exception(OAUTH2_GRANT_CODE_NOT_EXISTS);
 | 
			
		||||
        }
 | 
			
		||||
        // 校验 clientId 是否匹配
 | 
			
		||||
        if (!StrUtil.equals(clientId, codeDO.getClientId())) {
 | 
			
		||||
            throw exception(ErrorCodeConstants.OAUTH2_GRANT_CLIENT_ID_MISMATCH);
 | 
			
		||||
        }
 | 
			
		||||
        // 校验 redirectUri 是否匹配
 | 
			
		||||
        if (!StrUtil.equals(redirectUri, codeDO.getRedirectUri())) {
 | 
			
		||||
            throw exception(ErrorCodeConstants.OAUTH2_GRANT_REDIRECT_URI_MISMATCH);
 | 
			
		||||
        }
 | 
			
		||||
        // 校验 state 是否匹配
 | 
			
		||||
        state = StrUtil.nullToDefault(state, ""); // 数据库 state 为 null 时,会设置为 "" 空串
 | 
			
		||||
        if (!StrUtil.equals(state, codeDO.getState())) {
 | 
			
		||||
            throw exception(ErrorCodeConstants.OAUTH2_GRANT_STATE_MISMATCH);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 创建访问令牌
 | 
			
		||||
        return oauth2TokenService.createAccessToken(codeDO.getUserId(), codeDO.getUserType(),
 | 
			
		||||
                codeDO.getClientId(), codeDO.getScopes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -50,13 +50,12 @@ public class OAuth2Utils {
 | 
			
		||||
        Map<String, Object> vars = new LinkedHashMap<String, Object>();
 | 
			
		||||
        Map<String, String> keys = new HashMap<String, String>();
 | 
			
		||||
        vars.put("access_token", accessToken);
 | 
			
		||||
        vars.put("token_type", SecurityFrameworkUtils.TOKEN_TYPE.toLowerCase());
 | 
			
		||||
        vars.put("token_type", SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase());
 | 
			
		||||
        if (state != null) {
 | 
			
		||||
            vars.put("state", state);
 | 
			
		||||
        }
 | 
			
		||||
        if (expireTime != null) {
 | 
			
		||||
            long expires_in = (expireTime.getTime() - System.currentTimeMillis()) / 1000;
 | 
			
		||||
            vars.put("expires_in", expires_in);
 | 
			
		||||
            vars.put("expires_in", getExpiresIn(expireTime));
 | 
			
		||||
        }
 | 
			
		||||
        if (CollUtil.isNotEmpty(scopes)) {
 | 
			
		||||
            vars.put("scope", CollUtil.join(scopes, " "));
 | 
			
		||||
@@ -83,4 +82,8 @@ public class OAuth2Utils {
 | 
			
		||||
        return HttpUtils.append(redirectUri, query, null, !responseType.contains("code"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static long getExpiresIn(Date expireTime) {
 | 
			
		||||
        return (expireTime.getTime() - System.currentTimeMillis()) / 1000;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user