6. Java配置

Spring 3.1在Spring Framework中添加了对Java Configuration的一般支持。自Spring Security 3.2以来,Spring Security Java Configuration支持使用户无需使用任何XML即可轻松配置Spring Security。

如果您熟悉第7章安全命名空间配置,那么您应该发现它与安全Java配置支持之间有很多相似之处。

[注意] | Spring Security提供了许多示例应用程序,用于演示Spring Security Java Configuration的使用。

6.1 Hello Web Security Java配置

第一步是创建Spring Security Java配置。该配置创建一个Servlet过滤器,称为springSecurityFilterChain负责应用程序内的所有安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。您可以在下面找到Spring Security Java配置的最基本示例:

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.annotation.*;
  3. import org.springframework.security.config.annotation.authentication.builders.*;
  4. import org.springframework.security.config.annotation.web.configuration.*;
  5. @EnableWebSecurity
  6. public class WebSecurityConfig implements WebMvcConfigurer {
  7. @Bean
  8. public UserDetailsService userDetailsService() throws Exception {
  9. InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
  10. manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
  11. return manager;
  12. }
  13. }

这种配置确实没什么用,但它做了很多。您可以在下面找到以下功能的摘要:

6.1.1 AbstractSecurityWebApplicationInitializer

下一步是注册springSecurityFilterChain战争。这可以在Java配置中使用Spring的WebApplicationInitializer支持在Servlet 3.0+环境中完成。并不令人惊讶的是,Spring Security提供了一个基类AbstractSecurityWebApplicationInitializer,可以确保springSecurityFilterChain为您注册。我们使用的方式AbstractSecurityWebApplicationInitializer取决于我们是否已经使用Spring,或者Spring Security是否是我们应用程序中唯一的Spring组件。

6.1.2没有现有Spring的AbstractSecurityWebApplicationInitializer

如果您不使用Spring或Spring MVC,则需要将其WebSecurityConfig传入超类以确保获取配置。你可以在下面找到一个例子:

  1. import org.springframework.security.web.context.*;
  2. public class SecurityWebApplicationInitializer
  3. extends AbstractSecurityWebApplicationInitializer {
  4. }

SecurityWebApplicationInitializer将做以下事情:

  • 自动为应用程序中的每个URL注册springSecurityFilterChain过滤器
  • 添加一个加载WebSecurityConfig的ContextLoaderListener。

    6.1.3使用Spring MVC的AbstractSecurityWebApplicationInitializer

    如果我们在应用程序的其他地方使用Spring,我们可能已经有了WebApplicationInitializer加载Spring配置的东西。如果我们使用以前的配置,我们会收到错误。相反,我们应该使用现有的注册Spring Security ApplicationContext。例如,如果我们使用Spring MVC,我们SecurityWebApplicationInitializer将看起来如下所示:

  1. import org.springframework.security.web.context.*;
  2. public class SecurityWebApplicationInitializer
  3. extends AbstractSecurityWebApplicationInitializer {
  4. }

这只是为应用程序中的每个URL注册springSecurityFilterChain过滤器。之后,我们将确保WebSecurityConfig在现有的ApplicationInitializer中加载。例如,如果我们使用Spring MVC,它将被添加到getRootConfigClasses()

  1. public class MvcWebApplicationInitializer extends
  2. AbstractAnnotationConfigDispatcherServletInitializer {
  3. @Override
  4. protected Class<?>[] getRootConfigClasses() {
  5. return new Class[] { WebSecurityConfig.class };
  6. }
  7. // ... other overrides ...
  8. }

6.2 HttpSecurity

到目前为止,我们的WebSecurityConfig仅包含有关如何验证用户身份的信息。Spring Security如何知道我们要求所有用户都经过身份验证?Spring Security如何知道我们想要支持基于表单的身份验证?这样做的原因是WebSecurityConfigurerAdapterconfigure(HttpSecurity http)方法中提供了一个默认配置,如下所示:

  1. protected void configure(HttpSecurity http) throws Exception {
  2. http
  3. .authorizeRequests()
  4. .anyRequest().authenticated()
  5. .and()
  6. .formLogin()
  7. .and()
  8. .httpBasic();
  9. }

