1 背景
公司内部应用通过CAS提供鉴权和授权服务,实现了单点登录。如果是第三方站点也需要单点登录,那就需要考虑如下问题:
- 不可以向第三方暴露账号密码
- 限制授权第三方应用获取的用户信息和权限范围
这个时候就需要OAuth2协议来帮助我们实现。
2 OAuth2是什么
OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方 应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他 们数据的所有内容。OAuth在全世界得到广泛应用,目前的版本是2.0版。
协议特点:
- 简单:不管是OAuth服务提供者还是应用开发者,都很易于理解与使用;
- 安全:没有涉及到用户密钥等信息,更安全更灵活;
- 开放:任何服务提供商都可以实现OAuth,任何软件开发商都可以使用OAuth;
3 OAuth2的设计思路
OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。"客户端"不能直接登 录"服务提供商",只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层所用的令牌 (token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期,"客户端"登 录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。
OAuth 2.0的运行流程如下图,摘自RFC 6749:
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向授权服务器申请令牌。
(D)授权服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
第三方应用申请令牌之前,都必须先到系统备案,说明自己的身份,然后会拿到两个 身份识别码:客户端 ID(client ID)和客户端密钥(client secret)。这是为了防止令牌被滥用,没有备案过的第三方应用,是不会拿到令牌的。
4. OAuth2的四种认证模式及适用场景
4.1 授权码模式
这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 WEB应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
认证过程:
(1) 客户端访问用户代理,后者将前者重定向到授权服务器 。
(2) 授权服务器认证客户端信息,并确定用户是否给予客户端授权。
(3) 用户给予授权,授权服务器将重定向事先指定的"重定向URI"(redirection URI),同时附上授权码。
(4) 客户端收到授权码,附上早先的"重定向URI",向授权服务器申请令牌。(客户端后台进行,无感知)
(5) 授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access_token)和更新令牌(refresh_token)。
适用场景:目前主流的第三方验证都是采用这种模式
4.2 简化(隐式)模式
有些 Web 应用是纯前端应用,没有后端。这时就不能用上面的方式了,必须将令牌储存在前端,这也增加了一定的风险。RFC 6749就规定了第二种方式,允许直接向前端颁发令牌,这种方式没有授权码这个中间步骤,所以称为(授权码)"隐藏式"(implicit)。
认证过程:
(1)客户端将用户导向授权服务器。
(2)用户决定是否给于客户端授权。
(3)假设用户给予授权,授权服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。
(4)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。
(5)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。
(6)浏览器执行上一步获得的脚本,提取出令牌。
(7)浏览器将令牌发给客户端。
适用场景:针对部分网站不存在后端,只有前端界面。
4.3 密码模式
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而授权服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
认证过程:
(1)用户向客户端提供用户名和密码。
(2)客户端将用户名和密码发给授权服务器,向后者请求令牌。
(3)授权服务器确认无误后,向客户端提供访问令牌。
适用场景:自家公司搭建的授权服务器。
4.4 客户端模式
客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行授权。
认证过程:
(1)客户端向授权服务器进行身份认证,并要求一个访问令牌。
(2)授权服务器确认无误后,向客户端提供访问令牌。
适用场景:没有前端的命令行应用,即在命令行下请求令牌。一般用来提供给我们完全信任的服务器端服务。
4.5 四种模式小结
四种模式时序图
授权码许可类型是流程最完备、安全性最高的授权方式。在选择授权类型时,优先考虑授权码模式,然后结合实际场景选择最适合的类型。
5 Spring Security-OAuth2的实践与挑战
Spring Security OAuth2是Spring Security框架的一个扩展模块,用于实现基于OAuth2协议的身份验证和授权功能。它提供了一套易于使用和集成的API,方便开发者在Spring应用程序中实现OAuth2的各种授权模式和流程,它使得开发者可以轻松地构建安全的OAuth2服务和客户端应用程序。
Spring Security OAuth2整体架构图:
基于实际的业务场景,我们选择了授权码模式实现。客户端在重定向 URI 中收到授权码,然后使用该授权码与授权服务器进行身份验证,并获取访问令牌。作为鉴权和授权平台,安全性是需放在第一位考虑的要素,安全性问题以及防护措施是重中之重,下面将关于Oauth几项安全挑战展开讨论。
5.1 令牌的安全传输
令牌在客户端和服务器之间传输时应进行安全加密,以防止令牌被拦截和篡改。可以使用HTTPS协议来保护令牌的传输安全。
在客户端与服务器建立连接时,客户端发送一个HTTPS请求。服务器会返回一个包含公钥的证书,客户端使用该公钥来加密对称密钥,并将加密后的密钥发送给服务器。服务器使用私钥解密对称密钥,并与客户端建立安全连接。可以通过配置Spring Security来启用HTTPS。首先,需要生成SSL证书,并将其配置到应用程序中。然后,在Spring Security的配置类中添加以下代码:
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.requiresChannel() .anyRequest().requiresSecure(); }}
5.2 令牌的保密性
令牌应仅由授权服务器和资源服务器持有,并且不应通过客户端或其他不受信任的渠道传播。客户端应采取适当的安全措施,如存储令牌时进行加密处理。
Spring Security OAuth2可以在授权服务器和资源服务器中,配置加密算法和密钥来对令牌进行加密处理。授权服务器配置:
@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // 密钥存储在数据库或配置文件中 private static final String SECRET_KEY = "your-secret-key"; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients.inMemory() .withClient("your-client-id") .secret(SECRET_KEY) .authorizedGrantTypes("password", "refresh_token") .scopes("read", "write") .accessTokenValiditySeconds(3600) .refreshTokenValiditySeconds(86400); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore()); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(accessTokenConverter()); } @Bean public JwtAccessTokenConverter accessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setSigningKey(SECRET_KEY); return converter; }}
资源服务器配置:
@Configuration@EnableResourceServerpublic class ResourceServerConfig extends ResourceServerConfigurerAdapter { @Override public void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/api/**").authenticated(); }}
5.3 令牌的有效期管理
令牌应具有适当的有效期,以限制其使用时间。令牌有效期过长可能导致安全隐患。
授权服务器应定期检查和清理过期的令牌,并提供令牌刷新机制,使客户端能够获取新的令牌。
Spring Security OAuth2可以通过配置来管理令牌的有效期:
@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { // ... @Autowired private TokenStore tokenStore; @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.tokenStore(tokenStore) .tokenEnhancer(tokenEnhancerChain()); } @Bean public TokenEnhancerChain tokenEnhancerChain() { TokenEnhancerChain chain = new TokenEnhancerChain(); chain.setTokenEnhancers(Arrays.asList(accessTokenConverter())); return chain; }}
5.4 跨站请求伪造(CSRF)攻击
客户端应采取适当的CSRF防护措施,如使用随机生成的令牌进行请求验证,以防止恶意站点利用受信任的用户凭据进行攻击。
可以通过Spring Security的CSRF防护功能来防止CSRF攻击:
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); }}
也可以通过在URL后添加随机生成的state参数,state参数能标记这是来自哪一个网站,且 state 参数是攻击者拿不到的,因此可以避免 CSRF 攻击。
5.5 频率限制和访问控制
授权服务器和资源服务器应实施适当的频率限制和访问控制策略,以防止恶意用户或恶意客户端对系统进行滥用和攻击。
在Spring Boot中,限制访问频率有多种方法:
(1)使用Spring Boot内置的应用拦截器:您可以通过实现HandlerInterceptor接口并将其注册为拦截器来实现该功能。
(2)使用Spring AOP:您可以使用Spring AOP创建切面并对特定的接口进行频率限制。
(3)使用Guava的RateLimiter:您可以使用Guava的RateLimiter类对特定的接口进行频率限制。
(4)使用第三方工具:您可以使用第三方工具(如Redis)来实现该功能。
具体实现方法取决于您的需求和项目特征。您可以根据您的需求选择最合适的方法。
5.6 安全审计和监控
系统应具备安全审计和监控机制,记录和监测与令牌相关的活动,以及检测和响应潜在的安全事件。
这里可以使用Spring Boot Actuator和其他安全审计工具来实现安全审计和监控。首先,添加所需的依赖项到项目的pom.xml文件中:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency>
然后,在application.properties或application.yml文件中配置安全审计和监控:
management.endpoints.web.exposure.include=*management.endpoint.health.show-details=alwaysmanagement.endpoint.auditevents.enabled=true
这样配置后,可以通过访问/actuator/auditevents端点来获取与令牌相关的审计事件信息。
5.7 安全存储凭证
客户端标识和客户端密钥应该被安全地存储,不应该被直接硬编码在客户端应用程序中。
定期轮换凭证,并限制授权服务器的访问,能够有效减少凭证泄漏带来的风险。
6 小结
通过上面的介绍,相信您对Oauth2.0的概念和如何使用有了初步的了解,希望本文能对您有所帮助。