您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
xxhh 发布于2019年06月10日 20:30 最近更新于 2019年06月29日 17:01

翻译 Spring OAuth 2.0 开发者指南

283 次浏览 读完需要≈ 43 分钟 SpringJava

内容目录

引入

本指南提供对OAuth 2.0的支持,OAuth 2.0指南相对于OAuth 1.0指南而言完全不同,参见see it's developers guide

本用户指南分为两部分,第一部分是OAuth 2.0提供者,第二部分是OAuth 2.0客户端。对于这两部分来说,最好的源码样例在integration testssample apps

OAuth 2.0提供者

OAuth 2.0提供者的责任是暴露被保护资源,它的配置涉及到建立可以独立访问被保护资源或是用户信息的OAuth 2.0客户端,OAuth 2.0提供者通过管理和认证token来访问被保护的资源。在适当的情况下,OAuth 2.0提供者必须提供一个接口给使用者以确认其客户端是否有权访问被保护的资源。

OAuth 2.0提供者的实现

OAuth 2.0提供者的作用是分离授权服务和资源服务,但有时授权服务和资源服务属于同一应用。在Spring Security OAuth中可以选择把它们分成两个应用,也可以多个资源服务共享一个授权服务,Spring MVC负责处理获取token的请求,标准的Spring Security请求过滤器负责访问被保护的资源,为了实现OAuth 2.0授权服务,Spring Security过滤链必须配置以下两点:

  • AuthorizationEndpoint:用于处理授权的服务请求,默认URL:/oauth/authorize
  • TokenEndpoint:用于处理token访问,默认URL:/oauth/token

以下过滤器是实现OAuth 2.0资源访问服务必须的:

  • OAuth2AuthenticationProcessingFilter用于加载授权请求指定的token

对于所有OAuth 2.0提供者的而言,它的配置被特殊的Spring OAuth注解@Configuration适配器简化了,这也可以使用OAuth XML命名空间的配置,即使用http://www.springframework.org/schema/security/spring-security-oauth2.xsd约束下的命名空间:http://www.springframework.org/schema/security/oauth

授权服务端的配置

当你配置授权服务端的时候,你需要考虑客户端获取终端用户信息的token的授权类型(比如:授权码、用户认证、token更新),授权服务的配置用于提供客户端服务细节的实现、token服务以及某些全局配置的启用和禁用。注意,每个客户端都可以使用某些授权机制配置特殊的访问权限。即:即使你提供了"客户端认证"的授权,并不意味着一些特殊的客户端也使用"客户端认证"授权。

@EnableAuthorizationServer注解用于配置OAuth 2.0授权服务,配合@Beans注解一起在实现AuthorizationServerConfigurer的类上使用(有一个便利的适配类AuthorizationServerConfigurerAdapter已经有了方法的实空现),以下配置被委派给Spring创建并传递到AuthorizationServerConfigurer

  • ClientDetailsServiceConfigurer:一个定义客户端服务细节的配置器,客户端服务细节可以在此初始化,或是引用一个已经存在的存储库。
  • AuthorizationServerSecurityConfigurer:用于定义token的安全约束。
  • AuthorizationServerEndpointsConfigurer:用于定义授权端和token端以及token服务。

把授权码提供给客户端的方式是OAuth 2.0提供者配置的其中一个重要点,授权码可以通过OAuth客户端引导终端用户到一个用户可以输入自己的认证凭据的页面获取,之后授权服务端以重定向的方式回调到OAuth客户端并携带上授权码,这就是OAuth 2规范。

在XML配置中使用<authorization-server/>元素就可以简单的配置OAuth 2.0的授权服务端。

配置客户端服务细节

ClientDetailsServiceConfigurerAuthorizationServerConfigurer的回调)可以用于定义一个基于内存或JDBC的客户端服务,客户端包含几个重要属性:

  • clientId:客户端的id,必须配置。
  • secret:客户端的密钥,如果有需要的话。
  • scope:客户端的作用域限制,如果此参数没有定义或者是空的则表示客户端的作用域不受限。
  • authorizedGrantTypes:客户端的授权类型,默认为空。
  • authoritiesSpring Security授予客户端的权限。

客户端服务细节在程序运行时可以直接通过底层的存储服务(如:使用JdbcClientDetailsService时的数据库表)或ClientDetailsManager接口(ClientDetailsService的两个实现)进行动态的更新。

