Spring Session集成到Spring Security中-如何填充身份验证



在将spring-session与spring-security集成时,我不确定当spring session标识会话时应该如何填充SecurityContextImpl#Authentication

上下文:spring-boot应用程序实际上并不处理登录、注销或创建会话本身。会话是在一个外部的非spring微服务中创建的,并通过MongoDB共享。共享和映射会话信息是有效的,并且在春季安全之前没有出现任何问题。

什么有效:

  • Spring会话正确解析会话id
  • Spring会话从会话存储库(mongo)中检索会话(使用会话id),并填充属性
  • 请求有一个填充的会话对象,其中包括所有属性

什么不起作用:

  • 使用http.authorizeRequests().antMatchers("admin/**").authenticated(),然后使用请求和端点(使用会话cookie)决不能填充SecurityContext#Authenticated

可能的选项

a) 我知道,我可以实现一个自定义的CustomUserNameFromSessionFilter来预填充Authenticated在SecureContext中(但已验证=false),并将其放置在SecurityFilter链的早期。此外,我还实现了一个自定义的AuthenticationProviderCustomFromSessionAuthenticationProvider,然后它会拾取Authenticated,并在会话有效的情况下基本上设置authenticated=true(在这一点上总是正确的)

b) 使用RememberMeAuthenticationFilter,但我不确定该文档如何适合

c) 以某种方式使用AbstractPreAuthenticatedProcessingFilter,但它似乎用于外部身份验证请求

每一种选择似乎都不正确,而且这种实施的必要性似乎太普遍,以至于没有现有/更好的解决方案。正确的方法是什么?

代码片段

@Override
protected void configure(HttpSecurity http) throws Exception
{

http.csrf().disable();
// Logout is yet handled by PHP Only, we yet cannot delete/write sessions here
http.logout().disable();
http.formLogin().disable();


http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.authorizeRequests()
.antMatchers("/admin").authenticated();
}

感谢@Steve Riesenberg为我找到正确的解决方案提供了足够的提示!

为了理解我的解决方案并理解何时需要走这条额外的路线,如果首先解释默认集成:

春季安全中的经典春季会议集成

当您使用spring安全性,包括通过spring引导应用程序进行身份验证时,spring会话和spring安全性的集成将变得自然,而不需要任何其他功能。

当您第一次通过spring security的身份验证授权(登录)您的用户时,它将:

  • Authentication对象存储在该请求的SecurityContext中。-然后,SecurityContext将被存储在HttpSession中(如果存在的话),其中的spring会话是您将spring会话配置为(redis/mongo)的地方
  • 使用公共会话数据中的密钥CCD_ 17权限的会话属性来存储CCD_

当您获取此身份验证后提供给您的会话id并发出额外请求时,会发生以下

  • spring-session从存储中加载HttpSession(包括会话属性中的SecurityContext,关键字为SPRING_SECURITY_CONTEXT
  • spring-security将在SecurityFilter链中很早调用HttpSessionSecurityContextRepository,并检查HttpSession是否存在会话属性SPRING_SECURITY_CONTEXT以及是否找到SecurityContext。如果是,则使用该SecurityContext并将其加载为current请求SecurityContext。由于此上下文包含先前身份验证中已通过身份验证的Authentication对象,因此AuthenticationManager/Provider将跳过身份验证,因为它已全部完成,并且您的请求被视为已通过身份认证

这是一种普通的方法,它有一个要求-身份验证过程(登录)需要在登录过程中将SecurityContext写入HttpSession对象。


我的案例-外部登录过程

在我的案例中,一个外部的、非spring引导的微服务正在处理整个登录过程。尽管如此,它还是将会话存储在(外部)会话存储中,在我的例子中就是MongoDB。

Spring会话配置正确,可以使用会话cookie/会话id读取此会话,并加载外部创建的会话。

最大的"但是"是,此外部登录不会在会话数据中存储任何SecurityContext,因为它无法做到这一点。此时,如果您有一个创建会话的外部登录服务,并且它是一个春季启动服务,请确保正确编写SecurityContext。因此,所有其他微服务都可以使用会话id和默认的spring会话/安全集成来正确地加载该会话(经过身份验证)。

由于这对我来说不是一个选项,如果对你来说也不是,以下解决方案似乎是春季启动/安全IMHO中的"设计"方式:

您可以实现自己的CustomHttpSessionSecurityContextRepository,并通过在安全配置中注册

public class ApplicationSecurity extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.securityContext()
.securityContextRepository(new FromExternalSessionSecurityContextRepository());
}
}

这确保了我们用自己的实现替换库存HttpSessionSecurityContextRepository

现在我们的自定义实现

public class CustomExternalSessionSecurityContextRepository implements SecurityContextRepository
{
@Override
public SecurityContext loadContext(final HttpRequestResponseHolder requestResponseHolder)
{
HttpServletRequest request = requestResponseHolder.getRequest();
HttpSession httpSession = request.getSession(false);
// No session yet, thus we cannot load an authentication-context from the session. Create a new, blanc
// Authentication context and let others AuthenticationProviders deal with it.
if (httpSession == null) {
return generateNewSecurityContext();
}
Optional<Long> userId = Optional.ofNullable(httpSession.getAttribute(Attribute.SUBJECT_ID.attributeName))
SecurityContext sc = generateNewSecurityContext();
if (userId.isEmpty()) {
// Return a emtpy context if the session has neither no subjectId
return sc;
}
// This is an session of an authenticated user. Create the security context with the principal we know from
// the session and mark the user authenticated
// OurAuthentication uses userId.get() as principal and implements Authentication
var authentication = new OurAuthentication(userId.get());
authentication.setAuthenticated(true);
sc.setAuthentication(authentication);
httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, sc);
return sc;
}
@Override
public void saveContext(
final SecurityContext context, final HttpServletRequest request, final HttpServletResponse response
)
{
// do implement storage back into HttpSession if you want spring-boot to be
// able to write it. 
}
@Override
public boolean containsContext(final HttpServletRequest request)
{
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
return session.getAttribute(SPRING_SECURITY_CONTEXT_KEY) != null;
}
private SecurityContext generateNewSecurityContext()
{
return SecurityContextHolder.createEmptyContext();
}
}

因此,现在更改了spring-security如何从会话加载SecurityContext的行为。我们不希望SecurityContext已经存在,而是检查会话是否正确,并根据给定的数据创建SecurityContext并存储返回。这将使整个后续链使用并尊重此SecurityContext

最新更新