本文档阐述了如何在spring OAuth2资源服务器中,针对特定端点实现自定义Token授权方案。重点介绍了利用JWT (json Web Token) 的方式,通过Keycloak配置,将自定义的授权信息添加到Token中,并在资源服务器端进行验证。同时,也探讨了使用客户端凭据流 (Client Credentials Flow) 为受信任的客户端进行授权的方法。
基于JWT的自定义Token授权方案
在标准的OAuth2流程中,资源服务器通常依赖于JWT中的信息进行授权。为了实现特定端点的自定义授权,我们可以利用Keycloak的mapper功能,将额外的授权信息添加到JWT中。
1. 在Keycloak中配置Mapper:
Keycloak的Mapper允许我们在用户登录时,将自定义的信息添加到access token中。例如,我们可以创建一个REST API来暴露订阅数据 (GET请求,返回给定用户订阅了什么,以及有效期至何时)。然后,在Keycloak中声明一个“confidential”客户端,使用client-credentials进行身份验证,并拥有访问此数据的专用角色。接下来,创建一个Keycloak mapper,在用户登录时查询此端点,并将返回的值作为私有声明添加到Access token中。
参考示例项目: https://www.php.cn/link/68090119a695209306eefe7f69ebf574
2. 自定义AbstractAuthenticationToken:
在资源服务器端,我们需要创建一个自定义的AbstractAuthenticationToken实现,用于解析JWT中的自定义声明,并将其用于授权决策。这可以通过覆盖jwtAuthenticationConverter bean来实现,返回我们自定义的AbstractAuthenticationToken实现,而不是默认的JwtAuthenticationToken。
参考示例项目: https://www.php.cn/link/666108a9094a0ec0f62ca61a2eb74538
3. 使用@PreAuthorize进行授权:
有了自定义的AbstractAuthenticationToken,我们就可以在@PreAuthorize注解中使用自定义的表达式进行授权。例如:
@PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('GREET')")
其中,is(#username)、isNice()、onBehalfOf(#username).can(‘GREET’)都是自定义的表达式,它们可以访问自定义AbstractAuthenticationToken中包含的信息,并进行授权决策。
使用Client Credentials Flow进行授权
对于不需要用户上下文的受信任客户端,我们可以使用Client Credentials Flow进行授权。
1. 在Keycloak中配置客户端:
在Keycloak中,为每个受信任的客户端声明一个“confidential”客户端,并启用client credentials。为这些客户端分配所需的角色。
2. 客户端获取Access Token:
客户端使用client credentials向授权服务器请求access token,并将access token作为Bearer authorization header发送给资源服务器。
3. 资源服务器验证Access Token:
从资源服务器的角度来看,所有请求都将使用相同的授权服务器颁发的access token进行授权,没有区别。
示例代码 (spring security配置):
@EnableWebSecurity class WebSecurityConfiguration { @Bean fun filterChain(http: httpsecurity): SecurityFilterChain { http.authorizeRequests() .antMatchers("/actuator/health").permitAll() .antMatchers("/api/custom/players").access("hasAuthority('ROLE_PLAYERS')") // 使用角色进行授权 .antMatchers("/**").hasAnyRole("User", "Client") .anyRequest().authenticated() .and() .oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()) return http.build() } private fun jwtAuthenticationConverter(): Converter<Jwt?, out AbstractAuthenticationToken?> { val jwtConverter = JwtAuthenticationConverter() jwtConverter.setJwtGrantedAuthoritiesConverter(KeycloakRealmRoleConverter()) return jwtConverter } }
注意事项:
- 确保Keycloak中的Mapper配置正确,能够将所需的授权信息添加到JWT中。
- 自定义AbstractAuthenticationToken的实现需要考虑到性能和安全性。
- 在使用Client Credentials Flow时,需要仔细配置客户端的权限,以防止未授权访问。
总结:
通过结合Keycloak的Mapper功能和Spring Security的@PreAuthorize注解,我们可以实现灵活的自定义Token授权方案,满足不同场景下的授权需求。无论是基于用户上下文的授权,还是基于客户端凭据的授权,都可以通过本文介绍的方法来实现。