注意:JDBC服务并没有与本库打包到一起(因为在实践中你可能会有很多变更),不过你可以从test code in github运行代码示例。

管理token

AuthorizationServerTokenServices接口定义了管理OAuth 2.0token的必要操作。注意以下规则:

  • 当一个token被创建时,必须将其保存起来,以便于之后的资源接收时可以引用它。
  • token用来加载授权认证的授权证明。

当你创建AuthorizationServerTokenServices实现的时候,你需要考虑是否使用DefaultTokenServices,它有很多可插入的策略来改变token的格式和存储。默认创建token的方式是使用随机值,处理所有token的持久化工作委托给TokenStore,默认的存储方式是基于内存实现,但还有一些别的方式可以选择,下面对它的每一种实现方式进行讨论

  • 默认的实现InMemoryTokenStore:对于单服务(低流量并且发生故障时不用热转移到备份服务器)来说是非常好的,大多数项目就是由此实现开始,也可以开在发模式下使用这种方式以便于在没有其他依赖的情况下更简单的启动服务。
  • 第二个是JDBC版本的实现JdbcTokenStore:它把token保存到一个相关的数据库中,如果你可以在多个服务之间共享数据库则使用这个实现。又或者是你在只有一个服务实例时需要扩充相同服务器实例,或是授权和资源服务器是有多个组件时,也需要使用此实现。如果使用此实现,你需要把"spring-jdbc"引入到classpath下。
  • 第三个是JSON Web Token(JWT)版本的实现JwtTokenStore:它把所有数据编码作为token(所以不需要存储是它的一大优势),它的其中一个缺点是你不容易撤销某个token,所以它通常用于在短期的token和在刷新token时做撤销处理。另一个缺点是如果用户认证数据非常多的时候,token也会相应的变得非常长。在感觉上JwtTokenStore并没有持久化存储任何数据,但是在DefaultTokenServices中它扮演着把授权信息转换为token的翻译角色。

注意:JDBC服务并没有与本库打包到一起(因为在实践中你可能会有很多变更),不过你可以从test code in github运行这个代码示例。但是在创建token的时候,你要使用@EnableTransactionManagement预防不同客户端之间竞争相同行记录的冲突。同时要有明确的主键声明,这在并发环境中非常重要。

JWT Tokens

实现JWT Tokens需要JwtTokenStore类,资源服务需要对token进行解码所以JwtTokenStore依赖JwtAccessTokenConverter,并且授权服务和资源服务都需要相同的实现。默认情况下token已经被签名了,资源服务需要去校验签名,所以授权服务需要使用对称的key或者和资源服务共享相同的key,又或者授权服务和资源服务使用公钥和私钥(公钥和私钥相互匹配)进行非对称的签名方式。公钥(如果是用此方式)通过授权服务的/oauth/token_key路径进行暴露,默认情况下通过denyAll()访问,这是安全的。你可以使用标准的SpEL表达式注入到AuthorizationServerSecurityConfigurer中。

如果要使用JwtTokenStore,你需要把"spring-security-jwt"引入到classpath下(你可以在与Spring OAuth相同发布周期不同的github仓库下找到它)。

授权类型

授权类型由AuthorizationEndpoint支持,它可以通过AuthorizationServerEndpointsConfigurer进行配置。默认情况下除了密码之外所有的授权类型都支持(怎么切换详见以下说明),授权类型受以下属性影响:

  • authenticationManager:密码授权,可以注入一个AuthenticationManager实例开启。
  • userDetailsService:如果注入一个UserDetailsService的实例或者有一个UserDetailsService实例的全局配置(例如配置在GlobalAuthenticationManagerConfigurer中),则在刷新token的时候会包含一个检查用户详情的操作,确保账户是活跃的。
  • authorizationCodeServices:在使用授权码授权时定义授权码的服务(AuthorizationCodeServices的实例)。
  • implicitGrantService:在隐式的授权中管理授权状态。
  • tokenGranterTokenGranter的实例(对授权全局控制,如果有配置则会覆盖上述相同属性的配置)。

如果使用XML配置,则授权类型在authorization-server的子节点中配置。

配置URL

AuthorizationServerEndpointsConfigurer类中有一个pathMapping()方法,它接收两个参数:

  • 第一个是默认(框架实现)的URL路径。
  • 第二个是需要自定义的路径(以"/"开头)。