上面的默认配置:

  • 确保对我们的应用程序的任何请求都要求用户进行身份验证
  • 允许用户使用基于表单的登录进行身份验证
  • 允许用户使用HTTP基本身份验证进行身份验证

您会注意到此配置与XML命名空间配置非常相似:

  1. <http>
  2. <intercept-url pattern="/**" access="authenticated"/>
  3. <form-login />
  4. <http-basic />
  5. </http>

使用and()允许我们继续配置父标记的方法表示关闭XML标记的Java配置等效项。如果您阅读代码,它也是有道理的。我想配置授权请求配置表单登录配置HTTP基本身份验证。

6.3 Java配置和表单登录

当您被提示登录时,您可能想知道登录表单的来源,因为我们没有提及任何HTML文件或JSP。由于Spring Security的默认配置未明确设置登录页面的URL,因此Spring Security会根据启用的功能自动生成一个URL,并使用处理提交的登录的URL的标准值,用户将使用的默认目标URL登录后发送给等等。

虽然自动生成的登录页面便于快速启动和运行,但大多数应用程序都希望提供自己的登录页面。为此,我们可以更新我们的配置,如下所示:

  1. protected void configure(HttpSecurity http) throws Exception {
  2. http
  3. .authorizeRequests()
  4. .anyRequest().authenticated()
  5. .and()
  6. .formLogin()
  7. .loginPage("/login") //1
  8. .permitAll(); //2
  9. }
  • //1更新的配置指定登录页面的位置。
  • //2我们必须授予所有用户(即未经身份验证的用户)访问我们的登录页面的权限。该formLogin().permitAll()方法允许为与基于表单的登录相关联的所有URL授予对所有用户的访问权限

使用JSP实现当前配置的示例登录页面如下所示:

[注意] | 下面的登录页面代表我们当前的配置。如果某些默认设置不符合我们的需求,我们可以轻松更新配置。

  1. <c:url value="/login" var="loginUrl"/>
  2. <form action="${loginUrl}" method="post"> //1
  3. <c:if test="${param.error != null}"> //2
  4. <p>
  5. Invalid username and password.
  6. </p>
  7. </c:if>
  8. <c:if test="${param.logout != null}"> //3
  9. <p>
  10. You have been logged out.
  11. </p>
  12. </c:if>
  13. <p>
  14. <label for="username">Username</label>
  15. <input type="text" id="username" name="username"/> //4
  16. </p>
  17. <p>
  18. <label for="password">Password</label>
  19. <input type="password" id="password" name="password"/> //5
  20. </p>
  21. <input type="hidden" //6
  22. name="${_csrf.parameterName}"
  23. value="${_csrf.token}"/>
  24. <button type="submit" class="btn">Log in</button>
  25. </form>
  • //1/loginURL 的POST 将尝试对用户进行身份验证
  • //2如果查询参数error存在,则尝试进行身份验证并失败

  • //3如果查询参数logout存在,则用户已成功注销

  • //4用户名必须作为名为username的HTTP参数出现

  • //5密码必须作为名为password的HTTP参数出现

6.4授权请求

我们的示例仅要求用户进行身份验证,并且已针对应用程序中的每个URL进行了身份验证。我们可以通过向http.authorizeRequests()方法添加多个子项来指定URL的自定义要求。例如:

  1. protected void configure(HttpSecurity http) throws Exception {
  2. http
  3. .authorizeRequests() // 1
  4. .antMatchers("/resources/**", "/signup", "/about").permitAll() //2
  5. .antMatchers("/admin/**").hasRole("ADMIN") //3
  6. .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") //4
  7. .anyRequest().authenticated() //5
  8. .and()
  9. // ...
  10. .formLogin();
  11. }
