共计 8967 个字符,预计需要花费 23 分钟才能阅读完成。
如何阅读
前几节简单介绍了 OAuth, 以及 spring secuirty OAuth 的一些基本构成。文中有一些关联知识点的引用,如果要深入研究,可以点进去详细了解一下,这样可以加深立即。如果你只想快速浏览 OAuth server 的搭建可以跳过前面几节,直接看下面的代码实例。
什么是 OAuth2
OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。
Oauth2 授权方式
authorization_code(授权码类型,使用三方 qq 登录使用该方式)
implicit(隐式授权类型)
password(资源所有者即用户密码类型,典型应用是微服务里使用 uaa 服务器进行登录认证)
client_credentials(客户端凭据【客户端 ID 以及 Key】类型,典型应用是微服务内部系统与系统之间调用客服端授权)
refresh_token(通过以上授权获得的刷新令牌来获取新的令牌)。
关于 OAuth 不同授权的具体流程可以参考[理解 OAuth 2.0- 阮一峰](http://www.ruanyifeng.com/blo…。
spring Security OAuth2
spring security OAuth 是对 OAuth2 协议的一个实现。是在 spring security 的基础上发展而来,之前是 spring security 的一个子项目,现在已经独立出来。详细见官网.spring OAuth 使用类似 spring secuirty 的机制来实现 OAuth2 . 根据 OAuth 协议规定,一个完整的授权服务器应当包含:授权服务器和资源服务器。授权服务器负责认证和授权,资源服务器负责根据用户提供的授权凭证(比如给以 token)。这里有一篇官方文档介绍了 spring OAuth 的使用.[OAuth 2 Developers Guide](http://projects.spring.io/spr…
授权服务
一个授权服务大致几个模块:client 管理、授权接口、用户认证、令牌管理。
client 管理主要用来管理和区分不同的 client, 我们可以通过配置认证 client 链接是否合法,能为该 client 提供哪些授权服务,个性化定制 client 允许的行为。在 spring secruity OAuth2 中,可以对 client 进行如下属性配置:
clientId:(必须的)用来标识客户的 Id。
secret:(需要值得信任的客户端)客户端安全码,如果有的话。
scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。
authorities:此客户端可以使用的权限(基于 Spring Security authorities)。
授权接口是授权服务对外提供的 http 入口。在 spring 中授权端口如下:
spring security OAuth 授端口
/oauth/authorize:授权端点。对应 AuthorizationEndpoint 类
/oauth/token:令牌端点。对应 TokenEndpoint 类
/oauth/confirm_access:用户确认授权提交端点。对应 WhitelabelApprovalEndpoint 类
/oauth/error:授权服务错误信息端点。对应 WhitelabelApprovalEndpoint 类
/oauth/check_token:用于资源服务访问的令牌解析端点。对应 CheckTokenEndpoint 类
/oauth/token_key:提供公有密匙的端点,如果你使用 JWT 令牌的话。对应 TokenKeyEndpoint 类
授权是使用 AuthorizationEndpoint 这个端点来进行控制的,你能够使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来进行配置,如果你不进行设置的话,默认是除了资源所有者密码(password)授权类型以外,支持其余所有标准授权类型的(RFC6749),我们来看一下这个配置对象有哪些属性可以设置吧,如下列表:
authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。
userDetailsService:如果啊,你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现,或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对象),当你设置了这个之后,那么 “refresh_token” 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。
authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 “authorization_code” 授权码类型模式。
implicitGrantService:这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
tokenGranter:这个属性就很牛 B 了,当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个
用户认证,例如在使用 password 授权模式时,需要在获取令牌之前先校验用户提供的凭证是否合法, 合法的凭证是用户获取授权令牌的前提,spring secuiurty OAuth 使用了 spring security 的认证服务,在令牌获取端口 AuthenticationManager 进行授权,这个会在后面的授权端口中提到。
令牌管理服务负责令牌是生成、校验等操作。
令牌主要有两种解决方案:使用随机算法生成唯一标示与用户授权关联,然后保存起来供校验时查询,为了方便资源服务方便验证令牌,这种方案常常是授权服务和资源服务共存的,如果不共存,那么资源服务的 tokenStore 服务于授权服务 tokenStore 要做到数据互通,spring 的解决方案是提供 /oauth/check_token 接口来完成。第二种是授权服务器使用某种算法生成字符串,资源服务器使用约定好的算法对令牌进行解析校验,以验证 他的合法性。在 spring security OAuth2 中提供了 InMemoryTokenStore、JdbcTokenStore、JwtTokenStore。前面两者需要将令牌存在起来,最后一个 JwtTokenStore 是 jwt 令牌 TokenStore 的实现,他不存储令牌,只根据一定的规则和秘钥验证令牌的合法性。jwt 令牌分为三段:头部信息、playload、签名。每段使用 base64 编码,每段之间使用 ”.” 连接。
资源服务
一个资源服务(可以和授权服务在同一个应用中,当然也可以分离开成为两个不同的应用程序)提供一些受 token 令牌保护的资源,Spring OAuth 提供者是通过 Spring Security authentication filter 来验证过滤器来实现的保护(OAuth2AuthenticationProcessingFilter),你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上来标记应用是一个资源服务器,你可以通过配置 ResourceServerConfigurer 配置对象来进行资源服务器的一些自定义配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性:
tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
resourceId:这个资源服务的 ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。
其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌,也就说,你可以自定义提如何在请求中提取令牌。
请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径。
受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。
其他的自定义权限保护规则通过 HttpSecurity 来进行配置。
@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链。ResourceServerTokenServices 是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题,这通常是很困难的。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。在授权服务器上,你通常可以使用 DefaultTokenServices 并且选择一些主要的表达式通过 TokenStore(后端存储或者本地编码)。RemoteTokenServices 可以作为一个替代,它将允许资源服务器通过 HTTP 请求来解码令牌(也就是授权服务的 /oauth/check_token 端点)。如果你的资源服务没有太大的访问量的话,那么使用 RemoteTokenServices 将会很方便(所有受保护的资源请求都将请求一次授权服务用以检验 token 值),或者你可以通过缓存来保存每一个 token 验证的结果。
spring Security OAuth 配置示例
关于 spring OAuth 的更多配置细节可以参考官方文档:OAuth2 Autoconfig.spring Secuirty OAuth2 配置比较简单,关键还是要理解 OAuth2 协议,以及他的使用场景。
添加依赖
<dependencies>
<!– … other dependency elements … –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
</dependencies>
授权服务配置
授权服务配置主要需要完成以下几个配置:
ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
AuthorizationServerSecurityConfigurer:用来配置令牌端点 (Token Endpoint) 的安全约束.
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)
我们可以继承 AuthorizationServerConfigurerAdapter 并且覆写其中的三个 configure 方法来进行配置。下面是简单的配置 demo,配置来源于官方文档。
@Component
public class CustomAuthorizationServerConfigurer extends
AuthorizationServerConfigurerAdapter {
AuthenticationManager authenticationManager;
public CustomAuthorizationServerConfigurer(
AuthenticationConfiguration authenticationConfiguration
) throws Exception {
this.authenticationManager =
authenticationConfiguration.getAuthenticationManager();
}
@Override
public void configure(
ClientDetailsServiceConfigurer clients
) throws Exception {
// 下面配置了一个简单的客服端
clients.inMemory()// 使用内存保存客服端信息,建议编写自己的 clientService 来配置客服端
.withClient(“client”)// 客服端的 clientid
.authorizedGrantTypes(“password”,”client_credentials”,”refresh_token”)// 支持的授权方式
.secret(“secret”)// 该客服端访问时的密码
.scopes(“all”);//scope 范围
}
@Override
public void configure(
AuthorizationServerEndpointsConfigurer endpoints
) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.tokenStore(tokenStore());
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
以上完成了一个简单的授权服务配置,我们的配置重点放在了 client 的配置上,由于我们为配置资源拥有者(用户),我们无法使用 password 授权,应为用户信不存在,我们无论输入用户名和密码都不正确。因此我暂且只能使用,client_credentials,这个仅仅是 client 授权,不牵扯到用户信息。使用下面命令
curl client:secret@localhost:8080/oauth/token -d grant_type=client_credentials
一切顺利的话我们可以得到 token 相关信息。如果要使用其他比如 password 等用户授权,那么需要在授权服务器配置用户相关信息,并把相关的 userDetailService 配置到 AuthorizationServerEndpointsConfigurer 对象上,上面有提到 endponint 配置相关规则。
资源服务配置
资源服务的作用前面已经介绍过了,资源服务主要关注两个事情,对 token 进行鉴权,根据具体权限包含资源。由于 token 可能是其他服务器生成,因此要做到 ResourceServerTokenServices 与授权服务的 tokenService 相匹配。下面是简单的配置.
@EnableResourceServer
public static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// 注意这个 TokenStore 需要和之前配置的一致
private final TokenStore tokenStore;
public ResourceServerConfiguration(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
// 资源服务器安全规则配置
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(“/api/register”).permitAll()
.antMatchers(“/api/activate”).permitAll()
.antMatchers(“/api/authenticate”).permitAll()
.antMatchers(“/api/account/reset-password/init”).permitAll()
.antMatchers(“/api/account/reset-password/finish”).permitAll()
.antMatchers(“/api/**”).authenticated()
// 配置 scope 权限访问
// .antMatchers(“/api/**”).access(“#oauth2.hasScope(‘read’) or (!#oauth2.isOAuth() and hasRole(‘ROLE_USER’))”);
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(“jhipster-uaa”)
.tokenStore(tokenStore);
}
}
上面是资源服务和授权服务在一台机器上时的配置。由于共享 tokenStore,因此资源服务在验证 token 的时候不会出任何问题。
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
private final OAuth2Properties oAuth2Properties;
public SecurityConfiguration(OAuth2Properties oAuth2Properties) {
this.oAuth2Properties = oAuth2Properties;
}
// 主要配置资源的保护规则
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(“/api/**”).authenticated()
.antMatchers(“/management/health”).permitAll()
.antMatchers(“/management/info”).permitAll()
.antMatchers(“/management/**”).hasAuthority(AuthoritiesConstants.ADMIN);
}
// 配置 tokenstore,资源服务器在进行 token 验证的时候需要,
@Bean
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(OAuth2SignatureVerifierClient signatureVerifierClient) {
return new OAuth2JwtAccessTokenConverter(oAuth2Properties, signatureVerifierClient);
}
}
上面是采用 jwt,资源 / 授权服务分离的配置,以上配置无法和上面的授权服务配置配合使用。若要配置使用,需要修改授权服务的 TokenStore 为 JwtTokenStore,并且在 OAuth2JwtAccessTokenConverter 中需访问授权服务器 /token/access_key 获取 RSA 公钥,该公钥会在 token 验证时用到。