7. Spring Security 5.1之OAuth 2.0 Client

1. OAuth 2.0 Client

OAuth 2.0客户端功能为OAuth 2.0授权框架中定义的客户端角色提供支持。


Roles
OAuth 定义了四种角色:

  • resource owner
    能够授予对受保护资源的访问权限的实体。当资源所有者是一个人时,它被称为最终用户。
  • resource server
    托管受保护资源的服务器,能够接受
    并使用访问token响应受保护的资源请求。
  • client
    代表服务器创建受保护资源请求的应用程序资源所有者及其授权。 “客户”一词的确如此 并不意味着任何特定的实施特征(例如,应用程序是在服务器,桌面还是其他服务器上执行设备)。
  • authorization server
    服务器成功后向客户端发出访问令牌
    验证资源所有者并获得授权。

授权服务器和资源服务器之间的交互超出了本规范的范围。 授权服务器可以是与资源服务器相同的服务器或单独的实体。单个授权服务器可以发出接受的访问token多个资源服务器。


可以使用以下主要功能:

  • 授权代码授予
  • 客户凭证授权
  • Servlet环境的WebClient扩展(用于创建受保护的资源请求)

HttpSecurity.oauth2Client() 提供了许多用于自定义OAuth 2.0 Client的配置选项。

以下代码显示了oauth2Client() DSL可用的完整配置选项:

@EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Client()
                .clientRegistrationRepository(this.clientRegistrationRepository())
                .authorizedClientRepository(this.authorizedClientRepository())
                .authorizedClientService(this.authorizedClientService())
                .authorizationCodeGrant()
                    .authorizationRequestRepository(this.authorizationRequestRepository())
                    .authorizationRequestResolver(this.authorizationRequestResolver())
                    .accessTokenResponseClient(this.accessTokenResponseClient());
    }
}

以下部分详细介绍了每个可用的配置选项:

  • 1.1 “ClientRegistration”
  • 1.2, “ClientRegistrationRepository”
  • 1.3, “OAuth2AuthorizedClient”
  • 1.4, “OAuth2AuthorizedClientRepository / OAuth2AuthorizedClientService”
  • 1.5, “RegisteredOAuth2AuthorizedClient”
  • 1.6, “AuthorizationRequestRepository”
  • 1.7, “OAuth2AuthorizationRequestResolver”
  • 1.8, “OAuth2AccessTokenResponseClient”

1.1 ClientRegistration

ClientRegistration表示在OAuth 2.0或OpenID Connect 1.0 Provider中注册的客户端。

客户端注册保存信息,例如客户端ID,客户端密钥,授权授权类型,重定向URI,范围,授权URI,令牌URI和其他详细信息。

ClientRegistration及其属性定义如下:

public final class ClientRegistration {
    private String registrationId;  1
    private String clientId;    2
    private String clientSecret;    3
    private ClientAuthenticationMethod clientAuthenticationMethod;  4
    private AuthorizationGrantType authorizationGrantType;  5
    private String redirectUriTemplate; 6
    private Set<String> scopes; 7
    private ProviderDetails providerDetails;
    private String clientName;  8

    public class ProviderDetails {
        private String authorizationUri;    9
        private String tokenUri;    10
        private UserInfoEndpoint userInfoEndpoint;
        private String jwkSetUri;   11
        private Map<String, Object> configurationMetadata;  12

        public class UserInfoEndpoint {
            private String uri; 13
            private AuthenticationMethod authenticationMethod;  14
            private String userNameAttributeName;   15

        }
    }
}
  • 1 registrationId:唯一标识ClientRegistration的ID。

  • 2 clientId:客户端标识符。

  • 3 clientSecret:客户端密码。

  • 4 clientAuthenticationMethod:用于使用Provider对客户端进行身份验证的方法。支持的值是基本和后期。

  • 5.authorizationGrantType:OAuth 2.0授权框架定义了四种授权授权类型。支持的值是authorization_code,implicit和client_credentials。

  • 6 redirectUriTemplate:客户端注册的重定向URI,授权服务器将最终用户的用户代理重定向到最终用户对客户端进行身份验证和授权访问之后。

  • 7范围:客户在授权请求流程中请求的范围,例如openid,电子邮件或配置文件。

  • 8.clientName:用于客户端的描述性名称。该名称可能在某些情况下使用,例如在自动生成的登录页面中显示客户端的名称时。

  • 9 authorizationUri:授权服务器的授权端点URI。

  • 10 tokenUri:授权服务器的令牌端点URI。

  • 11 jwkSetUri:用于从授权服务器检索JSON Web密钥(JWK)集的URI,其包含用于验证ID令牌的JSON Web签名(JWS)以及可选的UserInfo响应的加密密钥。

  • 12 configurationMetadata:OpenID提供程序配置信息。仅当配置了Spring Boot 2.x属性spring.security.oauth2.client.provider。[providerId] .issuerUri时,才能使用此信息。

  • 13(userInfoEndpoint)uri:UserInfo端点URI,用于访问经过身份验证的最终用户的声明/属性。

  • 14(userInfoEndpoint)authenticationMethod:将访问令牌发送到UserInfo端点时使用的身份验证方法。支持的值是标题,表单和查询。

  • 15 userNameAttributeName:UserInfo响应中返回的属性的名称,该属性引用最终用户的名称或标识符。