1 http.authorizeRequests()方法有多个子节点,每个匹配器按其声明的顺序进行考虑。
2 我们指定了任何用户都可以访问的多种URL模式。具体来说,如果URL以“/ resources /”开头,等于“/ signup”或等于“/ about”,则任何用户都可以访问请求。
3 任何以“/ admin /”开头的URL都将仅限于具有“ROLEADMIN”角色的用户。您会注意到,由于我们正在调用该hasRole方法,因此我们不需要指定“ROLE”前缀。
4 任何以“/ db /”开头的URL都要求用户同时拥有“ROLEADMIN”和“ROLE_DBA”。您会注意到,由于我们使用的是hasRole表达式,因此我们不需要指定“ROLE”前缀。
5 任何尚未匹配的URL只需要对用户进行身份验证

6.5处理注销

使用时WebSecurityConfigurerAdapter,会自动应用注销功能。默认情况下,访问URL /logout将通过以下方式记录用户:

  • 使HTTP会话无效
  • 清理已配置的任何RememberMe身份验证
  • 清除 SecurityContextHolder
  • 重定向到 /login?logout

但是,与配置登录功能类似,您还可以使用各种选项来进一步自定义注销要求:

  1. protected void configure(HttpSecurity http) throws Exception {
  2. http
  3. .logout() // 1
  4. .logoutUrl("/my/logout") // 2
  5. .logoutSuccessUrl("/my/index") // 3
  6. .logoutSuccessHandler(logoutSuccessHandler) // 4
  7. .invalidateHttpSession(true) // 5
  8. .addLogoutHandler(logoutHandler) // 6
  9. .deleteCookies(cookieNamesToClear) // 7
  10. .and()
  11. ...
  12. }
1 提供注销支持。使用时会自动应用此选项WebSecurityConfigurerAdapter
2 触发注销的URL(默认为/logout)。如果启用了CSRF保护(默认),则该请求也必须是POST。有关更多信息,请参阅JavaDoc
3 注销后重定向到的URL。默认是/login?logout。有关更多信息,请参阅JavaDoc
4 我们指定一个自定义LogoutSuccessHandler。如果指定了,logoutSuccessUrl()则忽略。有关更多信息,请参阅JavaDoc
5 指定HttpSession在注销时是否使其无效。默认情况下这是真的。配置SecurityContextLogoutHandler封面。有关更多信息,请参阅JavaDoc
6 添加一个LogoutHandler。 默认情况下SecurityContextLogoutHandler添加为最后一个LogoutHandler
7 允许指定在注销成功时删除的cookie的名称。这是CookieClearingLogoutHandler显式添加的快捷方式。

[注意] | ===当然也可以使用XML Namespace表示法配置注销。有关更多详细信息,请参阅Spring Security XML Namespace部分中logout元素的文档。===

通常,为了自定义注销功能,您可以添加 LogoutHandler 和/或 LogoutSuccessHandler 实现。对于许多常见场景,使用流畅的API时,这些处理程序将在幕后应用。

6.5.1 LogoutHandler

通常,LogoutHandler 实现指示能够参与注销处理的类。预计将调用它们以进行必要的清理。因此,他们不应该抛出异常。提供了各种实现:

有关详细信息请参见第10.5.4节“记住我的接口和实现”

而不是LogoutHandler直接提供实现,流畅的API还提供了快捷方式,提供了各自的LogoutHandler实现。例如,deleteCookies()允许指定在注销成功时要删除的一个或多个cookie的名称。与添加a相比,这是一个捷径CookieClearingLogoutHandler

6.5.2 LogoutSuccessHandler

LogoutSuccessHandler被成功注销后调用LogoutFilter,来处理如重定向或转发到相应的目的地。请注意,界面几乎与该界面相同,LogoutHandler但可能引发异常。

提供以下实现:

