微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

将 spring 会话与 spring 安全性集成时,我不确定当 spring 会话标识会话时应该如何填充 SecurityContextImpl#Authentication

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

什么有效:

  • Spring session 正确解析 session-id
  • Spring session 从 session-repository(mongo) 中检索 session(使用 session-id)并填充属性
  • 请求有一个包含所有属性的填充会话对象

什么不起作用:

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

可能的选择

a) 我明白,我可以实现一个自定义CustomUserNameFromSessionFilter 来预填充 Authenticated 在 SecureContext 中(但已验证=false)并将其放在 SecurityFilter chain 的早期。此外,我实现了一个自定义AuthenticationProvider CustomFromSessionAuthenticationProvider,然后选择 Authenticated 并在会话有效时基本上设置 authenticated=true(此时始终为真)>

b) 使用 RememberMeAuthenticationFilter,但我不确定这 documentation 是否适合该目的

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-security 中的经典 spring-session 集成

当您通过 spring-boot 应用程序使用包括身份验证在内的 spring-security 时,spring-session 和 spring-security 的集成将变得自然而然,无需任何其他操作。

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

  • Authentication 对象存储在该请求的 SecurityContext 中。 - 然后 SecurityContext 将存储在 HttpSession(如果存在)中,使用 spring-session 将 spring-session 配置为 (redis/mongo)。
  • SecurityContext 使用密钥 SPRING_SECURITY_CONTEXT 的会话属性存储在公共会话数据(序列化)中。

当您在此身份验证后获取给您的 session-id 并发出额外请求时,会发生以下情况

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

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


我的案例 - 外部登录过程

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

Spring-session 配置正确,可以使用 session cookie/session id 读取这个 session,并加载外部创建的 session。

最大的“但是”是,这个外部登录不会在会话数据中存储任何 SecurityContext,因为它不能这样做。 此时 - 如果您有一个创建会话的外部登录服务,并且它是一个 spring-boot 服务,请确保正确编写 SecurityContext。因此,所有其他微服务只需使用会话 ID 和默认的 spring-session/security 集成即可正确加载此会话(经过身份验证)。

因为这对我来说不是一个选择,如果你也没有,下面的解决方案似乎是 spring-boot/security 恕我直言的“设计”方式:

您实现自己的 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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?