Spring Boot整合Spring Security简记-OAuth2高级配置(十五)

new無语 转载请注明原创出处,谢谢!

Spring Security学习目录

HttpSecurity.oauth2Login()提供了许多用于自定义OAuth 2.0登录的配置选项。主要配置选项在他们的Endpoint()中。

例如,oauth2Login().authorizationEndpoint()允许配置授权端点,而oauth2Login().tokenEndpoint()允许配置令牌端点。

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .authorizationEndpoint()
                    ...
                .redirectionEndpoint()
                    ...
                .tokenEndpoint()
                    ...
                .userInfoEndpoint()
                    ...
    }
}

OAuth 2.0授权框架将协议端点定义如下:
授权过程使用两个授权服务器端点(HTTP资源):

  • Authorization Endpoint(授权端点):客户端通过用户代理重定向从资源所有者获取授权。
  • Token Endpoint(令牌端点):客户端用来为访问令牌交换授权授权,通常使用客户端验证。
    以及一个客户端端点:
  • Redirection Endpoint(重定向端点):由授权服务器用来通过资源所有者用户代理将包含授权凭证的响应返回给客户端。

OpenID Connect Core 1.0规范定义了UserInfo端点,如下所示:

UserInfo端点是一个OAuth 2.0保护资源,用于返回有关经过身份验证的最终用户的信息。为了获得有关最终用户的请求信息,客户端使用通过OpenID Connect Authentication获取的访问令牌向UserInfo端点发出请求。

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .clientRegistrationRepository(this.clientRegistrationRepository())
                .authorizedClientService(this.authorizedClientService())
                .loginPage("/login")
                .authorizationEndpoint()
                    .baseUri(this.authorizationRequestBaseUri())
                    .authorizationRequestRepository(this.authorizationRequestRepository())
                    .and()
                .redirectionEndpoint()
                    .baseUri(this.authorizationResponseBaseUri())
                    .and()
                .tokenEndpoint()
                    .accessTokenResponseClient(this.accessTokenResponseClient())
                    .and()
                .userInfoEndpoint()
                    .userAuthoritiesMapper(this.userAuthoritiesMapper())
                    .userService(this.oauth2UserService())
                    .oidcUserService(this.oidcUserService())
                    .customUserType(GitHubOAuth2User.class, "github");
    }
}

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

OAuth 2.0登录页面

默认情况下,OAuth 2.0登录页面是由DefaultLoginPageGeneratingFilter自动生成的。默认登录页面显示每个已配置的OAuth客户端ClientRegistration.clientName 作为链接,可以启动授权请求(或OAuth 2.0登录)。
每个OAuth客户端的链接目标默认为以下内容:
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI +/ {registrationId}

谷歌

要覆盖默认登录页面,请配置oauth2Login().loginPage()和(可选)oauth2Login().authorizationEndpoint().baseUri()

例子:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .loginPage("/login/oauth2")
                ...
                .authorizationEndpoint()
                    .baseUri("/login/oauth2/authorization")
                    ....
    }
}
  • oauth2Login().loginPage()需要自己实现登陆页面。

Authorization Endpoint(授权端点)

AuthorizationRequestRepository负责保存维护OAuth2AuthorizationRequest

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

AuthorizationRequestRepository 的默认实现是HttpSessionOAuth2AuthorizationRequestRepositoryOAuth2AuthorizationRequestHttpSession中存储。

如果想提供一个自定义实现AuthorizationRequestRepository 存储OAuth2AuthorizationRequestCookie。例子:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

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

    private AuthorizationRequestRepository cookieAuthorizationRequestRepository() {
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    }
}

还需要确保ClientRegistration.redirectUriTemplate与自定义授权响应相匹配baseUri

return CommonOAuth2Provider.GOOGLE.getBuilder("google")
  .clientId("google-client-id")
  .clientSecret("google-client-secret")
  .redirectUriTemplate("{baseUrl}/login/oauth2/callback/{registrationId}")
  .build();

Token Endpoint(令牌端点)

  • OAuth2AccessTokenResponseClient
    OAuth2AccessTokenResponseClient 负责在授权服务器的令牌端点交换访问令牌凭证的授权许可证书。

OAuth2AccessTokenResponseClient 的缺省实现是NimbusAuthorizationCodeTokenResponseClient在令牌端点交换访问令牌的授权码。

如果想提供一个OAuth2AccessTokenResponseClient 使用Spring Framework 5 的自定义实现WebClient来启动对令牌端点的请求,请按照以下示例中所示进行配置:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .tokenEndpoint()
                    .accessTokenResponseClient(this.accessTokenResponseClient())
                    ...
    }

    private OAuth2AccessTokenResponseClient accessTokenResponseClient() {
        return new SpringWebClientAuthorizationCodeTokenResponseClient();
    }
}

UserInfo Endpoint(UserInfo端点)

映射用户权限
用户成功通过OAuth 2.0服务程序进行身份验证后,OAuth2User.getAuthorities()(或OidcUser.getAuthorities())可能会映射到一组新的GrantedAuthority实例,这些实例将OAuth2AuthenticationToken在完成身份验证时提供给该实例。

OAuth2AuthenticationToken.getAuthorities()用于授权请求,如in hasRole('USER')hasRole('ADMIN')