如上所述,您无需SimpleUrlLogoutSuccessHandler直接指定。相反,流畅的API通过设置提供快捷方式logoutSuccessUrl()。这将设置SimpleUrlLogoutSuccessHandler封底。发生注销后,提供的URL将重定向到。默认是/login?logout

HttpStatusReturningLogoutSuccessHandler可以在REST API类型场景有趣。成功注销后,LogoutSuccessHandler不允许重定向到URL,而是允许您提供要返回的纯HTTP状态代码。如果未配置,则默认情况下将返回状态代码200。

6.5.3进一步注销相关参考

6.6 OAuth 2.0客户端

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

可以使用以下主要功能:

HttpSecurity.oauth2Client()提供了许多用于自定义OAuth 2.0 Client的配置选项。以下代码显示了可用于oauth2Client()DSL 的完整配置选项:

  1. @EnableWebSecurity
  2. public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http
  6. .oauth2Client()
  7. .clientRegistrationRepository(this.clientRegistrationRepository())
  8. .authorizedClientRepository(this.authorizedClientRepository())
  9. .authorizedClientService(this.authorizedClientService())
  10. .authorizationCodeGrant()
  11. .authorizationRequestRepository(this.authorizationRequestRepository())
  12. .authorizationRequestResolver(this.authorizationRequestResolver())
  13. .accessTokenResponseClient(this.accessTokenResponseClient());
  14. }
  15. }

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

6.6.1 ClientRegistration

ClientRegistration 表示在OAuth 2.0或OpenID Connect 1.0提供程序中注册的客户端。

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

ClientRegistration 其属性定义如下:

  1. public final class ClientRegistration {
  2. private String registrationId; //1
  3. private String clientId; //2
  4. private String clientSecret; // 3
  5. private ClientAuthenticationMethod clientAuthenticationMethod; //4
  6. private AuthorizationGrantType authorizationGrantType; // 5
  7. private String redirectUriTemplate;// 6
  8. private Set<String> scopes; //7
  9. private ProviderDetails providerDetails;
  10. private String clientName; // 8
  11. public class ProviderDetails {
  12. private String authorizationUri; // 9
  13. private String tokenUri; // 10
  14. private UserInfoEndpoint userInfoEndpoint;
  15. private String jwkSetUri; // 11
  16. private Map<String, Object> configurationMetadata; // 12
  17. public class UserInfoEndpoint {
  18. private String uri;// 13
  19. private AuthenticationMethod authenticationMethod;// 14
  20. private String userNameAttributeName; // 15
  21. }
  22. }
  23. }
1 registrationId:唯一标识的ID ClientRegistration
2 clientId:客户端标识符。
3 clientSecret:客户的秘密。
4 clientAuthenticationMethod:用于使用Provider对客户端进行身份验证的方法。支持的值是基本后期
5 authorizationGrantType:OAuth 2.0授权框架定义了四种授权授权类型。支持的值是authorization_code,implicit和client_credentials。
6 redirectUriTemplate:客户端的注册重定向URI,授权服务器将最终用户的用户代理重定向到最终用户对客户端进行身份验证和授权访问之后。
7 scopes:客户端在授权请求流程中请求的范围,例如openid,电子邮件或配置文件。
8 clientName:用于客户端的描述性名称。该名称可能在某些情况下使用,例如在自动生成的登录页面中显示客户端的名称时。
9 authorizationUri:授权服务器的授权端点URI。
10 tokenUri:授权服务器的令牌端点URI。
11 jwkSetUri:用于从授权服务器检索JSON Web密钥(JWK)集的URI ,其包含用于验证ID令牌的JSON Web签名(JWS)以及可选的UserInfo响应的加密密钥。
12 configurationMetadataOpenID提供程序配置信息。仅当spring.security.oauth2.client.provider.[providerId].issuerUri配置了Spring Boot 2.x属性时,才能使用此信息。
13 (userInfoEndpoint)uri:UserInfo端点URI,用于访问经过身份验证的最终用户的声明/属性。
14 (userInfoEndpoint)authenticationMethod:将访问令牌发送到UserInfo端点时使用的身份验证方法。支持的值是标题表单查询
15 userNameAttributeName:UserInfo响应中返回的属性的名称,该属性引用最终用户的名称或标识符。