框架默认提供的URL路径如下:/oauth/authorize(授权URL),/oauth/token(获取token的URL),/oauth/confirm_access(用户批准授权的URL),/oauth/error(渲染授权服务中错误的页面URL),/oauth/check_token(资源服务解码token的URL),/oauth/token_key(如果使用JWT,这个URL暴露公钥)。

注:授权URL/oauth/authorize(或者它的映射)需要使用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作为安全过滤器链,对于那些受token保护的请求,你需要让他们的URL路径不与面向用户的过滤器链匹配到。所以一定要有一个在WebSecurityConfigurer之上仅匹配非API资源的请求匹配器。

/oauth/token路径默认是被@Configuration配置的基于HTTP客户端密码认证的Spring OAuth保护,它不在XML中(所以它一定被保护着)。

在使用XML配置时,<authorization-server/>节点有一些属性可以改变默认的URL,但是/check_token必须要启用(即属性check-token-enabled)。

自定义的UI

大多数的授权服务主要是被物理机器所使用,但是有一些资源他们需要发送GET请求到/oauth/confirm_access上获取UI页面以及从/oauth/error中返回HTML响应。在框架中只提供了使用whitelabel的实现,所以授权服务在实际中需要提供他自己的UI,这样它才可以控制页面的样式和内容。你需要做的就是使用Spring MVC@RequestMapping注解进行控制,同时框架默认会提供一个低优先级的转发规则。在/oauth/confirm_access中,你可以使用AuthorizationRequest把所有需要的数据绑定到session中以取得用户的授权(默认的实现是WhitelabelApprovalEndpoint所以你可以从这里开始)。你可以从请求中抓取所有你喜欢的数据去渲染UI页面,这样所有的用户都需要通过POST请求并携带信息回调到/oauth/authorize地址,服务端才知道用户授权与否。请求参数会传递到AuthorizationEndpoint中的UserApprovalHandler,所以你可以在UserApprovalHandler中或多或少的进行数据处理。默认的UserApprovalHandler实现取决于你是否在AuthorizationServerEndpointsConfigurer中配置了ApprovalStore(如果你已经配置,则UserApprovalHandler的实现是ApprovalStoreUserApprovalHandler,否则它的实现是TokenStoreUserApprovalHandler)。UserApprovalHandler的各实现接收的信息:

  • TokenStoreUserApprovalHandler:通过参数user_oauth_approval值(true或false)简单的确认是否授权通过。
  • ApprovalStoreUserApprovalHandler:一个包含"scope.*"作为key的参数set集合(*表示请求的通配scope符)。参数的值可以是"true"或者"approved"(如果用户同意授权),否则就认为用户拒绝了此次授权。只要用户授权某个scope就认为授权成功。

注意:不要忘记用户从页面请求到服务器时别忘记跨域处理,Spring Security默认需要一个名为"_csrf"请求参数(它在request中提供了值)。你可以在Spring Security文档中获取更多信息或者看whitelabel实现指南。

强制SSL

普通的HTTP适合用来测试,但是在生产环境中授权服务需要使用SSL。你可以在一个安全的容器中或者在代理运行服务器后应用程序,如果你设置了代理并且容器配置正确,应用程序将会很好的运行(与OAuth2.0无关)。你可能还希望使用Spring SecurityrequiresChannel()约束来保证程序安全。/authorize请求路径可以作为你应用程序安全的一部分。/token路径有一个标志在AuthorizationServerEndpointsConfigurer中可以通过sslOnly()方法设置。在这两种的情况下的安全设置都是可选的,但是当你设置了任何一个之后,如果Spring Security发现客户端在不安全的通道发起请求它就会把请求重定向到它认为安全的通道。

自定义的错误处理

授权服务中的错误处理使用了标准的Spring MVC的错误处理方式,也就是把注解@ExceptionHandler加在方法上,你也可以提供一个WebResponseExceptionTranslator的实现,这是最好的改变响应内容的方式。获取token的异常渲染委派给了HttpMesssageConverters(可以把它添加到Spring MVC的配置中),而授权页面的错误处理委托给了错误视图(/oauth/error)。对于错误视图框架仅提供了whitelabel的HTML响应,但是为了更好的用户体验,开发者可能还需要提供自定义的实现(只需要在类上加@Controller注解,并在类的某个方法加上@RequestMapping("/oauth/error"))。

