[译文]Spring Security OAuth2 开发者指南

因为最近在做毕设,涉及到给现有项目添加Spring Security OAuth2功能,参考了网上的几个两个项目,然而并不能达到预期的效果。
参考项目1
参考项目2这个是官方的项目。
所以想通过官方文档来解决。这里是官网原文

一、介绍

该用户指南分为两部分,分别供OAuth 2.0 的提供者(Provider)和客户端(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,你可以选择将它们分放在两个应用中,或是让多个资源服务共享同一个授权服务。令牌请求是被Spring MVC controller的endpoints处理的,对受保护资源的访问是被标准的Spring Security请求过滤器处理的。为了实现OAuth 2.0授权服务器,需要在Spring Security过滤器链中包含以下endpoints:

  • AuthorizationEndpoint 用于授权的服务请求。默认URL:/oauth/authorize.
  • TokenEndpoint 用于令牌的服务请求。默认URL:/oauth/token.

为了实现OAuth 2.0资源服务器,需要以下过滤器:

  • OAuth2AuthenticationProcessingFilter 用于为获得授权访问令牌的请求加载授权。
    所有OAuth 2.0提供者,都通过特殊的Spring OAuth @Configuration adapters简化了配置。还有一个XML命名空间也用于OAuth配置,相应的schema位于 http://www.springframework.org/schema/security/spring-security-oauth2.xsd. 命名空间是http://www.springframework.org/schema/security/oauth2.

四、授权服务器配置

当你配置授权服务器时,你需要考虑客户端将会使用的grant type(如:授权码,用户账号user credentials,更新令牌),用于客户端从终端用户获取访问令牌。这个配置用于提供客户端细节服务和令牌服务的实现,以及用于全局激活或禁用该机制的某些层面。然而,需要注意的是,每个客户端都可以特殊配置,只要有使用某些授权机制和授予访问的许可就行。所以,当仅有提供者被配置为支持“client credentials”的grant type时,并不意味着一个具体的客户端就被授权可以使用那种grant type了.

注解@EnableAuthorizationServer和任何实现了AuthorizationServerConfigurer(有一种简便的实现办法就是使用空的方法)的@Beans一起,被用于配置OAuth 2.0授权服务器机制。以下特性被分派于将Spring产生的配置项分开,并传进AuthorizationServerConfigurer:

  • ClientDetailsServiceConfigurer: 定义了用户细节服务的配置项。用户细节可以是已经被初始化的,或指定为一个已存在的store.
  • AuthorizationServerSecurityConfigurer: 定义了令牌endpoint的安全限制。
  • AuthorizationServerEndpointConfigurer: 定义了授权endpoint和令牌endpoint,以及令牌服务。

提供方配置的一个重要方面就是授权码被提供给(已获取授权码的)OAuth客户端的方式。OAuth客户端通过将终端用户定向到授权页面(用户可以在该页面输入其账号密码)来获取授权码,然后携带着授权码从提供者授权服务器重定向回OAuth客户端。相关示例在OAuth 2说明中有详细阐述。

在XML中,有<authorization-server/>元素可以以相似的方式配置OAuth 2.0授权服务器。

1 配置用户细节

ClientDetailsServiceCongigurer(是AuthorizationServerConfigurer的回调)可用于定义一个in-memory或JDBC的用户细节服务的具体实现。一个客户端的重要属性包括:

  • clientId: (必要)客户端的id.
  • secret: (受信任客户端必要)
  • scope: 客户端受限范围。如果该范围未定义或为空(默认为空),客户端就无范围限制。
  • authorizedGrantTypes: 授权给客户端的grant types。 默认值为空。
  • authorities: 授予客户端的权限。(常规的Spring Security权限)

用户细节可以在应用运行时更新,通过直接访问其下的store(如,JdbcClientDetailsService的数据库表),或通过ClientDetailsManager接口类(ClientDetailsService的两种实现类都实现了)即可更新。

注意:JDBC服务的schema没有打包进库(因为在实践中你可能会用到其中太多的变量),github上有一个相关的入门案例供您使用:test code in github

2 管理令牌

AuthorizationServerTokenServices接口类定义了管理OAuth 2.0令牌的必要操作。注意:

  • 当创建了一个访问令牌,相应验证必须存储起来,以便接收该访问令牌的资源将来可以找到它。
  • 访问令牌用于加载验证,该验证用于批准该令牌的创建。

当你在创建AuthorizationServerTokenServices实现类时,你可能想使用DefaultTokenServices,它有许多可以引入的策略,这些策略可以改变令牌的格式和存储。默认情况下,它会通过随机值生成令牌,并负责令牌相关的所有问题,除了令牌持久性,这个问题它委托给了一个TokenStore来负责。默认的store是in-memory 实现,也有一些其他可用的实现。下面是对这些实现的讨论和介绍:

  • 默认的InMemoryTokenStore对单个服务器(也即:低流量,宕机时不会热切换为备份服务器)来说表现完美。大多数项目可以由此开始(可能以开发模式操作),来轻松开启一个无依赖的服务器。
  • JdbcTokenStore是同一个东西的JDBC 版本,它将令牌存储于关系数据库。如果你能在不同服务器或同一个服务器的按比例放大的实例(如果只有一个服务器的话)中共享一个数据库,那么就可以使用JDBC版的TokenStore。要想使用JdbcTokenStore,你需要在classpath中设置”spring-jdbc”。
  • JSON Web Token(JWT) 版的store把授权相关的所有数据编码进了令牌本身(因此没有后端store,这是个显著优势)。有一个缺点是,你无法轻易撤销一个访问令牌,因此它们都被授予了短暂时效,由刷新令牌负责撤销操作。另一个缺点是,如果你在其中存储了大量用户账号信息,令牌可能会变得非常大。JwtTokenStore并不真的是一个“仓库”,因为它并不持久化任何数据,但是它同样扮演着转换令牌值和DefaultTokenServices中的验证信息的角色。

注意:确保注解@EnableTransactionManagement来避免令牌创建时,客户端应用竞争同一行时产生的冲突。样例schema有明确的PRIMARY KEY声明 —— 这些在一致环境中也是必要的。

3 JWT Tokens

要想使用JWT令牌,你需要在你的授权服务器中使用JwtTokenStore。资源服务器需要能够对令牌解码,因此JwtTokenStore依赖于JwtAccessTokenConverter,授权服务器和资源服务器都需要有相同的实现。令牌是默认有签名的,资源服务器应能验证签名,因此它要么需要和授权服务器(共享secret,或对称秘钥)相同的对称(签名)秘钥,要么需要共有秘钥(验证秘钥)来匹配授权服务器中的(公-私或非对称秘钥)私有秘钥(签名秘钥)。公有秘钥(如果有的话)是被授权服务器暴露在/oauth/token_keyendpoint上的,该endpoint由默认的访问规则denyAll()保证安全。你可以通过向AuthorizationServerSecurityConfigurer注入标准的SPEL表达式来打开它(例如,permitAll()也许就足够了,因为它是一个共有秘钥)。

要使用JwtTokenStore,你需要在你的classpath中配置spring-security-jwt.(你可以在Spring OAuth github中找到它的一个不同的发布版本)。

4 Grant Types

AuthorizationEndpoint支持的grant types可以通过AuthorizationServerEndpointsConverter配置。默认情况下所有grant types都是被支持的,除了密码(如何开启见下方介绍)。以下属性影响grant types:

  • authenticationManager: 通过注入一个AuthenticationManager开启密码授予。
  • userDetailService: 如果你注入一个UserDetailServicer或如果已全局配置了一个,比如在一个GlobalAuthenticationManagerConfigurer,那么一个刷新令牌授予将会包含对用户细节的核对,以确保账户仍然可用。
  • authorizationCodeServices: 为授权码授予定义了授权码服务(AuthorizationCodeServices的实例)。
  • implicitGrantService: 隐形授予期间的状态管理。
  • tokenGranter: TokenGranter完全控制授予过程,忽略以上其他属性。

在XML中,grant types 作为authorization-server的子节点被包含。

5 配置endpoint URLs

AuthorizationServerEndpointsConfigurer有一个pathMapping()方法。它有两个参数:

  • endpoint的默认URL路径(框架实现)
  • 必须的定制路径(以“/”开始)

框架提供的URL路径是:/oauth/authorize(授权endpoint), /oauth/token(令牌endpoint) /oauth/confirm_access(用户在此发送授予准许), oauth/error(用于在授权服务器中渲染错误), oauth/check_token(资源服务器用来解码访问令牌)和/oauth/token_key(如果使用JWT令牌,为令牌验证暴露出公有密钥)。

注意:授权endpoint /oauth/authorize(或其映射替代)应该使用Spring Security进行保护,这样它就只对认证用户可访问。例如,使用标准Spring Decurity WebSecurityConfigurer:

1
2
3
4
5
6
7
8
9
@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资源。对于那些请求被访问令牌保护的资源,你需要保证它们的路径不会和主要user-facing过滤器链中的路径匹配,因此,请确保包含一个请求匹配器,仅仅选出以上WebSecurityConfigurer中的非API资源。

默认通过@Configuration支持中的Spring OAuth,使用HTTP基础认证客户端secret来保护令牌endpoint。这不是XML中的情形(因此它需要明确被保护)。

XML中,<authorization-server/>元素有一些可用于以相似的方式改变默认endpoint URL的属性。/check_tokenendpoint需要被明确激活(使用check-token-enabled属性)。

五、定制UI

大多数授权服务器endpoint主要被机器使用,但也有几个资源需要UI:/oauth/confirm_access的GET和来自/oauth/error的HTML响应。通过使用框架中的whitelabel实现来提供。因此大多数真实的授权服务器实例会想要提供他们自己,因而它们可以空着形式和内容。你所需要做的就是用@RequestMapping为那些endpoints提供一个Spring MVC controller,框架默认会在dispatcher中使用相对较低的优先级。在oauth/confirm_accessendpoint中,你可以期待一个可以绑定到会话的AuthorizationRequest,携带着为获取用户准许所需的所有数据(默认实现是WhitelabelApprovalEndpoint,因此请查看要复制的起点)。你可以从请求中抓取所有的数据,以你喜欢的方式渲染它,然后用户索要做的就是将准许与否的信息POST回/oauth/authorize. 请求参数直接传递给AuthorizationEndpoint中的UserApprovalHandler,因此你可以随意转述数据。默认的UserApprovalHandler取决于你是否已经提供了一个ApprovalStore在你的AuthorizationServerEndpointsConfigurer中(在某些情况下是ApprovalStoreUserApprovalHandlerTokenStoreUserApprovalHandler)。标准的approval handlers如下:

  • TokenStoreUserApprovalHandler: 一个简单的通过user_oauth_approval是/否决定,相当于true/false
  • ApprovalStoreUserApprovalHandler: 一个scope.*参数集,”*”相当于要求的范围。参数值可以是”true”或”approved”(如果用户准许了grant),或用户拒绝了该scope. 一个grant如果至少有一个scope被approved了,就视为成功。

注意:别忘了在你渲染给用户的表单中包含CSRF保护。Spring Security期待有一个默认名为”_csrf”的请求参数(它在一个请求属性中提供了值)。有关更多信息,请参阅Spring Security用户指南,或查看whitelabel实现的指导。

强制实施SSL

简单HTTP对测试或许够用,但是在生产中,一个授权服务器必须只能通过SSL使用。你可以在一个安全的容器中或在一个代理后运行应用,如果你正确设置了代理和容器,它应该正常工作(这与OAuth2无关)。你也许也希望通过Spring Security的requiresChannel()限制来确保endpoint的安全。对于/tokenendpoint,在AuthorizationServerEndpointsConfigurer中有一个标志,你可以通过sslOnly()方法设置它。在两种情形下,安全通道设置都是可选的,但是会导致Spring Security重定向到它认为的安全通道中,如果它检测到一个非安全通道的话。

六、定制错误处理

授权服务器中的错误处理使用标准的Spring MVC特性,在endpoints自身中名为@ExceptionHanlder方法。用户也可以提供WebResponseExceptionTranslator给endpoints自身,和他们被渲染的方式相比,这是改变响应内容最好的方式。异常的渲染工作,在令牌endpoint中被委托给了HttpMessageConverters(它可以被添加到MVC的配置中),在授权endpoint中被委托给了OAuth错误视图(/oauth/error)。whitelabel错误endpoint被提供给HTML响应,但是用户可能需要提供自定义实现(如,只是使用@RequestMapping("/oauth/error")添加一个@Controller)。

七、用户角色到范围的映射

不仅通过分配给客户端的scope来限制令牌的scope,还可以根据用户自己的许可,有时这是很有用的。如果你在AuthorizationEndpoint中使用DefaultOAuth2RequestFactory,你可以设置一个flag checkUserScopes=true来限制准许的scopes于匹配的用户角色。你也可以注入一个OAuth2RequestFactoryTokenEndpoint中,但是这只在你安装了TokenEndpointAuthenticationFilter的情况下才能工作( 比如密码授予)。当然,你要可以实现你自己的映射范围到角色的规则,并安装你自己的OAuth2RequestFactory版本。AuthorizationServerEndpointsConfigurer允许你注入一个自定义OAuth2RequestFactory,因此你可以使用这个特性来建立一个工厂,如果你使用@EnableAuthorizationServer

八、资源服务器配置

资源服务器(可以和授权服务器相同或是一个分开的应用)提供了通过OAuth2令牌保护的资源。Spring OAuth提供了Spring Security认证过滤器,实现了这种保护功能。你可以在@Configuration类中使用@EnableResourceServer来开启它,并使用ResourceServerConfigurer进行必要的配置。以下特性可以被配置:

  • tokenServices: 定义了令牌服务的bean(ResourceServerTokenServices的实例)。
  • resourceId: 资源的id (可选,但推荐使用,通过授权服务器使之生效)。
  • 资源服务器的其他扩展点(如, tokenExtractor,用于从收到的请求中抽取令牌)。
  • 受保护资源的请求匹配器(默认用于所有)。
  • 受保护资源的访问规则(默认用于“authenticated”)。
  • 受保护资源的其他定制,得到Spring Security的HttpSecurity的准许。

@EnableResourceServer自动向Spring Security filter chain添加了OAuth2AuthenticationProcessingFilter类型的过滤器。

在XML中,有一个<resource-server/>元素,其id属性是servletFilter的bean的id. Filter接着会被手动添加到标准的Spring Security chain中。

你的ResourceServerTokenServices是与授权服务器的合同的另一半。如果资源服务器和授权服务器在同一个应用中并且你使用了DefaultTokenServices,那你就不需要过多考虑这些了,因为它实现了所有需要的接口,因此它是自动一致的。如果你的资源服务器是单独的应用,那你就需要确保你匹配了授权服务器的能力,并提供一个ResourceServerTokenServices,它知道怎样正确地解码令牌。对于授权服务器,你可以经常使用DefaultTokenServices,而且TokenStore(后端存储或本地编码)也展示了大部分选择。一个替代方案是RemoteTokenServices,这是一个Spring OAuth特性(不是特殊部分),它允许资源服务器通过授权服务器上的HTTP资源来解码令牌(/oauth/check_token)。如果资源服务器没有很大体量的流量(每个请求都要用授权服务器去验证),或者你能负担得起缓存结果,RemoteTokenServices会很方便。要使用/oauth/check_tokenendpoint,你需要通过改变它的访问规则(默认是denyAll())来在AuthorizationServerSecurityConfigurer暴露它,比如:

1
2
3
4
5
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess(
"hasAuthority('ROLE_TRUSTED_CLIENT')");
}