6.6.2 ClientRegistrationRepository

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

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

Spring Boot 2.x自动配置将每个属性绑定到一个实例,然后组成一个实例中的每个实例。spring.security.oauth2.client.registration.*[registrationId]*``ClientRegistration``ClientRegistration``ClientRegistrationRepository

[注意] | 默认实现ClientRegistrationRepositoryInMemoryClientRegistrationRepository

如果应用程序需要,自动配置还会将其注册ClientRegistrationRepository为a @BeanApplicationContext以便可用于依赖注入。

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

  1. @Controller
  2. public class OAuth2ClientController {
  3. @Autowired
  4. private ClientRegistrationRepository clientRegistrationRepository;
  5. @RequestMapping("/")
  6. public String index() {
  7. ClientRegistration googleRegistration =
  8. this.clientRegistrationRepository.findByRegistrationId("google");
  9. ...
  10. return "index";
  11. }
  12. }

6.6.3 OAuth2AuthorizedClient

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

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

6.6.4 OAuth2AuthorizedClientRepository / OAuth2AuthorizedClientService

OAuth2AuthorizedClientRepository负责OAuth2AuthorizedClient在Web请求之间保持持久性。然而,主要作用OAuth2AuthorizedClientServiceOAuth2AuthorizedClient在应用程序级别进行管理。

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

[注意]| Spring Boot2.X自动配置寄存器的OAuth2AuthorizedClientRepository和/或OAuth2AuthorizedClientService @BeanApplicationContext

开发者还可注册一个OAuth2AuthorizedClientRepositoryOAuth2AuthorizedClientService @BeanApplicationContext(覆盖弹簧引导2.x的自动配置)以便具有查找一个的能力OAuth2AccessToken与特定关联ClientRegistration(客户端)。

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

  1. @Controller
  2. public class OAuth2LoginController {
  3. @Autowired
  4. private OAuth2AuthorizedClientService authorizedClientService;
  5. @RequestMapping("/userinfo")
  6. public String userinfo(OAuth2AuthenticationToken authentication) {
  7. // authentication.getAuthorizedClientRegistrationId() returns the
  8. // registrationId of the Client that was authorized during the oauth2Login() flow
  9. OAuth2AuthorizedClient authorizedClient =
  10. this.authorizedClientService.loadAuthorizedClient(
  11. authentication.getAuthorizedClientRegistrationId(),
  12. authentication.getName());
  13. OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
  14. ...
  15. return "userinfo";
  16. }
  17. }

6.6.5 RegisteredOAuth2AuthorizedClient

所述@RegisteredOAuth2AuthorizedClient注释提供解决方法参数,以类型的参数值的能力OAuth2AuthorizedClient。与通过查找OAuth2AuthorizedClient通道相比,这是一种方便的替代方案OAuth2AuthorizedClientService

  1. @Controller
  2. public class OAuth2LoginController {
  3. @RequestMapping("/userinfo")
  4. public String userinfo(@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) {
  5. OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
  6. ...
  7. return "userinfo";
  8. }
  9. }

@RegisteredOAuth2AuthorizedClient注释被处理OAuth2AuthorizedClientArgumentResolver,并提供以下功能:

  • 一个OAuth2AccessToken如果客户尚未授权将自动请求。
    • 因为authorization_code,这涉及触发授权请求重定向以启动流程
    • 因为client_credentials,使用令牌端点直接获取访问令牌DefaultClientCredentialsTokenResponseClient

6.6.6 AuthorizationRequestRepository

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

[小费] | 将OAuth2AuthorizationRequest被用来关联和验证授权响应。