用户角色的作用域映射

有时候限制token的作用域是很有用的,不仅仅是对客户端的token作用域限制,还需要根据用户的权限对用户token作用域做限制。如果你在AuthorizationEndpoint中使用DefaultOAuth2RequestFactory那么你可以设置一个标记checkUserScopes=true对作用域进行限制,仅允许那些角色匹配的用户进行访问。你也可以把OAuth2RequestFactory注入到TokenEndpoint中,但它仅仅在你配置了TokenEndpointAuthenticationFilter(你只需要把它在HTTP容器中,但要保证它在BasicAuthenticationFilter之后)时生效。当然,你也可以实现自己的用户作用域映射及配置自己的OAuth2RequestFactoryAuthorizationServerEndpointsConfigurer允许你注入自定义的OAuth2RequestFactory,所以如果你使用@EnableAuthorizationServer配置,那么你就可以使用这个特性实现自己的OAuth2RequestFactory

资源服务器的配置

资源服务器(可以类比于授权服务器或一个单独的应用)服务于被token所保护的资源。Spring OAuth提供了一个Spring Security授权过滤器来实现这个保护机制。你可以在@Configuration注解的类上加@EnableResourceServer开启同时使用ResourceServerConfigurer进行配置(如果需要)。可以配置以下几个属性:

  • tokenServices:token服务的bean(ResourceServerTokenServices的实例)。
  • resourceId:resource的id(可选的,建议配置上,如果有配置则授权服务器将会进行校验)。
  • 其他的资源服务器扩展点(如:tokenExtractor可以获取额外的请求token)。
  • 被保护资源的请求匹配器(默认匹配所有的资源)。
  • 被保护资源的访问规则(默认是"authenticated")。
  • 其他通过Spring SecurityHttpSecurity类配置的被保护资源访问规则。

OAuth2AuthenticationProcessingFilter类型的过滤器上加@EnableResourceServer注解可以配置Spring Security的过滤器链。

如果是XML配置,<resource-server/>元素的id属性值是手动添加的标准Spring Security的servlet过滤器的bean id。

你的ResourceServerTokenServices是授权服务器规范的另一半,如果资源服务器和授权服务器属于同一个应用,那么你要使用DefaultTokenServices,你就不需要考虑太多资源服务器和授权服务器分离的场景,因为它们都是根据框架定义的接口实现,会自动集成。如果你的资源服务器是一个单独的应用,那你必须确保它能与对应的授权服务器配对,并且提供一个ResourceServerTokenServices的实现让资源服务器知道怎么对token进行正确的解码。与授权服务器一样,你通常可以使用DefaultTokenServices并且选择合适的TokenStore(后台存储或者本地存储)。另一个是RemoteTokenServices它是Spring OAuth提供的特性(非标准规范的),它允许资源服务器向授权服务器(/oauth/check_token)发送HTTP请求对token进行解码,如果在资源服务器(每一个请求授权服务器都会进行校验)没有大量的数据传输或者你可以把请求结果进行缓存那么这种方式非常便利。如果要使用/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客户端密码认证所保护的。

配置OAuth-Aware的表达式处理器

你可能需要利用Spring Security基于表达式的访问控制,默认情况下表达式处理器将在@EnableResourceServer注册,表达式包含#oauth2.clientHasRole,#oauth2.clientHasAnyRole,以及#oath2.denyClient它们用于提供对OAuth客户端进行基于角色的访问控制(详见OAuth2SecurityExpressionMethods)。在XML配置中你可以在常规<http/>安全配置的expression-handler元素注册一个表达式处理器。

OAuth 2.0客户端

OAuth客户端作用是访问其他服务器中被OAuth 2.0保护的资源,相关的配置会涉及到用户访问的被保护资源,客户端可能也需要提供一些存储授权码和访问token的机制给用户。

被保护资源的配置