映射用户权限时有几个选项可供选择:

  • 用一个 GrantedAuthoritiesMapper
  • 基于代理的策略 OAuth2UserService

用一个 GrantedAuthoritiesMapper

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .userInfoEndpoint()
                    .userAuthoritiesMapper(this.userAuthoritiesMapper())
                    ...
    }

    private GrantedAuthoritiesMapper userAuthoritiesMapper() {
        return (authorities) -> {
            Set mappedAuthorities = new HashSet<>();

            authorities.forEach(authority -> {
                if (OidcUserAuthority.class.isInstance(authority)) {
                    OidcUserAuthority oidcUserAuthority = (OidcUserAuthority)authority;

                    OidcIdToken idToken = oidcUserAuthority.getIdToken();
                    OidcUserInfo userInfo = oidcUserAuthority.getUserInfo();

                    // Map the claims found in idToken and/or userInfo
                    // to one or more GrantedAuthority's and add it to mappedAuthorities

                } else if (OAuth2UserAuthority.class.isInstance(authority)) {
                    OAuth2UserAuthority oauth2UserAuthority = (OAuth2UserAuthority)authority;

                    Map userAttributes = oauth2UserAuthority.getAttributes();

                    // Map the attributes found in userAttributes
                    // to one or more GrantedAuthority's and add it to mappedAuthorities

                }
            });

            return mappedAuthorities;
        };
    }
}

或者直接注册@Bean自动配置

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.oauth2Login();
    }

    @Bean
    public GrantedAuthoritiesMapper userAuthoritiesMapper() {
        ...
    }
}

基于代理的策略 OAuth2UserService
它允许您访问的OAuth2UserRequestOAuth2User(使用OAuth 2.0用户UserService时)或OidcUserRequestOidcUser(使用 OpenID Connect 1.0 UserService时)。

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .userInfoEndpoint()
                    .oidcUserService(this.oidcUserService())
                    ...
    }

    private OAuth2UserService oidcUserService() {
        final OidcUserService delegate = new OidcUserService();

        return (userRequest) -> {
            // Delegate to the default implementation for loading a user
            OidcUser oidcUser = delegate.loadUser(userRequest);

            OAuth2AccessToken accessToken = userRequest.getAccessToken();
            Set mappedAuthorities = new HashSet<>();

            // TODO
            // 1) Fetch the authority information from the protected resource using accessToken
            // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities

            // 3) Create a copy of oidcUser but use the mappedAuthorities instead
            oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());

            return oidcUser;
        };
    }
}
  • 配置自定义OAuth2User
    CustomUserTypesOAuth2UserServiceOAuth2UserService 为自定义OAuth2User类型提供支持的实现。

如果默认实现(DefaultOAuth2User)不适合你的需要,你可以定义你自己的实现OAuth2User

以下代码演示了如何OAuth2User为GitHub 注册一个自定义类型:

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .userInfoEndpoint()
                    .customUserType(GitHubOAuth2User.class, "github")
                    ...
    }
}

以下代码显示了OAuth2UserGitHub 的自定义类型的示例:

public class GitHubOAuth2User implements OAuth2User {
    private List authorities =
        AuthorityUtils.createAuthorityList("ROLE_USER");
    private Map attributes;
    private String id;
    private String name;
    private String login;
    private String email;

    @Override
    public Collection getAuthorities() {
        return this.authorities;
    }

    @Override
    public Map getAttributes() {
        if (this.attributes == null) {
            this.attributes = new HashMap<>();
            this.attributes.put("id", this.getId());
            this.attributes.put("name", this.getName());
            this.attributes.put("login", this.getLogin());
            this.attributes.put("email", this.getEmail());
        }
        return attributes;
    }

    public String getId() {
        return this.id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLogin() {
        return this.login;
    }

    public void setLogin(String login) {
        this.login = login;
    }

    public String getEmail() {
        return this.email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

idnamelogin,和email是属性在GitHub的的UserInfo响应返回。有关从UserInfo端点返回的详细信息,请参阅“获取已认证的用户”的API文档。

OAuth 2.0 UserService
DefaultOAuth2UserServiceOAuth2UserService 支持标准OAuth 2.0 Provider的实现。

OAuth2UserServiceUserInfo端点(通过在授权流程中使用授予客户端的访问令牌)获取最终用户(资源所有者)的用户属性,并返回一个OAuth2User

如果默认实现不适合,可以定义自己的OAuth2UserService

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Login()
                .userInfoEndpoint()
                    .userService(this.oauth2UserService())
                    ...
    }

    private OAuth2UserService oauth2UserService() {
        return new CustomOAuth2UserService();
    }
}

OpenID Connect 1.0 UserService
OidcUserService是一个OAuth2UserService 支持OpenID Connect 1.0 Provider的实现。

OAuth2UserService负责(通过使用授权流程中授予给客户机的访问令牌)获得从用户信息端点终端用户的用户属性(资源所有者),并返回一个OidcUser

如果默认实现不适合,可以定义自己的OAuth2UserService OpenID Connect 1.0 Provider的实现。

@EnableWebSecurity
public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .oauth2Login()
            .userInfoEndpoint()
                .oidcUserService(this.oidcUserService())
                ...
}

private OAuth2UserService oidcUserService() {
    return new CustomOidcUserService();
}

}

你可能感兴趣的:(Spring Boot整合Spring Security简记-OAuth2高级配置(十五))