在本例中,我们配置了/oauth/check_tokenendpoint和/oauth/token_keyendpoint(这样受信任的资源就能获取JWT验证所需的公钥了)。这两个endpoint受HTTP基础认证保护,HTTP基础认证使用了客户端资格认证。

配置一个OAuth可知的表达式处理器

你可能想利用Spring Security的基于表达式的访问控制的优势。表达式处理器可以默认在@EnableResourceServer步骤中注册。表达式包括#oauth.clientHasRole, #oauth.clientHasAnyRole#oauth.denyClient,它们可以用于提供基于oauth客户端角色的访问权限(参照OAuth2SecurityExpressionMethods获得全面的清单)。 在XML中你可以使用常规的<http/>安全配置中的expression-handler元素注册一个oauth可知的表达式处理器。

九、OAuth 2.0 客户端

OAuth 2.0 客户端机制负责访问其他服务器上的OAuth 2.0受保护资源。配置包括创建用户可能有访问权限的相关受保护资源。客户端可能也需要为用户存储授权码和访问令牌的存储机制。

受保护资源的配置

受保护资源(或“远程资源”)可以通过使用OAuth2ProtectedResourceDetails类型的bean来定义。一个手不好胡资源有以下属性:

  • id: 资源的id。这个id只被用户用于查找资源;它从来不用再OAuth协议中使用。它也是bean的id。
  • clientId: OAuth 客户端id. OAuth提供者用来识别你的客户端。
  • accessTokenUri: 提供访问令牌的提供者OAuth endpoint.
  • scope: 是一组以逗号分隔的字符串列表,确定了资源访问范围。默认没有范围。
  • clientAuthenticationSchema: 客户端用来认证访问令牌endpoint的schema. 建议的值:”http_basic”和”form”. 默认值:”http_basic”. 参照OAuth2.1。

