文章目录
一、什么是OAuth2.0协议
OAuth 2.0 是一种开放标准,用于访问委托(即授权)。它允许第三方应用以安全的方式获取对HTTP服务的有限访问权限,而无需将用户名和密码提供给第三方。OAuth 2.0是OAuth协议的继任者,被设计为简化客户端开发人员的工作,并提供特定于用例的功能,如桌面应用程序、移动电话以及基于浏览器的应用程序。详细说明可查看这个IETF OAuth Working Group.。
OAuth 2.0的核心概念包括:
- 资源所有者Resource Owner:能够授予对其资源访问权限的实体,通常是最终用户。
- 资源服务器Resouce Server:持有受保护资源的服务器,能够接收并响应使用访问令牌进行的请求。
- 客户端Client:代表资源所有者及其自身请求访问受保护资源的应用程序。
- 授权服务器Authorization Server:负责验证资源所有者的身份,并在验证后颁发访问令牌给客户端。
- 访问令牌Access Token:表示对特定范围和持续时间内的资源服务器上的资源的访问权限的字符串。
OAuth2.0的授权流程如下所示:
二、什么又是OIDC协议
OpenID Connect(OIDC)是OAuth 2.0协议之上的一个简单的身份层。它使客户端能够根据授权服务器执行的身份验证来验证最终用户的身份,并以可互操作和类似REST的方式获取有关最终用户的基本配置文件信息。具体协议标准可以点击官网标准说明进行查看。
有些人会问有了OAuth2.0为啥还要OpenID Connect(OIDC)呢,其实OAuth2.0解决的本质问题是授权(Authorization),即允许一个应用访问另一个应用的数据,但它并不直接提供身份验证(Authentication)的功能。因此,我们可以基于OpenID Connect(OIDC)来实现单点登录(SSO)。
三、如何设计应用端的认证鉴权(以SPA为例)
许多新的应用包含一个主要以 JavaScript 编写的单页应用 (SPA) 前端,通常使用 Angular、React 或 Vue 之类的框架。 开发 SPA 时,应使用带有 PKCE 的授权代码流。 此流比不再推荐使用的隐式流更安全。 我们可以按照OIDC协议进行如下认证
其中PKCE 是 Proof Key for Code Exchange 的缩写,PKCE 是一种用于增强授权码模式安全性的方法,它可以防止恶意应用程序通过截获授权码和重定向 URI 来获得访问令牌。PKCE 通过将随机字符串(code_verifier)和其 SHA-256 哈希值(code_challenge)与授权请求一起发送,确保访问令牌只能由具有相应 code_verifier 的应用程序使用,保障用户的安全性。
【OAuth 2.0 协议扩展】PKCE 扩展协议:为了解决公开客户端的授权安全问题
public 客户端,其本身没有能力保存密钥信息(恶意攻击者可以通过反编译等手段查看到客户端的密钥 client_secret, 也就可以通过授权码 code 换取 access_token, 到这一步,恶意应用就可以拿着 token 请求资源服务器了)
PKCE 协议本身是对 OAuth 2.0 的扩展, 它和之前的授权码流程大体上是一致的, 区别在于在向授权服务器的 authorize endpoint 请求时,需要额外的 code_challenge 和 code_challenge_method 参数;向 token endpoint 请求时, 需要额外的 code_verifier 参数。最后授权服务器会对这三个参数进行对比验证, 通过后颁发令牌。
最终,考虑到刷新Token的存放问题(不建议Public客户端存放refresh_token),我们在SPA端增加了BFF层,使用BFF层来代理掉相关认证鉴权,如下图所示:
SPA客户端存储access_token即可,其他的clientId,clientSecret及refresh_token等全部存在BFF上。
四、代码实现
1. 实现一个基于OIDC协议的IAM(Spring Authorization Server)
maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
配置类:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
// Apply default security configuration for OAuth2 Authorization Server
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
// Customize OAuth2 Authorization Server configuration
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 10
// Handle unauthenticated requests
http.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
// Protect resources with OAuth2 JWT
http.oauth2ResourceServer((resourceServer) -> resourceServer
.jwt(Customizer.withDefaults())
);
// Enable CORS
http.cors(Customizer.withDefaults());
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/", "/login","/favicon.ico").permitAll()
.anyRequest().authenticated()
)
// Form login handles the redirect to the login page from the
// authorization server filter chain
.formLogin(Customizer.withDefaults());
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails test1 = User.