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