1.2 ClientRegistrationRepository

clientRegistrationRepository充当OAuth 2.0 / OpenID Connect 1.0 ClientRegistration的存储库。

客户端注册信息最终由关联的授权服务器存储和拥有。 此存储库提供检索主客户端注册信息的子集的功能,该子集与授权服务器一起存储。

Spring Boot 2.x auto-configuration将spring.security.oauth2.client.registration.[registrationId]下的每个属性绑定到ClientRegistration的实例,然后组成ClientRegistrationRepository中的每个ClientRegistration实例。

ClientRegistrationRepository的默认实现是InMemoryClientRegistrationRepository

自动配置还将ClientRegistrationRepository注册为ApplicationContext中的@Bean,以便在应用程序需要时可以依赖注入。

以下清单显示了一个示例:

@Controller
public class OAuth2ClientController {

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @RequestMapping("/")
    public String index() {
        ClientRegistration googleRegistration =
            this.clientRegistrationRepository.findByRegistrationId("google");

        ...

        return "index";
    }
}

1.3 OAuth2AuthorizedClient

OAuth2AuthorizedClient是授权客户端的表示。 当最终用户(资源所有者)已授权客户端访问其受保护资源时,将认为客户端已获得授权。

OAuth2AuthorizedClient用于将OAuth2AccessToken(和可选的OAuth2RefreshToken)与ClientRegistration(客户端)和资源所有者相关联,后者是授予授权的Principal最终用户。

1.4 OAuth2AuthorizedClientRepository / OAuth2AuthorizedClientService

OAuth2AuthorizedClientRepository负责在Web请求之间持久化OAuth2AuthorizedClient。 然而,OAuth2AuthorizedClientService的主要作用是在应用程序级别管理OAuth2AuthorizedClient

从开发人员的角度来看,OAuth2AuthorizedClientRepositoryOAuth2AuthorizedClientService提供了查找与客户端关联的OAuth2AccessToken的功能,以便可以使用它来启动受保护的资源请求。

Spring Boot 2.x自动配置在ApplicationContext中注册OAuth2AuthorizedClientRepository和/或OAuth2AuthorizedClientService @Bean。

开发人员还可以在ApplicationContext中注册OAuth2AuthorizedClientRepository或OAuth2AuthorizedClientService @Bean(覆盖Spring Boot 2.x自动配置),以便能够查找与特定ClientRegistration(客户端)关联的OAuth2AccessToken。

以下清单显示了一个示例:

@Controller
public class OAuth2LoginController {

    @Autowired
    private OAuth2AuthorizedClientService authorizedClientService;

    @RequestMapping("/userinfo")
    public String userinfo(OAuth2AuthenticationToken authentication) {
        // authentication.getAuthorizedClientRegistrationId() returns the
        // registrationId of the Client that was authorized during the oauth2Login() flow
        OAuth2AuthorizedClient authorizedClient =
            this.authorizedClientService.loadAuthorizedClient(
                authentication.getAuthorizedClientRegistrationId(),
                authentication.getName());

        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();

        ...
        return "userinfo";
    }
}

1.5 RegisteredOAuth2AuthorizedClient

@RegisteredOAuth2AuthorizedClient批注提供了将方法参数解析为OAuth2AuthorizedClient类型的参数值的功能。

与通过OAuth2AuthorizedClientService查找OAuth2AuthorizedClient相比,这是一种方便的替代方法。