不同的grant types 对OAuth2ProtectedResourceDetails有不同的具体实现(如,”client_credentials” grant type的ClientCredentialsResource)。对于需要用户认证的grant types,有更多的属性:

  • userAuthorizationUri: 如果用户需要对资源的认证访问权限,用户会被重定向的uri。注意,这并不总是必须的,取决于哪个OAuth2侧面被支持。

在XML中,有个<resource/>元素可以用于创造OAuth2ProtectedResourceDetails类型的bean. 它有以上相应的所有属性。

客户端配置

对于OAuth 2.0 客户端,使用@EnableOAuth2Client可以简化配置。它做了两件事:

  • 创建一个过滤器bean (使用ID oauth2ClientContextFilter)来存储当前请求和上下文。在需要请求时认证的情况下,它会管理以OAuth 认证uri 为起始或终点的重定向。
  • 创建请求范围内的AccedssTokenRequest类型的bean。这可以用于认证码(或隐形认证)授予客户端,使个人用户相关的状态不被冲突掉。

过滤器应该放进应用中(例如,使用一个Servlet initializer或web.xml的DelegatingFilterProxy的同名配置)。

AccessTokenRequest可以像下面这样用于OAuth2RestTemplate:

1
2
3
4
5
6
7
@Autowired
private OAuth2ClientContext oauth2Context;
@Bean
public OAuth2RestTemplate sparklrRestTemplate() {
return new OAuth2RestTemplate(sparklr(), oauth2Context);
}

