【OAuth2】Spring Security OAuth2 授权码模式

背景

由于业务实现中涉及到接入第三方系统(app接入有赞商城等),所以涉及到第三方系统需要获取用户信息(用户手机号、姓名等),为了保证用户信息的安全和接入方式的统一,采用Oauth2四种模式之一的授权码模式。

介绍

在这里插入图片描述

  1. 第三方系统调用我方提供的授权接口(步骤1)
  2. 用户同意授权,后跳转第三方系统(步骤2、3)
  3. 第三方系统获得code,根据code到我方系统获取token(步骤5、6 )
  4. 根据获取token访问受保护的资源(步骤8、9)

实际应用中由于合作商户,所以需要直接返回code,不需要用户手动授权,即静默模式,所以需要扩展框架,使其支持自动授权

扩展

项目使用的是spring-security-oauth2-2.0.15 由于默认情况下,需要用户授权通过才能生成授权码。所以需简要对框架进行扩展

(1)spring-security-oauth2获取code的controller:

RequestMapping(value = "/oauth/authorize")
public ModelAndView authorize(Map<String, Object> model, @RequestParam Map<String, String> parameters,
        SessionStatus sessionStatus, Principal principal) {

    // Pull out the authorization request first, using the OAuth2RequestFactory. All further logic should
    // query off of the authorization request instead of referring back to the parameters map. The contents of the
    // parameters map will be stored without change in the AuthorizationRequest object once it is created.
    AuthorizationRequest authorizationRequest = getOAuth2RequestFactory().createAuthorizationRequest(parameters);

    Set<String> responseTypes = authorizationRequest.getResponseTypes();

    if (!responseTypes.contains("token") && !responseTypes.contains("code")) {
        throw new UnsupportedResponseTypeException("Unsupported response types: " + responseTypes);
    }

    if (authorizationRequest.getClientId() == null) {
        throw new InvalidClientException("A client id must be provided");
    }

    try {

        if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {
            throw new InsufficientAuthenticationException(
                    "User must be authenticated with Spring Security before authorization can be completed.");
        }

        ClientDetails client = getClientDetailsService().loadClientByClientId(authorizationRequest.getClientId());

        // The resolved redirect URI is either the redirect_uri from the parameters or the one from
        // clientDetails. Either way we need to store it on the AuthorizationRequest.
        String redirectUriParameter = authorizationRequest.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);
        String resolvedRedirect = redirectResolver.resolveRedirect(redirectUriParameter, client);
        if (!StringUtils.hasText(resolvedRedirect)) {
            throw new RedirectMismatchException(
                    "A redirectUri must be either supplied or preconfigured in the ClientDetails");
        }
        authorizationRequest.setRedirectUri(resolvedRedirect);

        // We intentionally only validate the parameters requested by the client (ignoring any data that may have
        // been added to the request by the manager).
        oauth2RequestValidator.validateScope(authorizationRequest, client);

        // Some systems may allow for approval decisions to be remembered or approved by default. Check for
        // such logic here, and set the approved flag on the authorization request accordingly.
        authorizationRequest = userApprovalHandler.checkForPreApproval(authorizationRequest,
                (Authentication) principal);
        // TODO: is this call necessary?
        boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);
        authorizationRequest.setApproved(approved);

        // Validation is all done, so we can check for auto approval...
        if (authorizationRequest.isApproved()) {
            if (responseTypes.contains("token")) {
                return getImplicitGrantResponse(authorizationRequest);
            }
            if (responseTypes.contains("code")) {
                return new ModelAndView(getAuthorizationCodeResponse(authorizationRequest,
                        (Authentication) principal));
            }
        }

        // Place auth request into the model so that it is stored in the session
        // for approveOrDeny to use. That way we make sure that auth request comes from the session,
        // so any auth request parameters passed to approveOrDeny will be ignored and retrieved from the session.
        model.put("authorizationRequest", authorizationRequest);

        return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

    }
    catch (RuntimeException e) {
        sessionStatus.setComplete();
        throw e;
    }

}

52行到59行可知,当approved为true的时候会直接返回code码,不会需要用户授权,所以问题变成了如何让扩展的userApprovalHandler生效