默认实现AuthorizationRequestRepositoryHttpSessionOAuth2AuthorizationRequestRepository,它存储OAuth2AuthorizationRequestHttpSession

如果你想提供一个自定义实现AuthorizationRequestRepository存储的属性OAuth2AuthorizationRequestCookie,你可以配置它,如下面的例子:

  1. @EnableWebSecurity
  2. public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http
  6. .oauth2Client()
  7. .authorizationCodeGrant()
  8. .authorizationRequestRepository(this.cookieAuthorizationRequestRepository())
  9. ...
  10. }
  11. private AuthorizationRequestRepository<OAuth2AuthorizationRequest> cookieAuthorizationRequestRepository() {
  12. return new HttpCookieOAuth2AuthorizationRequestRepository();
  13. }
  14. }

6.6.7 OAuth2AuthorizationRequestResolver

该主要角色OAuth2AuthorizationRequestResolverOAuth2AuthorizationRequest从提供的Web请求中解析一个。默认实现DefaultOAuth2AuthorizationRequestResolver匹配(默认)路径/oauth2/authorization/{registrationId}提取registrationId并使用它来构建OAuth2AuthorizationRequest关联的路径ClientRegistration

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

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

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

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

  1. @EnableWebSecurity
  2. public class OAuth2LoginSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Autowired
  4. private ClientRegistrationRepository clientRegistrationRepository;
  5. @Override
  6. protected void configure(HttpSecurity http) throws Exception {
  7. http
  8. .authorizeRequests()
  9. .anyRequest().authenticated()
  10. .and()
  11. .oauth2Login()
  12. .authorizationEndpoint()
  13. .authorizationRequestResolver(
  14. new CustomAuthorizationRequestResolver(
  15. this.clientRegistrationRepository)); //1
  16. }
  17. }
  18. public class CustomAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
  19. private final OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver;
  20. public CustomAuthorizationRequestResolver(
  21. ClientRegistrationRepository clientRegistrationRepository) {
  22. this.defaultAuthorizationRequestResolver =
  23. new DefaultOAuth2AuthorizationRequestResolver(
  24. clientRegistrationRepository, "/oauth2/authorization");
  25. }
  26. @Override
  27. public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
  28. OAuth2AuthorizationRequest authorizationRequest =
  29. this.defaultAuthorizationRequestResolver.resolve(request); // 2
  30. return authorizationRequest != null ? // 3
  31. customAuthorizationRequest(authorizationRequest) :
  32. null;
  33. }
  34. @Override
  35. public OAuth2AuthorizationRequest resolve(
  36. HttpServletRequest request, String clientRegistrationId) {
  37. OAuth2AuthorizationRequest authorizationRequest =
  38. this.defaultAuthorizationRequestResolver.resolve(
  39. request, clientRegistrationId); // 4
  40. return authorizationRequest != null ? // 5
  41. customAuthorizationRequest(authorizationRequest) :
  42. null;
  43. }
  44. private OAuth2AuthorizationRequest customAuthorizationRequest(
  45. OAuth2AuthorizationRequest authorizationRequest) {
  46. Map<String, Object> additionalParameters =
  47. new LinkedHashMap<>(authorizationRequest.getAdditionalParameters());
  48. additionalParameters.put("prompt", "consent"); // 6
  49. return OAuth2AuthorizationRequest.from(authorizationRequest) // 7
  50. .additionalParameters(additionalParameters)// 8
  51. .build();
  52. }
  53. }
1 配置自定义 OAuth2AuthorizationRequestResolver
2 4 尝试解决OAuth2AuthorizationRequest使用问题DefaultOAuth2AuthorizationRequestResolver
3 五 如果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属性。

  1. private OAuth2AuthorizationRequest customAuthorizationRequest(
  2. OAuth2AuthorizationRequest authorizationRequest) {
  3. String customAuthorizationRequestUri = UriComponentsBuilder
  4. .fromUriString(authorizationRequest.getAuthorizationRequestUri())
  5. .queryParam("prompt", "consent")
  6. .build(true)
  7. .toUriString();
  8. return OAuth2AuthorizationRequest.from(authorizationRequest)
  9. .authorizationRequestUri(customAuthorizationRequestUri)
  10. .build();
  11. }