OAuth2ClientContext被放置于会话范围,保持独立的不同用户的状态。没有这个,你就得自己在服务器上管理同等的数据结构,通过将受到的请求映射到用户,并且将每个用户关联到一个独立的OAuth2ClientContext实例。

在XML中有一个<client/>元素,其id属性是servletFilter的bean的id. 这个Filter必须在@Configuration中同名映射到@DelegatingFilterProxy

访问受保护的资源

一旦你已经提供了资源的所有配置,你现在就可以访问那些资源了。推荐的访问那些资源的方是通过使用Spring 3 中介绍的Rest Template。Spring Security OAuth提供了RestTemplate的一个扩展,它只需要拿到一个OAuth2ProtectedResourceDetails的实例。要通过user-tokens(认证码grants)使用它,你应该考虑使用@EnableOAuth2Client配置(或XML中同等效果的<oauth:rest-template/>),创建一些请求和会话范围内的上下文对象,这样同一时间发给不同用户的请求就不会产生冲突。

一个通用的规则是,web应用不应该使用密码grants,因此如果你能使用AuthorizationCodeResourceDetail(为所有访问令牌共享),就避免使用ResourceOwnerPasswordResourceDetails

在客户端中持久化令牌

虽然客户端不需要持久化令牌,但如果用户不需要在每次客户端重启时都被要求准许一个新的令牌授予的话,倒不失为一个不错的做法。ClientTokenServices接口定义了为具体用户持久化OAuth 2.0 令牌的必要操作。有现有的JDBC实现,但是如果你愿意自己实现将存储令牌和认证实例存储于持久数据库的服务的话,你也可以自己实现。如果你想使用这个特性吗,你需要提供一个特别的配置TokenProviderOAuth2RestTemplate。例如:

1
2
3
4
5
6
7
8
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestOperations restTemplate() {
OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest));
AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider()));
provider.setClientTokenServices(clientTokenServices());
return template;
}

十、外部OAuth2提供者的定制

一些外部的OAUth2提供者(如Facebook)可能没有正确实现特定功能,或他们只停留在一个较旧的Spring Security OAuth版本。要想在你的客户端应用中使用那些提供者,你可能需要调整客户端基础设施的一些部分。

以使用Facebook为例,在tonr2应用中有一个Facebook特性(你需要改变配置项来添加你自己的有效的client id 和secret - 它们都可以在Facebook网站上轻松生成)。

Facebook令牌响应也包括一个令牌时效的非兼容的JSON入口(它们使用expires而不是expires_in),因此如果你想要在你的应用中使用时效,你将需要通过使用一个定制的OAuth2SerializationService来手动对其进行解码。

分享
0%