我们有一个Spring项目,它是Spring安全OAuth2客户端和资源服务器。因此,它具有与我们的身份提供者(AWS Cognito)的所有集成部分,授权部分由我们的团队实现。
登录时,Cognito响应三个令牌:ID、访问和刷新令牌。我们将最后两个令牌存储为cookie。
然后,我们还审计创建数据和最后更新日期(对于两个用例——author user和datetime)。为此,我们使用自定义的AuditorAware
。当前版本是这样的:
class PrincipalIdExtractor: AuditorAware<UUID> {
override fun getCurrentAuditor(): Optional<UUID> {
val authentication = SecurityContextHolder.getContext().authentication
return if (authentication == null || !authentication.isAuthenticated)
Optional.empty()
else
Optional.ofNullable(UUID.fromString(authentication.name))
}
}
问题是我们希望避免对数据库的新请求(我们存储来自身份提供者和电子邮件的ID),或者对Cognito的userinfo端点的新请求。
authentication.principal
是一个Jwt,它不带email
声明属性。
解决这个问题的最佳方法是什么?
我不这么认为(找不到做这件事的方法)。但是ID令牌可以自定义。是否可以自定义Cognito的访问令牌并向其添加自定义声明?
作为序言,如果您不确定OAuth2客户端和OAuth2资源服务器的不同安全要求,请花时间阅读我的教程介绍中的OAuth2要点部分。
客户我不会在cookie中存储ID令牌,更不会存储刷新令牌。刷新令牌是非常机密的东西,不应该离开您的服务器。令牌在OAuth2AuthorizedClientRepository
中持久化。默认实现是AuthenticatedPrincipalOAuth2AuthorizedClientRepository
,它使用委托:HttpSessionOAuth2AuthorizedClientRepository
。这意味着默认情况下,令牌(访问、ID和刷新)存储在会话中。当然,这只适用于客户。资源服务器通常是无状态的,不存储令牌。
如果您正在使用k8或任何其他分布式系统,您可能已经使用Spring Session或其他东西来在客户端实例之间共享会话。如果是这样,请求的安全上下文中的OAuth2AuthenticationToken
应该与客户端实例相同,并包含ID令牌(它是在后台使用OAuth2AuthorizedClientRepository
构建的,因此,会话数据)
在安全上下文中从OAuth2AuthenticationToken
读取ID令牌,然后在向应用程序的资源服务器端发出请求之前将其添加到自定义标头(假设X-ID-TOKEN
)中是微不足道的。您甚至可以为此编写一个过滤器:将此自定义标头添加到发送到您信任的资源服务器的每个请求(并且仅这些服务器)。
在你的应用的资源服务器端,你可以配置一个自定义的jwtAuthenticationConverter
。这样的身份验证转换器可以返回JwtAuthenticationToken
以外的其他内容。这意味着除了访问令牌之外,您还可以定义自己的AbstractAuthenticationToken
实现,其中包含ID令牌声明(使用RequestContextHolder.getRequestAttributes()
从自定义头中检索)。
我在本教程中配置了这样一个资源服务器。这个项目使用的是我自定义的Spring Boot启动器之一,但所有东西都是开源的,并且在同一个Github repo中:如果你克隆它,你可以浏览和调试代码来观察会发生什么。
记录用户OpenID声明
关于您的AuditorAware
,它将根据使用的位置从不同的地方获取ID声明:
- 如果日志登录成功&注销事件或发送到资源服务器的请求,它将位于客户端,源可以是安全上下文中的
OAuth2AuthenticationToken
(它已经包含ID令牌,不需要查询OAuth2AuthorizedClientRepository
) - 如果在资源服务器上记录请求,那么在自定义头中发送ID令牌并将其声明添加到自定义
Authentication
实例将是我首选的方式