6.6.8 OAuth2AccessTokenResponseClient

主要角色OAuth2AccessTokenResponseClient是在授权服务器的令牌端点处为访问令牌凭证交换授权授予凭证。

默认实现OAuth2AccessTokenResponseClientauthorization_code补助DefaultAuthorizationCodeTokenResponseClient,它采用RestOperations了在令牌端点访问令牌交换一个授权码。

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

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

[重要] | 重要 | 自定义Converter必须返回RequestEntity预期OAuth 2.0提供程序可以理解的OAuth 2.0访问令牌请求的有效表示。

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

  1. RestTemplate restTemplate = new RestTemplate(Arrays.asList(
  2. new FormHttpMessageConverter(),
  3. new OAuth2AccessTokenResponseHttpMessageConverter()));
  4. restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

[小费] | FormHttpMessageConverter在发送OAuth 2.0访问令牌请求时使用Spring MVC 是必需的。

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

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

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

  1. @EnableWebSecurity
  2. public class OAuth2ClientSecurityConfig extends WebSecurityConfigurerAdapter {
  3. @Override
  4. protected void configure(HttpSecurity http) throws Exception {
  5. http
  6. .oauth2Client()
  7. .authorizationCodeGrant()
  8. .accessTokenResponseClient(this.customAccessTokenResponseClient())
  9. ...
  10. }
  11. private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customAccessTokenResponseClient() {
  12. ...
  13. }
  14. }

6.7 OAuth 2.0登录

OAuth 2.0登录功能为应用程序提供了使用OAuth 2.0提供程序(例如GitHub)或OpenID Connect 1.0提供程序(例如Google)上的现有帐户登录应用程序的功能。OAuth 2.0 Login实现了用例:“使用Google登录”或“使用GitHub登录”。

[注意] | OAuth 2.0登录是使用授权代码授予实现的,如OAuth 2.0授权框架OpenID Connect Core 1.0中所指定

6.7.1 Spring Boot 2.x示例

Spring Boot 2.x为OAuth 2.0登录带来了完整的自动配置功能。

本部分介绍如何使用Google作为身份验证提供程序配置OAuth 2.0登录示例,并介绍以下主题:

初始设置

要使用Google的OAuth 2.0身份验证系统进行登录,您必须在Google API控制台中设置项目以获取OAuth 2.0凭据。

[注意] | Google的OAuth 2.0身份验证实施符合OpenID Connect 1.0规范,并通过OpenID认证

按照OpenID Connect页面上的说明操作,从“设置OAuth 2.0”部分开始。

完成“获取OAuth 2.0凭据”说明后,您应该拥有一个新的OAuth客户端,其凭据包含客户端ID和客户端密钥。

设置重定向URI

重定向URI是应用程序中的路径,最终用户的用户代理在通过Google进行身份验证并在“同意”页面上授予了对OAuth客户端(在上一步中创建)的访问权限后重定向回的路径。

在“设置重定向URI”子部分中,确保将“ 授权重定向URI”字段设置为http://localhost:8080/login/oauth2/code/google

[小费]| 默认的重定向URI模板是{baseUrl}/login/oauth2/code/{registrationId}。该registrationId是用于唯一标识符ClientRegistration

配置application.yml

既然您有一个新的OAuth客户端与Google,您需要配置应用程序以使用OAuth客户端进行身份验证流程。为此:

  1. 转到application.yml并设置以下配置:
  1. spring:
  2. security:
  3. oauth2:
  4. client:
  5. registration: // 1
  6. google: // 2
  7. client-id: google-client-id
  8. client-secret: google-client-secret