被保护资源(或者叫"远程资源")可以定义为OAuth2ProtectedResourceDetails类型的bean,被保护的资源有以下几个属性:

  • id:资源的id,这个属性只用于客户端查找资源,不用于OAuth协议,它也可以是当前bean的id。
  • clientIdOAuth客户端的id,OAuth客户端的唯一标识符。
  • clientSecret:资源相关的密钥,没有密钥时默认是空的
  • accessTokenUri:获取资源访问token的URI。
  • scope:逗号分隔的字符串,表示资源的作用域,默认情况下不指定作用域。
  • clientAuthenticationScheme:客户端访问token的机制,推荐是"http_basic"以及"from",默认是"http_basic",参见2.1节《OAuth2.0规范》。

不同的授权类型对应着不同的OAuth2ProtectedResourceDetails具体实现(例如:"client_credentials"授权类型实现是ClientCredentialsResource),用户的授权类型需要进一步依赖这个属性:

  • userAuthorizationUri:表示如果用户需要授权才能访问指定的资源则重定向到此URI,注意这个属性不是必须的,它取决于OAuth 2的配置文件是否支持。

在XML配置中,可以使用<resource/>配置OAuth2ProtectedResourceDetails的bean实例,它有对应上述配置的属性。

客户端配置

对于OAuth2.0的客户端而言,配置已经使用@EnableOAuth2Client简化了,只需要做2个设置:

  • 创建一个过滤器的bean(id是oauth2ClientContextFilter)保存当前的请求以及上下文,在请求需要进行身份认证的情况下,它会管理OAuth身份认证URI的重定向。
  • request作用域中创建一个类型是AccessTokenRequest的bean,它会使用授权码向客户端授权,以此来保证每个独立客户端的关系状态不与其他客户端发生冲突。

此过滤器必须接入到应用中(如:使用Servlet的初始化器或使用web.xml配置一个有相同名称的DelegatingFilterProxy)。

AccessTokenRequest必须使用在OAuth2RestTemplate中,像这样:

@Autowired
private OAuth2ClientContext oauth2Context;

@Bean
public OAuth2RestTemplate sparklrRestTemplate() {
	return new OAuth2RestTemplate(sparklr(), oauth2Context);
}

OAuth2ClientContext需要放在session中以保持不同的用户有不同的状态,如果没有它那么你就需要自己在服务器上管理等效的数据结构,把请求映射到对应的用户,并将每个用户的与单独的OAuth2ClientContext实例相关联。

在XML配置中,<client/>元素的id属性值就是Servlet过滤器的bean id,它必须在@Configuration配置中映射到DelegatingFilterProxy(以相同的名称)。

访问被保护资源

一旦你提供了所有的资源配置,你就可以开始访问它们了。建议访问资源的方式是使用Spring 3 RestTemplateSpring SecurityOAuth提供了RestTemplate的扩展,你仅仅需要提供一个OAuth2ProtectedResourceDetails的实例。为了搭配token(使用授权码授权)使用,你需要考虑使用@EnableOAuth2Client配置(XML配置等同于<oauth:rest-template/>)。它会创建一些请求作用域以及session作用域的上下文对象,这样在不同的用户请求中用户状态就不会发生冲突。

一般情况下,web应用程序不会用密码授权,所以如果你更倾向于使用AuthorizationCodeResourceDetails则避免使用ResourceOwnerPasswordResourceDetails。如果你一定要从java客户端中使用密码授权,那就使用相同的机制配置你的OAuth2RestTemplate以及在AccessTokenRequest中添加认证(它是一个Map而且生命周期是短暂的),而不要使用ResourceOwnerPasswordResourceDetails(它是所有的token共享的)。

在客户端持久化存储token

一般客户端不需要存储token,但是如果客户端存了token,就不需要在每次应用程序重启的时候都给客户端重新分配一个token,ClientTokenServices接口定义存储指定用户token所必须的操作,并且已经提供了基于JDBC的实现,如果你要实现在自己的服务的数据库中存储token以及关联授权实例你可以使用它。如果你要使用这个功能,那么你必须给OAuth2RestTemplate提供一个TokenProvider的配置,如:

@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的功能(你需要更改配置以添加你自己的并且有效的客户端id和密钥 -- 他们很容易在Facebook网站中生成)。

Facebook在token请求的响应中包含了token以及token有效期的不规范的JSON串(他们使用expires代替了expires_in)。所以如果你想在你的程序中使用过期时间则必须使用自定义的OAuth2SerializationService手动解码。

原文链接: https://projects.spring.io/spring-security-oauth/docs/oauth2.html
  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

0 条评论

撰写评论