(2)扩展的userApprovalHandler生效

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
  endpoints
      .tokenStore(tokenStore)
      .authenticationManager(authenticationManager)
      .userDetailsService(authUserDetailService)
      .authorizationCodeServices(new JdbcAuthorizationCodeServices(dataSource))
      .reuseRefreshTokens(false)
      .userApprovalHandler(new AuthApprovalHandler())
      .exceptionTranslator(customWebResponseExceptionTranslator)
  ;
}

9行:AuthotizationServer中增加配置自定义配置

应用

(1)获取code值

需要APP端定制WebView开发,根据/oauth/authrorize路径参数中增加token

请求路径:

参数说明:

参数名称类型是否必填描述
response_typeString固定值“code”
client_idString第三方配置的client_id
redirect_uriString第三方配置的回调地址
stateString第三方自定义使用

请求示例:

curl -X POST http://localhost:8421/oauth/authorize -H 'Authorization: Bearer b7c2d63f-edff-4790-add9-0b69df7321b5' -d 'response_type=code&client_id=external&redirect_uri=http://www.baidu.com&state=123'

返回结果:重定向redirect_uri路径

(2)获取accessToken(有效期暂定72h)

请求参数:

参数名称类型是否必填描述
client_idString第三方配置的client_id
client_secretString第三方配置的密钥
codeString申请的code
grant_typeString固定值“authorization_code”
redirect_uriString第三方配置的回调地址,必须与生成code时的uri一样

请求示例:

curl -X POST http://localhost:8421/oauth/token -d 'grant_type=authorization_code&client_id=external&client_secret=D524C1A0811DA49592F841085CC0063EB62B3001252A94542795D1CA9824A941&redirect_uri=http://www.baidu.com&code=4TCYkV'

返回结果:

{"access_token":"95b5be18-49a3-44e1-a527-d5da036cfc3f","token_type":"bearer","refresh_token":"b4488c7d-1e8c-4317-a955-1f4bda013a35","expires_in":9891370,"scope":"auth_base"}

(3)获取refreshToken

暂时不支持

(4)访问资源(用户信息)

根据获取到的授权token访问用户资源信息

请求示例:

curl -X POST https://wuxi.test.brightcns.cn/api/v2/user/external/info -H 'Authorization: Bearer e86d752e-8d72-4a33-aa98-8e158ac5b50b'

返回结果:

{"success":true,"msg":"success","code":"SUCCESS","data":{"userId":4738295200051366773,"phone":13916413714,"nickname":"13916413714","avatar":"http://wxcardoss.oss-cn-shanghai.aliyuncs.com/null","realName":null,"extendParam":null}}

本文转载自博客园作者**浮生半日**的Spring Security OAuth2 授权码模式一文。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring SecurityOAuth2支持四种授权模式,其中一种是密码模式(password grant type)。密码模式是指客户端(也就是应用程序)使用用户的用户名和密码直接向OAuth2服务器请求访问令牌(access token)。 密码模式的使用场景比较特殊,因为它需要客户端直接持有用户凭据。通常情况下,OAuth2更多地是用于第三方应用程序访问受保护的资源,而不是直接访问用户的凭据。因此,在使用密码模式时,需要特别注意安全问题。 以下是使用Spring Security实现密码模式的简要步骤: 1. 配置OAuth2客户端信息:在Spring Security配置文件中,使用`ClientDetailsServiceConfigurer`配置客户端信息,包括客户端ID、客户端密码等。 2. 配置OAuth2授权服务器:使用`AuthorizationServerConfigurer`配置OAuth2授权服务器,包括令牌存储、授权类型等。 3. 配置资源服务器:使用`ResourceServerConfigurer`配置资源服务器,包括受保护的资源、访问规则等。 4. 在客户端中请求访问令牌:客户端通过向授权服务器发送POST请求,包括用户名、密码等信息,请求访问令牌。 5. 使用访问令牌访问受保护的资源:客户端使用访问令牌访问受保护的资源。 以上是使用Spring Security实现密码模式的一般步骤,具体实现可能会因为不同的业务需求而有所不同。需要注意的是,密码模式需要客户端直接持有用户凭据,因此需要特别注意安全问题,确保用户的凭据不会被泄露。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值