例6.1。OAuth客户端属性

1 spring.security.oauth2.client.registration 是OAuth客户端属性的基本属性前缀。
2 基本属性前缀后面是ClientRegistration的ID ,例如google。
  1. 使用您之前创建的OAuth 2.0凭据替换client-idand client-secret属性中的值。

启动应用程序

启动Spring Boot 2.x示例并转到http://localhost:8080。然后,您将被重定向到默认的自动生成的登录页面,该页面显示Google的链接。

点击Google链接,然后您将重定向到Google进行身份验证。

使用您的Google帐户凭据进行身份验证后,显示给您的下一页是“同意”屏幕。“同意”屏幕会要求您允许或拒绝访问您之前创建的OAuth客户端。单击“ 允许”以授权OAuth客户端访问您的电子邮件地址和基本配置文件信息。

此时,OAuth客户端从UserInfo端点检索您的电子邮件地址和基本配置文件信息,并建立经过身份验证的会话。

6.7.2 Spring Boot 2.x属性映射

下表概述了Spring Boot 2.x OAuth客户端属性到ClientRegistration属性的映射。

Spring Boot 2.x ClientRegistration
spring.security.oauth2.client.registration.*[registrationId]* registrationId
spring.security.oauth2.client.registration.*[registrationId]*.client-id clientId
spring.security.oauth2.client.registration.*[registrationId]*.client-secret clientSecret
spring.security.oauth2.client.registration.*[registrationId]*.client-authentication-method clientAuthenticationMethod
spring.security.oauth2.client.registration.*[registrationId]*.authorization-grant-type authorizationGrantType
spring.security.oauth2.client.registration.*[registrationId]*.redirect-uri redirectUriTemplate
spring.security.oauth2.client.registration.*[registrationId]*.scope scopes
spring.security.oauth2.client.registration.*[registrationId]*.client-name clientName
spring.security.oauth2.client.provider.*[providerId]*.authorization-uri providerDetails.authorizationUri
spring.security.oauth2.client.provider.*[providerId]*.token-uri providerDetails.tokenUri
spring.security.oauth2.client.provider.*[providerId]*.jwk-set-uri providerDetails.jwkSetUri
spring.security.oauth2.client.provider.*[providerId]*.user-info-uri providerDetails.userInfoEndpoint.uri
spring.security.oauth2.client.provider.*[providerId]*.user-info-authentication-method providerDetails.userInfoEndpoint.authenticationMethod
spring.security.oauth2.client.provider.*[providerId]*.userNameAttribute providerDetails.userInfoEndpoint.userNameAttributeName

6.7.3 CommonOAuth2Provider

CommonOAuth2Provider 为许多知名提供商预定义一组默认客户端属性:Google,GitHub,Facebook和Okta。

例如,authorization-uritoken-uri,和user-info-uri不经常对供应商变更。因此,提供默认值以减少所需配置是有意义的。

如前所述,当我们配置Google客户端时,只需要client-idclient-secret属性。

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

  1. spring
  2. security
  3. oauth2
  4. client
  5. registration
  6. google
  7. client-idgoogle-client-id
  8. client-secretgoogle-client-secret

[小费] | 客户端属性的自动默认无缝地在这里工作,因为registrationIdgoogle)匹配GOOGLE enum(不区分大小写)in CommonOAuth2Provider

对于您可能希望指定其他内容的情况registrationId,例如google-login,您仍然可以通过配置属性来利用客户端属性的自动默认provider

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

  1. spring:
  2. security:
  3. oauth2:
  4. client:
  5. registration:
  6. google-login: //1
  7. provider: google // 2
  8. client-id: google-client-id
  9. client-secret: google-client-secret
1 registrationId设置为google-login
2 provider属性设置为google,将利用设置的客户端属性的自动默认值CommonOAuth2Provider.GOOGLE.getBuilder()