Spring OAuth2.0 提供者实现原理:
Spring OAuth2.0提供者实际上分为:
- 受权服务 Authorization Service.
- 资源服务 Resource Service.
虽然这两个提供者有时候可能存在同一个应用程序中,但在Spring Security OAuth中你能够把
他它们各自放在不一样的应用上,并且你能够有多个资源服务,它们共享同一个中央受权服
务。
全部获取令牌的请求都将会在Spring MVC controller endpoints中进行处理,而且访问受保护
的资源服务的处理流程将会放在标准的Spring Security请求过滤器中(filters)。
下面是配置一个受权服务必需要实现的endpoints:
- AuthorizationEndpoint:用来做为请求者得到受权的服务,默认的URL是/oauth/authorize.
- TokenEndpoint:用来做为请求者得到令牌(Token)的服务,默认的URL是/oauth/token.
下面是配置一个资源服务必需要实现的过滤器:
- OAuth2AuthenticationProcessingFilter:用来做为认证令牌(Token)的一个处理流程过滤器。只有当过滤器经过以后,请求者才能得到受保护的资源。
配置提供者(受权、资源)均可以经过简单的Java注解@Configuration来进行适配,你也可使用基于XML的声明式语法来进行配置,若是你打算这样作的话,那么请使用http://www.springframework.org/schema/security/spring-security-oauth2.xsd来做为XML的schema(即XML概要定义)以及使用http://www.springframework.org/schema/security/oauth2来做为命名空间。
1、受权服务配置:
配置一个受权服务,你须要考虑几种受权类型(Grant Type),不一样的受权类型为客户端(Client)提供了不一样的获取令牌(Token)方式,为了实现并肯定这几种受权,须要配置使用 ClientDetailsService 和 TokenService 来开启或者禁用这几种受权机制。到这里就请注意了,无论你使用什么样的受权类型(Grant Type),每个客户端(Client)都可以经过明确的配置以及权限来实现不一样的受权访问机制。这也就是说,假如你提供了一个支持"client_credentials"的受权方式,并不意味着客户端就须要使用这种方式来得到受权。下面是几种受权类型的列表,具体受权机制的含义能够参见RFC6749(
中文版本):
- authorization_code:受权码类型。
- implicit:隐式受权类型。
- password:资源全部者(即用户)密码类型。
- client_credentials:客户端凭据(客户端ID以及Key)类型。
- refresh_token:经过以上受权得到的刷新令牌来获取新的令牌。
能够用 @EnableAuthorizationServer 注解来配置OAuth2.0 受权服务机制,经过使用@Bean注解的几个方法一块儿来配置这个受权服务。下面我们介绍几个配置类,这几个配置是由Spring建立的独立的配置对象,它们会被Spring传入AuthorizationServerConfigurer中:
- ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你可以把客户端详情信息写死在这里或者是经过数据库来存储调取详情信息。
- AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
- AuthorizationServerEndpointsConfigurer:用来配置受权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
(译者注:以上的配置能够选择继承AuthorizationServerConfigurerAdapter而且覆写其中的三个configure方法来进行配置。)
配置受权服务一个比较重要的方面就是提供一个受权码给一个OAuth客户端(经过 authorization_code 受权类型),一个受权码的获取是OAuth客户端跳转到一个受权页面,而后经过验证受权以后服务器重定向到OAuth客户端,而且在重定向链接中附带返回一个受权码。
若是你是经过XML来进行配置的话,那么可使用 <authorization-server/> 标签来进行配置。
(译者注:想一想如今国内各大平台的社会化登录服务,例如腾讯,用户要使用QQ登陆到某个网站,这个网站是跳转到了腾讯的登录受权页面,而后用户登陆而且肯定受权以后跳转回目标网站,这种受权方式规范在我上面提供的连接*RFC6749*的第4.1节有详细阐述。)
配置客户端详情信息(Client Details):
ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 可以使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),有几个重要的属性以下列表:
- clientId:(必须的)用来标识客户的Id。
- secret:(须要值得信任的客户端)客户端安全码,若是有的话。
- scope:用来限制客户端的访问范围,若是为空(默认)的话,那么客户端拥有所有的访问范围。
- authorizedGrantTypes:此客户端可使用的受权类型,默认为空。
- authorities:此客户端可使用的权限(基于Spring Security authorities)。
客户端详情(Client Details)可以在应用程序运行的时候进行更新,能够经过访问底层的存储服务(例如将客户端详情存储在一个关系数据库的表中,就可使用 JdbcClientDetailsService)或者经过 ClientDetailsManager 接口(同时你也能够实现 ClientDetailsService 接口)来进行管理。
(译者注:不过我并无找到 ClientDetailsManager 这个接口文件,只找到了 ClientDetailsService)
管理令牌(Managing Token):
AuthorizationServerTokenServices 接口定义了一些操做使得你能够对令牌进行一些必要的管理,在使用这些操做的时候请注意如下几点:
- 当一个令牌被建立了,你必须对其进行保存,这样当一个客户端使用这个令牌对资源服务进行请求的时候才可以引用这个令牌。
- 当一个令牌是有效的时候,它能够被用来加载身份信息,里面包含了这个令牌的相关权限。
当你本身建立 AuthorizationServerTokenServices 这个接口的实现时,你可能须要考虑一下使用 DefaultTokenServices 这个类,里面包含了一些有用实现,你可使用它来修改令牌的格式和令牌的存储。默认的,当它尝试建立一个令牌的时候,是使用随机值来进行填充的,除了持久化令牌是委托一个 TokenStore 接口来实现之外,这个类几乎帮你作了全部的事情。而且 TokenStore 这个接口有一个默认的实现,它就是 InMemoryTokenStore ,如其命名,全部的令牌是被保存在了内存中。除了使用这个类之外,你还可使用一些其余的预约义实现,下面有几个版本,它们都实现了TokenStore接口:
- InMemoryTokenStore:这个版本的实现是被默认采用的,它能够完美的工做在单服务器上(即访问并发量压力不大的状况下,而且它在失败的时候不会进行备份),大多数的项目均可以使用这个版本的实现来进行尝试,你能够在开发的时候使用它来进行管理,由于不会被保存到磁盘中,因此更易于调试。
- JdbcTokenStore:这是一个基于JDBC的实现版本,令牌会被保存进关系型数据库。使用这个版本的实现时,你能够在不一样的服务器之间共享令牌信息,使用这个版本的时候请注意把"spring-jdbc"这个依赖加入到你的classpath当中。
- JwtTokenStore:这个版本的全称是 JSON Web Token(JWT),它能够把令牌相关的数据进行编码(所以对于后端服务来讲,它不须要进行存储,这将是一个重大优点),可是它有一个缺点,那就是撤销一个已经受权令牌将会很是困难,因此它一般用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)。另一个缺点就是这个令牌占用的空间会比较大,若是你加入了比较多用户凭证信息。JwtTokenStore 不会保存任何数据,可是它在转换令牌值以及受权信息方面与 DefaultTokenServices 所扮演的角色是同样的。
JWT令牌(JWT Tokens):
使用JWT令牌你须要在受权服务中使用 JwtTokenStore,资源服务器也须要一个解码的Token令牌的类 JwtAccessTokenConverter,JwtTokenStore依赖这个类来进行编码以及解码,所以你的受权服务以及资源服务都须要使用这个转换类。Token令牌默认是有签名的,而且资源服务须要验证这个签名,所以呢,你须要使用一个对称的Key值,用来参与签名计算,这个Key值存在于受权服务以及资源服务之中。或者你可使用非对称加密算法来对Token进行签名,Public Key公布在/oauth/token_key这个URL链接中,默认的访问安全规则是"denyAll()",即在默认的状况下它是关闭的,你能够注入一个标准的 SpEL 表达式到 AuthorizationServerSecurityConfigurer 这个配置中来将它开启(例如使用"permitAll()"来开启可能比较合适,由于它是一个公共密钥)。
若是你要使用 JwtTokenStore,请务必把"spring-security-jwt"这个依赖加入到你的classpath中。
配置受权类型(Grant Types):
受权是使用 AuthorizationEndpoint 这个端点来进行控制的,你可以使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来进行配置(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) ,若是你不进行设置的话,默认是除了资源全部者密码(password)受权类型之外,支持其他全部标准受权类型的(RFC6749),咱们来看一下这个配置对象有哪些属性能够设置吧,以下列表:
- authenticationManager:认证管理器,当你选择了资源全部者密码(password)受权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。
- userDetailsService:若是啊,你设置了这个属性的话,那说明你有一个本身的 UserDetailsService 接口的实现,或者你能够把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对象),当你设置了这个以后,那么 "refresh_token" 即刷新令牌受权类型模式的流程中就会包含一个检查,用来确保这个帐号是否仍然有效,假如说你禁用了这个帐户的话。
- authorizationCodeServices:这个属性是用来设置受权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 "authorization_code" 受权码类型模式。
- implicitGrantService:这个属性用于设置隐式受权模式,用来管理隐式受权模式的状态。
- tokenGranter:这个属性就很牛B了,当你设置了这个东西(即 TokenGranter 接口实现),那么受权将会交由你来彻底掌控,而且会忽略掉上面的这几个属性,这个属性通常是用做拓展用途的,即标准的四种受权模式已经知足不了你的需求的时候,才会考虑使用这个。
在XML配置中呢,你可使用 "authorization-server" 这个标签元素来进行设置。
配置受权端点的URL(Endpoint URLs):
AuthorizationServerEndpointsConfigurer 这个配置对象(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 有一个叫作 pathMapping() 的方法用来配置端点URL连接,它有两个参数:
- 第一个参数:String 类型的,这个端点URL的默认连接。
- 第二个参数:String 类型的,你要进行替代的URL连接。
以上的参数都将以 "/" 字符为开始的字符串,框架的默认URL连接以下列表,能够做为这个 pathMapping() 方法的第一个参数:
- /oauth/authorize:受权端点。
- /oauth/token:令牌端点。
- /oauth/confirm_access:用户确认受权提交端点。
- /oauth/error:受权服务错误信息端点。
- /oauth/check_token:用于资源服务访问的令牌解析端点。
- /oauth/token_key:提供公有密匙的端点,若是你使用JWT令牌的话。
须要注意的是受权端点这个URL应该被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
}
注意:若是你的应用程序中既包含受权服务又包含资源服务的话,那么这里其实是另外一个的低优先级的过滤器来控制资源接口的,这些接口是被保护在了一个访问令牌(access token)中,因此请挑选一个URL连接来确保你的资源接口中有一个不须要被保护的连接用来取得受权,就如上面示例中的 /login 连接,你须要在 WebSecurityConfigurer 配置对象中进行设置。
令牌端点默认也是受保护的,不过这里使用的是基于 HTTP Basic Authentication 标准的验证方式来验证客户端的,这在XML配置中是没法进行设置的(因此它应该被明确的保护)。
在XML配置中可使用 <authorization-server/> 元素标签来改变默认的端点URLs,注意在配置 /check_token 这个连接端点的时候,使用 check-token-enabled 属性标记启用。
强制使用SSL(Enforcing SSL):
使用简单的HTTP请求来进行测试是能够的,可是若是你要部署到产品环境上的时候,你应该永远都使用SSL来保护受权服务器在与客户端进行通信的时候进行加密。你能够把受权服务应用程序放到一个安全的运行容器中,或者你可使用一个代理,若是你设置正确了的话它们应该工做的很好(这样的话你就不须要设置任何东西了)。
可是也许你可能但愿使用 Spring Security 的 requiresChannel() 约束来保证安全,对于受权端点来讲(还记得上面的列表吗,就是那个 /authorize 端点),它应该成为应用程序安全链接的一部分,而对于 /token 令牌端点来讲的话,它应该有一个标记被配置在 AuthorizationServerEndpointsConfigurer 配置对象中,你可使用 sslOnly() 方法来进行设置。固然了,这两个设置是可选的,不过在以上两种状况中,会致使Spring Security 会把不安全的请求通道重定向到一个安全通道中。(译者注:即将HTTP请求重定向到HTTPS请求上)。
自定义错误处理(Error Handling):
端点实际上就是一个特殊的Controller,它用于返回一些对象数据。
受权服务的错误信息是使用标准的Spring MVC来进行处理的,也就是 @ExceptionHandler 注解的端点方法,你也能够提供一个 WebResponseExceptionTranslator 对象。最好的方式是改变响应的内容而不是直接进行渲染。
假如说在呈现令牌端点的时候发生了异常,那么异常委托了 HttpMessageConverters 对象(它可以被添加到MVC配置中)来进行输出。假如说在呈现受权端点的时候未经过验证,则会被重定向到 /oauth/error 即错误信息端点中。whitelabel error (即Spring框架提供的一个默认错误页面)错误端点提供了HTML的响应,可是你大概可能须要实现一个自定义错误页面(例如只是简单的增长一个 @Controller 映射到请求路径上 @RequestMapping("/oauth/error"))。
映射用户角色到权限范围(Mapping User Roles to Scopes):
有时候限制令牌的权限范围是颇有用的,这不只仅是针对于客户端,你还能够根据用户的权限来进行限制。若是你使用 DefaultOAuth2RequestFactory 来配置 AuthorizationEndpoint 的话你能够设置一个flag即 checkUserScopes=true来限制权限范围,不过这只能匹配到用户的角色。你也能够注入一个 OAuth2RequestFactory 到 TokenEnpoint 中,不过这只能工做在 password 受权模式下。若是你安装一个 TokenEndpointAuthenticationFilter 的话,你只须要增长一个过滤器到 HTTP BasicAuthenticationFilter 后面便可。固然了,你也能够实现你本身的权限规则到 scopes 范围的映射和安装一个你本身版本的 OAuth2RequestFactory。AuthorizationServerEndpointConfigurer 配置对象容许你注入一个你自定义的 OAuth2RequestFactory,所以你可使用这个特性来设置这个工厂对象,前提是你使用 @EnableAuthorizationServer 注解来进行配置(见上面介绍的受权服务配置)。
2、资源服务配置:
一个资源服务(能够和受权服务在同一个应用中,固然也能够分离开成为两个不一样的应用程序)提供一些受token令牌保护的资源,Spring OAuth提供者是经过Spring Security authentication filter 即验证过滤器来实现的保护,你能够经过 @EnableResourceServer 注解到一个 @Configuration 配置类上,而且必须使用 ResourceServerConfigurer 这个配置对象来进行配置(能够选择继承自 ResourceServerConfigurerAdapter 而后覆写其中的方法,参数就是这个对象的实例),下面是一些能够配置的属性:
- tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
- resourceId:这个资源服务的ID,这个属性是可选的,可是推荐设置并在受权服务中进行验证。
- 其余的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌。
- 请求匹配器,用来设置须要进行保护的资源路径,默认的状况下是受保护资源服务的所有路径。
- 受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。
- 其余的自定义权限保护规则经过 HttpSecurity 来进行配置。
@EnableResourceServer 注解自动增长了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链,
在XML配置中,使用 <resource-server />标签元素并指定id为一个servlet过滤器就可以手动增长一个标准的过滤器链。
ResourceServerTokenServices 是组成受权服务的另外一半,若是你的受权服务和资源服务在同一个应用程序上的话,你可使用 DefaultTokenServices ,这样的话,你就不用考虑关于实现全部必要的接口的一致性问题,这一般是很困难的。若是你的资源服务器是分离开的,那么你就必需要确保可以有匹配受权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。
在受权服务器上,你一般可使用 DefaultTokenServices 而且选择一些主要的表达式经过 TokenStore(后端存储或者本地编码)。
RemoteTokenServices 能够做为一个替代,它将容许资源服务器经过HTTP请求来解码令牌(也就是受权服务的 /oauth/check_token 端点)。若是你的资源服务没有太大的访问量的话,那么使用RemoteTokenServices 将会很方便(全部受保护的资源请求都将请求一次受权服务用以检验token值),或者你能够经过缓存来保存每个token验证的结果。
使用受权服务的 /oauth/check_token 端点你须要将这个端点暴露出去,以便资源服务能够进行访问,这在我们受权服务配置中已经提到了,下面是一个例子:
@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 即HTTP基自己份验证,使用 client_credentials 受权模式能够作到这一点。
配置OAuth-Aware表达式处理器(OAuth-Aware Expression Handler):
你也许但愿使用 Spring Security's expression-based access control 来得到一些优点,一个表达式处理器会被注册到默认的 @EnableResourceServer 配置中,这个表达式包含了 #oauth2.clientHasRole,#oauth2.clientHasAnyRole 以及 #oauth2.denyClient 所提供的方法来帮助你使用权限角色相关的功能(在 OAuth2SecurityExpressionMethods 中有完整的列表)。
在XML配置中你能够注册一个 OAuth-Aware 表达式处理器即 <expression-handler />元素标签到 常规的 <http /> 安全配置上。