OAuth2 Boot(一、授权服务器)(官方文档翻译)
如果您的项目已经引入spring-security-oauth2
,则可以利用一些自动配置来简化设置授权和资源服务器。有关完整的详细信息,请参阅《 Spring Security OAuth 2开发人员指南》。
以下项目处于维护阶段:
- spring-security-oauth2
- spring-security-oauth2-autoconfigure
当然,欢迎您使用它们,我们将为您提供帮助!
但是,在选择
spring-security-oauth2
和spring-security-oauth2-autoconfigure
之前,您应该查看Spring Security功能列表,以查看新的 first-class support是否满足您的需求。该项目是Spring Boot 1.x附带的Spring Security OAuth支持的端口。在Spring Boot 2.x中删除了支持,以支持Spring Security 5’s first-class OAuth support。
为了简化迁移,该项目作为旧版Spring Security OAuth支持与Spring Boot 2.x之间的桥梁而存在。
1.授权服务器
Spring Security OAuth2引导简化了OAuth 2.0授权服务器的安装。
1.1 我需要开启自己的授权服务吗?
在以下情况下,您需要开启自己的授权服务:
- 您希望将登录,注销和密码重置的操作委派给您要管理的独立服务(也称为identity federation)。
- 您想使用OAuth 2.0协议将此单独的服务与其他服务配合使用
1.2 依赖
要使用此库中的自动配置功能,您需要spring-security-oauth2
,它具有原生OAuth 2.0和spring-security-oauth2-autoconfigure
。请注意,您需要为spring-security-oauth2-autoconfigure
指定版本,因为它不再由Spring Boot管理,尽管它仍然应该与Boot的版本匹配。
对于JWT支持,您还需要spring-security-jwt
。
1.3 OAuth2最小配置引导
创建最小的Spring Boot授权服务器包括三个基本步骤:
- 引入依赖
- 加上@EnableAuthorizationServer注解
- 指定至少一个客户端client ID和client-secret。
1.3.1 启动授权服务器
与其他Spring Boot @Enable注解类似,您可以将@EnableAuthorizationServer注解添加到包含main方法的类中,如以下示例所示:
@EnableAuthorizationServer
@SpringBootApplication
public class SimpleAuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleAuthorizationServerApplication, args);
}
}
添加此注解会导入其他Spring配置文件,这些文件会添加许多合理的默认项,例如应该如何对令牌进行签名,令牌的持续时间以及允许的授权范围。
1.3.2 指定一个Client和Secret
根据规范,许多OAuth 2.0端点都需要客户端身份验证,因此您需要至少指定一个客户端,以便任何人都可以与您的授权服务器进行通信。
security:
oauth2:
client:
client-id: first-client
client-secret: noonewilleverguess
尽管很方便,但是这导致了许多在生产中不太可行的情况。您可能需要做的比这更多。
您如何处理它?接下来我们将介绍。
1.3.3 检索令牌
OAuth 2.0本质上是一个框架,用于指定将long-lived令牌交换为short-lived令牌的策略。
默认情况下,@EnableAuthorizationServer
授予客户端访问客户端凭据的权限,这意味着您可以执行以下操作:
curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=client_credentials -dscope=any
获取到的内容如下:
{
"access_token" : "f05a1ea7-4c80-4583-a123-dc7a99415588",
"token_type" : "bearer",
"expires_in" : 43173,
"scope" : "any"
}
可以将该令牌提供给支持不透明OAuth 2.0令牌的任何资源服务器,并将其授权指向配置为此授权服务器进行验证。
从这里,您可以跳至:
- 如何关闭OAuth2引导程序的自动配置
- 如何使授权码授予流程起作用
- 如何使密码授予流程起作用
- 如何以及何时为授权服务器提供AuthenticationManager
- 授权服务器与Spring Security 5.1资源服务器和客户端兼容吗?
- 如何配置Jwt令牌
1.4 如何关闭OAuth2引导程序的自动配置
一般地,OAuth2 Boot项目会创建一个AuthorizationServerConfigurer
实例,并带有一些合理的默认值:
- 它注册一个
NoOpPasswordEncoder
(覆盖Spring Security的默认项) - 它使您提供的客户端可以使用该服务器支持的任何授权类型:
authorization_code
,password
,client_credentials
,隐式
或refresh_token
。
另外,它也会尝试装配一些bean(如果已定义)-即:
-
AuthenticationManager
:用于查找最终用户(不是客户端) -
TokenStore
:用于生成和检索令牌 -
AccessTokenConverter
:用于将访问令牌转换为不同的格式,例如JWT。
尽管本文档介绍了每种Bean的功能,但Spring Security OAuth文档是阅读其原始功能的更好位置
如果您配置了一个AuthorizationServerConfigurer
类型的bean,则不会自动完成任何操作.
例如,如果您需要配置多个客户端,更改其允许的授予类型或使用比NoOpPasswordEncoder
更好的方式(强烈建议!),如果您想要配置自己的AuthorizationServerConfigurer
,参考如下示例:
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired DataSource dataSource;
protected void configure(ClientDetailsServiceConfigurer clients) {
clients
.jdbc(this.dataSource)
.passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
}
前面的配置使OAuth2 Boot不再从环境属性中检索客户端,现在回退到Spring Security密码授权的默认方式。
了解更多相关文档:
1.5 如何使授权码授权模式起作用
使用默认配置,虽然从技术上允许使用授权码模式,但尚未完全配置它。这是因为,除了预先配置的内容外,授权代码流还需要:
- 终端用户
- 终端用户登录流程
- 向客户端注册的重定向URI
1.5.1 添加终端用户
在使用Spring Security的传统Spring Boot应用程序中,用户由UserDetailsService
定义。在这方面,授权服务器没有什么不同,如以下示例所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("enduser")
.password("password")
.roles("USER")
.build());
}
}
请注意,就像传统的Spring Security Web应用程序一样,用户是在WebSecurityConfigurerAdapter实例中定义的。
1.5.2 添加最终用户登录流程
顺便说一句,现在添加WebSecurityConfigurerAdapter
实例就是为最终用户添加表单登录流程所需要的。然而,请注意,这是Web程序本身配置(而不是OAuth 2.0 API的)。
如果您要自定义登录页面,为用户提供的不仅仅是表单登录,还要添加其他支持(如密码重置),WebSecurityConfigurerAdapter
会选择它
1.5.3 向客户端注册的重定向URI
OAuth2 Boot不支持将重定向URI配置为属性(例如,与client-id和client-secret一起使用)。
要添加重定向URI,您需要使用InMemoryClientDetailsService
或JdbcClientDetailsService
指定客户端。
进行以下任一操作都意味着用您自己的OAuth2 Boot提供的AuthorizationServerConfigurer
替换,如以下示例所示:
@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
protected void configure(ClientDetailsServiceConfigurer clients) {
clients
.inMemory()
.withClient("first-client")
.secret(passwordEncoder().encode("noonewilleverguess"))
.scopes("resource:read")
.authorizedGrantTypes("authorization_code")
.redirectUris("http://localhost:8081/oauth/login/client-app");
}
}
1.5.4. 测试授权码流程
测试OAuth可能很棘手,因为它需要多个服务器来查看完整的操作流程。但是,第一步很简单:
- 访问
http://localhost:8080/oauth/authorize?grant_type=authorization_code&response_type = code&client_id=first-client&state=1234
- 如果用户未登录,则该应用程序将重定向到登录页面,
http://localhost:8080/login
- 用户登录后,应用程序将生成code并重定向到已注册的重定向URI。在本例中,该地址为
http:// localhost:8081/oauth/login/client-app
此时,可以通过启动配置为不透明令牌配置并指向此授权服务器实例的任何资源服务器来继续该流程。
1.6. 如何使密码授权模式起作用
使用默认配置,虽然从技术上讲可能存在密码授权模式,但它与授权码一样,都缺少用户。
就是说,由于默认配置会创建一个具有用户名user和随机生成的密码的用户,因此,可以检查带有该密码的日志并执行以下操作:
curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=password -dscope=any -dusername=user -dpassword=the-password-from-the-logs
当您运行该命令时,您应会该获得一个令牌。
但是,您更有可能要指定一组用户。
如1.5 如何使授权码授权流程起作用中所述,在Spring Security
中,通常在UserDetailsService
中指定用户,并且此应用也一样,如以下示例所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("enduser")
.password("password")
.roles("USER")
.build());
}
}
这就是我们需要做的。我们不需要重写AuthorizationServerConfigurer
,因为客户端ID和密码被指定为环境属性。
因此,以下内容现在应该起作用:
curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=password -dscope=any -dusername=enduser -dpassword=password
1.7 如何以及何时为授权服务器提供AuthenticationManager
这是一个非常常见的问题,当AuthorizationServerEndpointsConfigurer
需要指定AuthenticationManager
实例时,不是很直观。简短的答案是:仅在使用资源所有者密码模式时。
记住一些基本知识会有所帮助:
-
AuthenticationManager
是用于认证用户的抽象。它通常需要指定某种UserDetailsService
才能完成。 - 最终用户在
WebSecurityConfigurerAdapter
中指定。 - 默认情况下,OAuth2 Boot程序会自动选择任何公开的
AuthenticationManager
。
但是,并非所有模式都需要AuthenticationManager
,因为并非所有模式都涉及最终用户。例如,“客户凭证”模式仅基于客户的权限而不是最终用户的权限来请求令牌。并且“刷新令牌”模式仅基于刷新令牌的权限来请求令牌。
另外,并非所有模式都明确要求OAuth 2.0 API本身也具有AuthenticationManager
。例如,授权码和隐式模式在用户登录(应用程序流程)时而不是在请求令牌(OAuth 2.0 API)时验证用户。
仅资源所有者密码模式返回基于最终用户凭据的code。这意味着,当客户端使用资源所有者密码模式时,授权服务器仅需要AuthenticationManager
。
以下示例展示了“资源所有者密码”模式:
.authorizedGrantTypes("password", ...)
在以上流程中,您的授权服务器需要AuthenticationManager
的实例。
有几种方法可以做到这一点(记住前面的基本原理):
- 保留OAuth2引导默认值(您不配置
AuthorizationServerConfigurer
)并配置UserDetailsService
。 - 保留OAuth2 Boot的默认设置,并配置
AuthenticationManager
。 - 重写
AuthorizationServerConfigurerAdapter
(删除OAuth2 Boot程序的默认配置),并依赖AuthenticationConfiguration
。 - 重写
AuthorizationServerConfigurerAdapter
并手动连接AuthenticationManager
。
1.7.1 配置UserDetailsService
终端用户是通过UserDetailsService
在WebSecurityConfigurerAdapter
中指定的。因此,如果您使用OAuth2 Boot程序的默认设置(即您尚未实现AuthorizationServerConfigurer
),则可以配置UserDetailsService
并完成操作,如以下示例所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired DataSource dataSource;
@Bean
@Override
public UserDetailsService userDetailsService() {
return new JdbcUserDetailsManager(this.dataSource);
}
}
1.7.2 配置AuthenticationManager
如果需要对AuthenticationManager
进行更专门的配置,可以在WebSecurityConfigurerAdapter
中进行配置,然后将其公开,如以下示例所示:
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean(BeansId.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthenticationProvider());
}
}
如果您使用默认的OAuth2 Boot配置,它将自动装配Bean。
1.7.3 依赖AuthenticationConfiguration
在AuthenticationConfiguration
中可以使用任何已配置的AuthenticationManager
。这意味着,如果需要一个AuthorizationServerConfigurer
(在这种情况下,您需要自己进行自动装配),则可以依赖AuthenticationConfiguration
来获取AuthenticationManager
bean,如以下类所示:
@Component
public class CustomAuthorizationServerConfigurer extends
AuthorizationServerConfigurerAdapter {
AuthenticationManager authenticationManager;
public CustomAuthorizationServerConfigurer(AuthenticationConfiguration authenticationConfiguration) {
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) {
// .. your client configuration that allows the password grant
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager);
}
}
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
@Override
public UserDetailsService userDetailsService() {
return new MyCustomUserDetailsService();
}
}
1.7.4 手动连接AuthenticationManager
在最复杂的情况下,AuthenticationManager
需要特殊配置,并且您拥有自己的AuthenticationServerConfigurer
,那么您既需要创建自己的AuthorizationServerConfigurerAdapter
,也需要创建自己的WebSecurityConfigurerAdapter
:
@Component
public class CustomAuthorizationServerConfigurer extends
AuthorizationServerConfigurerAdapter {
AuthenticationManager authenticationManager;
public CustomAuthorizationServerConfigurer(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) {
// .. your client configuration that allows the password grant
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager);
}
}
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean(BeansId.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthenticationProvider());
}
}
1.8 授权服务器与Spring Security 5.1资源服务器和客户端兼容吗?
不,不是开箱即用的。 Spring Security 5.1仅支持JWT编码的JWK签名授权,并且Authorization Server不附带JWK Set URI。
不过,可以提供基本支持。
例如,为了将Authorization Server配置为与Spring Security 5.1 Resource Server兼容,您需要执行以下操作:
- 配置它以使用JWK
- 添加JWK设置URI端点
1.8.1 配置授权服务器以使用JWK
要更改用于访问和刷新令牌的格式,可以更改AccessTokenConverter
和TokenStore
,如以下示例所示:
@EnableAuthorizationServer
@Configuration
public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {
AuthenticationManager authenticationManager;
KeyPair keyPair;
public JwkSetConfiguration(AuthenticationConfiguration authenticationConfiguration,
KeyPair keyPair) throws Exception {
this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
this.keyPair = keyPair;
}
// ... client configuration, etc.
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
// @formatter:off
endpoints
.authenticationManager(this.authenticationManager)
.accessTokenConverter(accessTokenConverter())
.tokenStore(tokenStore());
// @formatter:on
}
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(this.keyPair);
return converter;
}
}
1.8.2 添加JWK设置URI端点
Spring Security OAuth不支持JWK,@EnableAuthorizationServer
也不支持向其初始集合添加更多OAuth 2.0 API端点。但是,我们仅可以添加几行。
首先,您需要添加另一个依赖项:com.nimbusds:nimbus-jose-jwt
。这为您提供了适当的JWK支持。
其次,您无需直接使用@EnableAuthorizationServer
,而是直接包含其两个@Configuration
类:
-
AuthorizationServerEndpointsConfiguratio
n:用于配置OAuth 2.0 API端点的@Configuration
类,例如用于令牌的格式。 -
AuthorizationServerSecurityConfiguration
:这些端点周围的访问规则的@Configuration
类。这是您需要扩展的扩展,如以下示例所示:
@FrameworkEndpoint
class JwkSetEndpoint {
KeyPair keyPair;
public JwkSetEndpoint(KeyPair keyPair) {
this.keyPair = keyPair;
}
@GetMapping("/.well-known/jwks.json")
@ResponseBody
public Map<String, Object> getKey(Principal principal) {
RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}
@Configuration
class JwkSetEndpointConfiguration extends AuthorizationServerSecurityConfiguration {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http
.requestMatchers()
.mvcMatchers("/.well-known/jwks.json")
.and()
.authorizeRequests()
.mvcMatchers("/.well-known/jwks.json").permitAll();
}
}
然后,由于不需要更改AuthorizationServerEndpointsConfiguration
,因此可以@Import
而不是使用@EnableAuthorizationServer
,如以下示例所示:
@Import(AuthorizationServerEndpointsConfiguration.class)
@Configuration
public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {
// ... the rest of the configuration from the previous section
}
1.8.3 针对Spring Security 5.1资源服务器进行测试
现在您可以POST到/oauth /token端点(如前所述)以获取令牌,然后将其提供给Spring Security 5.1资源服务器。
上一篇: 分享:一例PHP翻页(分页)类的实例代码
下一篇: 详解PHP实现执行定时任务,