介绍
这是一份关于OAuth 2.0
的用户指南。对于OAuth 1.0来说,一切都是不同的,所以请看它的用户指南。
本指南分为两个部分,第一部分是OAuth 2.0服务端(OAuth 2.0 Provider),第二部分是OAuth 2.0的客户端(OAuth 2.0 Client)。对于服务端和客户端来说,样本代码的最佳来源是集成测试和样例应用程序。
OAuth 2.0 服务端
OAuth 2.0服务端的用途是负责将OAuth 2.0的受保护资源暴露出去。该配置涉及到如何建立与OAuth 2.0客户端的联系,使得客户端可以独立或代表用户访问其受保护的资源。服务端通过管理和验证用于访问受保护资源的OAuth 2.0令牌来实现这一点。在适当的情况下,服务端还必须为用户提供一个接口,以确认客户端可以被授予访问受保护资源的访问权(即确认页面)。
OAuth 2.0 服务端实现
在 OAuth 2.0中,服务端实际上是分成授权服务和资源服务两个角色的,虽然这两个角色有时是存在于同一个应用程序中。使用Spring Security OAuth,您可以选择在两个应用程序之间进行拆分,同时还可以拥有多个共享授权服务的资源服务。获取令牌(Tokens)的请求是由Spring MVC的控制器端点来处理的,访问受保护的资源是通过标准的Spring Security请求过滤器来处理的。为了实现OAuth 2.0授权服务器,在Spring Security 过滤器链中需要实现以下端点:
- AuthorizationEndpoint
是用于授权服务请求的。默认的URL是: /oauth/authorize
。
- TokenEndpoint
是用于获取访问令牌(Access Tokens)的请求。 默认的URL是: /oauth/token
。
要实现OAuth 2.0资源服务器,需要以下过滤器:
- OAuth2AuthenticationProcessingFilter
这个过滤器是用于加载请求提供的一个授权了的访问令牌是否有效。
对于所有的OAuth 2.0服务端的功能,通过使用Spring OAuth专门的@Configuration
适配器来简化配置。也可以通过XML命名空间来配置OAuth,XML的schema存在:http://www.springframework.org/schema/security/spring-security-oauth2.xsd。命令空间是http://www.springframework.org/schema/security/oauth2
。
授权服务器配置
配置授权服务器时,您必须考虑的grant类型,从终端用户那,客户端会使用获取到的访问令牌(如授权代码、用户凭证、刷新令牌)。服务器的配置是用于提供一些实现,像客户端细节服务和令牌服务全局机制上启用或禁用某些方面。但是,请注意,每个客户端可以专门配置权限能够使用某些授权机制和访问授权。也就是说,仅仅因为您的服务端被配置为支持客户凭证
(client credentials)授予类型
(grant type),并不意味着某个特定的客户端被授权使用这种类型的授权类型。
@EnableAuthorizationServer
注释用于配置OAuth 2.0 授权服务器机制。加上任意一个@Beans
去实现AuthorizationServerConfigurer
(这是一个实现了空方法的简单适配器)。以下功能委托给由spring创造的独立configurers而且传递到AuthorizationServerConfigurer
:
- ClientDetailsServiceConfigurer
定义客户详细信息服务的配置器。客户端详细信息可以被初始化,或者您可以直接引用一个现有的存储。
- AuthorizationServerSecurityConfigurer
定义令牌端点上的安全约束。
- AuthorizationServerEndpointsConfigurer
定义授权和令牌端点和令牌服务。
服务端配置的一个重要方面是将授权代码提供给OAuth客户端(在授权码模式中)。OAuth客户端通过将终端用户导向一个可以输入证书/口令的授权验证页面来获取授权码,然后,将授权码传递给服务端授权服务器,服务器验证后重定向页面。在OAuth 2说明文档中有详细的示例。
在xml配置文件中,可以使用<authorization-server/>
节点配置OAuth 2.0授权服务器。
配置客户端详细信息
ClientDetailsServiceConfigurer
类(AuthorizationServerConfigurer
类中的一个调用类)可以用来定义一个基于内存的或者JDBC的客户端信息服务。客户端的重要属性如下:
- clientId
: (必须)客户端ID
- secret
: (对于可信任的客户端是必须的)客户端的私钥。如果有的话。
- scope
: 客户端受限制的范围。如果范围是未定义的或者是空的(默认值),客户端不受范围的限制。
- authorizedGrantTypes
: 授权给客户端使用的权限类型。默认值为空。
- authorities
: 授权给客户端的权限(合格的Spring安全权限)。
客户端详细信息可以通过直接访问底层存储(例如JdbcClientDetailsService
的数据库表)或通过实现ClientDetailsManager
接口(也可以实现ClientDetailsService
接口,或者实现两个接口),从而在运行的应用程序中进行更新。
管理令牌
AuthorizationServerTokenServices
接口里定义了管理OAuth 2.0令牌的必要操作。注意以下几点:
- 当创建访问令牌时,必须存储身份验证,以便接受访问令牌的资源稍后可以引用它。
- 访问令牌用于加载创建令牌时的授权信息。
在创建您的AuthorizationServerTokenServices
接口的实现时,你可以考虑使用DefaultTokenServices
类,它有许多可以插入的策略,以更改访问令牌的格式和存储。它使用随机值创建令牌,并处理除永久令牌以外的所有令牌,对于永久令牌,它委托TokenStore
类进行处理。令牌默认采用基于内存实现的存储方式,但也有一些其它的存储方式。下面是其中一些方式的简介:
- 默认的InMemoryTokenStore
处理类对于单服务器场景非常适用(优点有:低阻塞,宕机时无需热切换到备份服务器)。大部分项目可以在开始时或者在开发模式下使用这种方式,这样比较容易启动一个没有其它依赖的服务器。
- JdbcTokenStore
类是实现存储令牌的JDBC版本,它将令牌信息保存到关系型数据库中。如果服务器间共享数据库或者同一个服务器有多个实例或者授权服务器、资源服务器有多个组件,那么可以使用JDBC方式存储令牌。使用JdbcTokenStore
类时,需要将spring-jdbc组件jar包添加到工程的编译路径中。
- 该存储的JSON Web Token(JWT)
版本将所有关于授权的数据都编码到令牌本身(因此,没有任何后端存储,这是一个显著的优势)。一个缺点是不能很容易地撤销访问令牌,所以它们通常都是在短时间内被授予的,而撤销则在刷新令牌中处理。另一个缺点是,如果您在其中存储大量的用户凭证信息,令牌可能会变得非常大。JwtTokenStore
不是一个真正的存储类,因为它不保存任何数据,但是它在传输令牌信息和授权信息方面扮演着同样的角色(在DefaultTokenServices
类中实现)。
注意:JDBC服务的模式并不是与库一起打包的(因为在实践中您可能希望使用较多的变体),但是您可以从github的测试代码中获得一个示例。一定要使用
@EnableTransactionManagement
注解防止客户端应用程序之间的冲突竞争相同的行创建令牌。还要注意,样例模式有显式的主键声明——在并发环境中也是必需的。
JWT令牌
要使用JWT令牌,您需要在授权服务器中使用JwtTokenStore
,资源服务器还需要能够对令牌进行解码,JwtTokenStore(接口)
依赖于JwtAccessTokenConverter
类,并且授权服务器和资源服务器都需要相同的实现。令牌以默认方式进行签名,资源服务器为了能够验证这些签名,它需要有与授权服务器相同的对称密钥(服务器共享对称密钥)。或者它需要有可以匹配签名私钥的公钥(公有私有密钥或混合密钥)。公共密钥(如果可用)在/oauth/token_key
密钥端点上的授权服务器公开,缺省情况下,使用访问规则”denyAll()”是安全的。你可以打开它通过注入一个SpEL表达式AuthorizationServerSecurityConfigurer
(例如”permitAll()”因为它是一个公开的密钥,所以它可能已经足够了)。
为了使用JwtTokenStore
类,你需要在工程编译路径下添加spring-security-jwt
组件jar包(你可以在SpringOAuth的github资源库中找到,但是两者的版本号是不一致的)。
认证(grant)类型
AuthorizationEndpoint
通过AuthorizationServerEndpointsConfigurer
可以配置支持 grant 类型。默认情况下支持所有的 grant 类型,除了密码认证(有关如何切换的细节,请参阅下面的内容)。以下属性影响 grant 类型:
- authenticationManager
:通过注入的AuthenticationManager
来开启密码认证。
- userDetailsService
:如果你注入UserDetailsService
或者一个全局配置(例如GlobalAuthenticationManagerConfigurer
)然后刷新令牌认证将包含一个检查用户详细信息的功能,以确保账户仍然活跃。
- authorizationCodeServices
:为了授权代码认证类型定义了授权代码服务(AuthorizationCodeServices
的实例)。
- implicitGrantService
:在简单(隐式)认证类型中管理状态。
- tokenGranter
:TokenGranter
(完全控制认证和忽略上面的其他属性)。
在XML中 grant 类型包括authorization-server
的子元素。
配置端点的URL
配置AuthorizationServerEndpointsConfigurer
有一个pathMapping()
方法。该方法有两个参数:
- 默认的端点URL(框架实现)。
- 自定义URL(从"/"
开始)。
框架提供的URL路径是/oauth/authorize
(授权端),/oauth/token
(令牌端),/oauth/confirm_access
(用户发送确认授权到这里),/oauth/error
(用户呈现授权服务器授权出错的请求),/oauth/check_token
(被资源服务器用来解码访问令牌), 还有/oauth/token_key
(如果使用JWT令牌,将公开用于令牌验证的公钥)。
授权端/oauth/authorize
(或其映射的备选方案)应该是受Spring Security保护的,以便仅对经过身份验证的用户进行访问。例如,使用标准的Spring Security WebSecurityConfigurer
:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/login").permitAll().and()
// default protection for all resources (including /oauth/authorize)
.authorizeRequests()
.anyRequest().hasRole("USER")
// ... more configuration, e.g. for form login
}
注意:如果您的授权服务器也是一个资源服务器,那么还有一个安全过滤器链,它具有较低的优先级控制API资源。对于那些受访问令牌保护的,在主要面向用户的过滤器链中不匹配的请求,您需要配置它们的路径。因此,请确保包含一个
request matcher
,它只在WebSecurityConfigurer
中选择非api资源。
在Spring OAuth的基于@Configuration
注解的配置中,令牌端默认是使用HTTP Basic
验证客户端的密钥的。这在XML中不是这样的(因此应该显式地保护它)。
使用XML的<authorization-server/>
元素可以使用一些属性来达到改变默认的端点URL。
自定义用户界面
大多数授权服务器端点主要由机器使用,但是有一些资源需要一个UI,这些资源是通过/oauth/confirm_access
的GET请求,以及来自/oauth/error
的HTML响应。它们在框架中使用 whitelabel 提供实现,因此大部分授权服务器应用实例都希望提供它们自己的功能,这样就可以控制样式和内容。您所需要做的就是为这些端点提供一个带有@RequestMappings
的Spring MVC controller,并且该框架的默认值将在 dispatcher 中得到较低的优先级。/oauth/confirm_access
端点,您可以预期一个AuthorizationRequest
将被绑定到会话中,该请求将携带所有需要从用户获得批准的数据(可以从WhitelabelApprovalEndpoint
复制一份作为起始代码)。您可以从该请求中获取所有数据,然后按照您喜欢的方式呈现它,然后所有用户需要做的就是向/oauth/authorize
POST 关于批准或拒绝批准的信息。请求参数被直接传递到UserApprovalHandler
的UserApprovalHandler
,这样您就可以多多少少地解释数据了。默认UserApprovalHandler
取决于你有提供一个ApprovalStore
在 AuthorizationServerEndpointsConfigurer
(在这种情况下,它是一个ApprovalStoreUserApprovalHandler
)或没有(在这种情况下,它是一个TokenStoreUserApprovalHandler
)。标准的 approval handlers 接受以下内容:
- TokenStoreUserApprovalHandler
:通过用户的简单yes/no
决定,等于"true"
或"false"
。
- ApprovalStoreUserApprovalHandler
:一组范围(scope.*
)。参数"*"
表示与所请求的范围相等。参数值可以为"true"
或者"approved"
(如果用户批准认证),否则用户被认为拒绝了该范围。如果至少一个范围被批准,代表认证成功的。
注意:不要忘记将CSRF保护包含在您为用户呈现的表单中。Spring Security在缺省情况下希望得到一个名为
"csrf"
的请求参数(它在 request attribute 中提供值)。有关这方面的更多信息,请参阅Spring Security用户指南,或者查看一下 whitelabel 的实现。
执行SSL
普通HTTP对测试来说足够了,但是在生产环境需要在授权服务器上使用SSL。您可以在一个安全的容器或代理后运行该应用程序,如果您正确地设置代理和容器(这与OAuth2无关),那么它就可以正常工作。您可能还希望使用Spring Security requiresChannel()
约束来保护端点。对于/authorize
端,您可以将其作为常规应用程序安全性的一部分来完成。对于/token
端,在AuthorizationServerEndpointsConfigurer
中你可以设置使用sslOnly()方法,改变标志位。在这两种情况下,安全通道设置都是可选的,但如果它检测到不安全通道上的请求,会导致Spring Security重定向到它认为是安全通道的安全通道。
自定义错误处理
在授权服务器上的错误处理使用标准的Spring MVC功能,即 @ExceptionHandler
端点本身的方法。用户还可以提供一个WebResponseExceptionTranslator
端点本身,最好的办法是改变响应的内容而不是他们呈现的方式。对于令牌端和授权端中抛出的异常,OAuth的错误视图(/oauth/error
)进行处理,异常的呈现委托给HttpMesssageConverters
(可以添加到MVC配置中)。为HTML响应提供了whitelabel错误端点,但是用户可能需要提供一个自定义实现(例如,只需添加一个@Controller
,它的请求映射是@RequestMapping("/oauth/error")
)。
将用户角色映射到Scopes
有时,限制令牌的范围不仅限于分配给客户端的范围,而且还根据用户自己的权限来限制。如果您在AuthorizationEndpoint
中使用DefaultOAuth2RequestFactory
,您可以设置一个标志checkUserScopes=true
来限制只允许范围匹配用户的角色。您也可以在TokenEndpoint
注入一个OAuth2RequestFactory
但是,如果您也安装一个TokenEndpointAuthenticationFilter
-在BasicAuthenticationFilter
之后添加这个过滤器,这仅适用与密码认证模式。当然,您也可以实现自己的规则,将范围映射到角色,并实现自己的OAuth2RequestFactory
版本。如果你使用@EnableAuthorizationServer
的话,AuthorizationServerEndpointsConfigurer
允许您注入一个自定义的OAuth2RequestFactory
,所以你可以使用这个功能来建立一个工厂。
资源服务器配置
资源服务器(可以与授权服务器在同一应用或是单独的应用程序)提供由OAuth2令牌保护的资源。Spring OAuth提供了一个实现该保护的Spring Security authentication filter。在@Configuration类
中,你可以使用@EnableResourceServer
来开启/关闭过滤器,使用ResourceServerConfigurer
来配置它(必要时)。下面是可配置的属性:
- tokenServices
:定义令牌服务的实体。(ResourceServerTokenServices
类的实例)
- resourceId
:资源的id(可选,推荐配置,如果存在的话,授权服务器将对它进行验证)。
- 资源服务器的其它扩展点(例如用于从传入请求提取令牌的tokenExtractor
)。
- 受保护资源的 request matchers(默认为所有)。
- 对受保护资源的访问规则(默认为"authenticated"
)。
- 在Spring Security中,由HttpSecurity
configurer允许的受保护资源的其他定制。
@EnableResourceServer
注解把一个OAuth2AuthenticationProcessingFilter
过滤器自动添加到Spring Security 过滤链中。
在 XML中,有一个id
属性的<resource-server/>
元素, 这是一个servlet Filter
的bean id,它可以被手动添加到标准的Spring Security链中。
您的ResourceServerTokenServices
是在OAuth协议中与授权服务器匹配的另一半。
如果资源服务器和授权服务器都在同一个应用程序中使用DefaultTokenServices
那么你就不用太想这个了因为它实现了所有必要的接口所以它是自动一致的。如果你的资源服务器是一个单独的应用程序,你必须确保你匹配的授权服务器的功能和提供一个知道如何正确解码的令牌的ResourceServerTokenServices
。与授权服务器一样,您通常可以使用DefaultTokenServices
,并且选择主要通过TokenStore
(后端存储或本地编码)来表达。另一种选择是RemoteTokenServices
,它是一个Spring OAuth的功能(不是规范的一部分)。允许资源服务器通过授权服务器上的HTTP资源(/oauth/check_token
)来解码令牌。如果资源服务器中没有大量的流量(每个请求都必须通过授权服务器进行验证),或者您能够搭建缓存,那么RemoteTokenServices
是很方便的。 要使用/oauth/check_token
断点,您需要在AuthorizationServerSecurityConfigurer
中改变它的访问规则(默认是"denyAll()"
)。如:
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess(
"hasAuthority('ROLE_TRUSTED_CLIENT')");
}
在本例中,我们将配置/oauth/check_token
端点和/oauth/token_key
(因此可信的资源可以获得JWT验证的公共密钥)。这两个端点受到使用客户端凭证的HTTP Basic authentication来保护 。
配置一个OAuth-Aware的表达式处理程序
你可能想要使用Spring Security的表达式语言配置访问控制的优点。 表达式处理器在@EnableResourceServer
配置中以默认方式进行注册。 表达式包括#oauth2.clientHasRole,#oauth2.clientHasAnyRole,和 #oath2.denyClient,这些可以提供基于oauth客户端角色的访问控制(详细列表见OAuth2SecurityExpressionMethods
)。 在XML中,你可以在<http/>
安全配置节点内使用expression-handler
元素注册一个oauth-aware表达式处理器。