@Controller
public class OAuth2LoginController {

    @RequestMapping("/userinfo")
    public String userinfo(@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) {
        OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
        ...
        return "userinfo";
    }
}

@RegisteredOAuth2AuthorizedClient注解由OAuth2AuthorizedClientArgumentResolver处理,并提供以下功能:

如果客户端尚未获得授权,将自动请求OAuth2AccessToken

  • 对于authorization_code,这涉及触发授权请求重定向以启动流
  • 对于client_credentials,使用DefaultClientCredentialsTokenResponseClient直接从令牌端点获取访问token。

1.6 AuthorizationRequestRepository

AuthorizationRequestRepository负责从启动授权请求到收到授权响应时(回调)的持久性OAuth2AuthorizationRequest。

OAuth2AuthorizationRequest用于关联和验证授权响应。

AuthorizationRequestRepository的默认实现是HttpSessionOAuth2AuthorizationRequestRepository,它将OAuth2AuthorizationRequest存储在HttpSession中。

如果您想提供在Cookie中存储OAuth2AuthorizationRequest属性的AuthorizationRequestRepository的自定义实现,您可以按以下示例所示进行配置:

@EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Client()
                .authorizationCodeGrant()
                    .authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
                    ...
    }

    private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    }
}

1.7 OAuth2AuthorizationRequestResolver

OAuth2AuthorizationRequestResolver的主要作用是从提供的Web请求中解析OAuth2AuthorizationRequest。 默认实现DefaultOAuth2AuthorizationRequestResolver匹配(默认)路径/ oauth2 / authorization / {registrationId},提取registrationId并使用它为关联的ClientRegistration构建OAuth2AuthorizationRequest。

OAuth2AuthorizationRequestResolver可以实现的主要用例之一是能够使用高于OAuth 2.0授权框架中定义的标准参数的附加参数来自定义授权请求。

例如,OpenID Connect为授权代码流定义了额外的OAuth 2.0请求参数,这些参数扩展自OAuth 2.0授权框架中定义的标准参数。 其中一个扩展参数是prompt参数。

可选的。 空格分隔,区分大小写的ASCII字符串值列表,指定授权服务器是否提示最终用户进行重新认证和同意。
定义的值为:none,login,consent,select_account

以下示例显示如何通过包含请求参数prompt = consent来实现自定义oauth2Login()的授权请求的OAuth2AuthorizationRequestResolver。

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ClientRegistrationRepository clientRegistrationRepository;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .oauth2Login()
                .authorizationEndpoint()
                    .authorizationRequestResolver(
                            new CustomAuthorizationRequestResolver(
                                    this.clientRegistrationRepository));    1
    }
}

public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
    private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver;

    public CustomAuthorizationRequestResolver(
            ClientRegistrationRepository clientRegistrationRepository) {

        this.defaultAuthorizationRequestResolver =
                new DefaultOAuth2AuthorizationRequestResolver(
                        clientRegistrationRepository, "/oauth2/authorization");
    }

    @Override
    public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
        OAuth2AuthorizationRequest authorizationRequest =
                this.defaultAuthorizationRequestResolver.resolve(request);  2

        return authorizationRequest != null ?   3
                customAuthorizationRequest(authorizationRequest) :
                null;
    }

    @Override
    public OAuth2AuthorizationRequest resolve(
            HttpServletRequest request, String clientRegistrationId) {

        OAuth2AuthorizationRequest authorizationRequest =
                this.defaultAuthorizationRequestResolver.resolve(
                    request, clientRegistrationId);    4

        return authorizationRequest != null ?   5
                customAuthorizationRequest(authorizationRequest) :
                null;
    }

    private OAuth2AuthorizationRequest customAuthorizationRequest(
            OAuth2AuthorizationRequest authorizationRequest) {

        Map<String, Object> additionalParameters =
                new LinkedHashMap<>(authorizationRequest.getAdditionalParameters());
        additionalParameters.put("prompt", "consent");  6

        return OAuth2AuthorizationRequest.from(authorizationRequest)    7
                .additionalParameters(additionalParameters) 8
                .build();
    }
}
  • 1配置自定义OAuth2AuthorizationRequestResolver
  • 2 4尝试使用DefaultOAuth2AuthorizationRequestResolver解析OAuth2AuthorizationRequest
  • 3 5如果解析了OAuth2AuthorizationRequest而不是返回自定义版本,则返回null
  • 6将自定义参数添加到现有OAuth2AuthorizationRequest.additionalParameters
  • 7创建默认OAuth2AuthorizationRequest的副本,该副本返回OAuth2AuthorizationRequest.Builder以进行进一步修改
  • 8覆盖默认的additionalParameters

OAuth2AuthorizationRequest.Builder.build()构造OAuth2AuthorizationRequest.authorizationRequestUri,它代表完整的授权请求URI,包括使用application/x-www-form-urlencoded格式的所有查询参数。

上面的示例显示了在标准参数之上添加自定义参数的常见用例。 但是,如果您需要删除或更改标准参数或者您的要求更高级,则可以通过简单地覆盖OAuth2AuthorizationRequest.authorizationRequestUri属性来完全控制构建授权请求URI。

以下示例显示了上一示例中customAuthorizationRequest()方法的变体,而是覆盖了OAuth2AuthorizationRequest.authorizationRequestUri属性。

private OAuth2AuthorizationRequest customAuthorizationRequest(
        OAuth2AuthorizationRequest authorizationRequest) {

    String customAuthorizationRequestUri = UriComponentsBuilder
            .fromUriString(authorizationRequest.getAuthorizationRequestUri())
            .queryParam("prompt", "consent")
            .build(true)
            .toUriString();

    return OAuth2AuthorizationRequest.from(authorizationRequest)
            .authorizationRequestUri(customAuthorizationRequestUri)
            .build();
}

1.8 OAuth2AccessTokenResponseClient

OAuth2AccessTokenResponseClient的主要作用是在授权服务器的令牌端点上为访问令牌凭据交换授权授予凭据。

用于authorization_code授权的OAuth2AccessTokenResponseClient的默认实现是DefaultAuthorizationCodeTokenResponseClient,它使用RestOperations在令牌端点处交换访问令牌的授权代码。

DefaultAuthorizationCodeTokenResponseClient非常灵活,因为它允许您自定义令牌请求的预处理和/或令牌响应的后处理。

如果需要自定义令牌请求的预处理,可以使用自定义Converter <OAuth2AuthorizationCodeGrantRequest,RequestEntity <?>>提供DefaultAuthorizationCodeTokenResponseClient.setRequestEntityConverter()。默认实现OAuth2AuthorizationCodeGrantRequestEntityConverter构建标准OAuth 2.0访问令牌请求的RequestEntity表示。但是,提供自定义转换器将允许您扩展标准令牌请求并添加自定义参数。

重要
自定义转换器必须返回OAuth 2.0访问令牌请求的有效RequestEntity表示,该表示由预期的OAuth 2.0提供程序理解。

另一方面,如果您需要自定义令牌响应的后处理,则需要使用自定义配置的RestOperations提供DefaultAuthorizationCodeTokenResponseClient.setRestOperations()。 默认的RestOperations配置如下:

RestTemplate restTemplate = new RestTemplate(Arrays.asList(
        new FormHttpMessageConverter(),
        new OAuth2AccessTokenResponseHttpMessageConverter()));

restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

发送OAuth 2.0访问令牌请求时,需要使用Spring MVC FormHttpMessageConverter。

OAuth2AccessTokenResponseHttpMessageConverter是OAuth 2.0访问令牌响应的HttpMessageConverter。 您可以使用自定义Converter <Map <String,String>,OAuth2AccessTokenResponse>提供OAuth2AccessTokenResponseHttpMessageConverter.setTokenResponseConverter(),用于将OAuth 2.0访问令牌响应参数转换为OAuth2AccessTokenResponse。

OAuth2ErrorResponseErrorHandler是一个ResponseErrorHandler,可以处理OAuth 2.0错误(400 Bad Request)。 它使用OAuth2ErrorHttpMessageConverter将OAuth 2.0 Error参数转换为OAuth2Error。

无论您是自定义DefaultAuthorizationCodeTokenResponseClient还是提供自己的OAuth2AccessTokenResponseClient实现,您都需要对其进行配置,如以下示例所示:

@EnableWebSecurity
public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Client()
                .authorizationCodeGrant()
                    .accessTokenResponseClient(this.customAccessTokenResponseClient())
                    ...
    }

    private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customAccessTokenResponseClient() {
        ...
    }
}

猜你喜欢

转载自blog.csdn.net/hadues/article/details/89298510