如何填充身份验证

5fjcxozz  于 2021-09-30  发布在  Java
关注(0)|答案(1)|浏览(348)

在将spring会话与Spring Security 集成时,我不确定 SecurityContextImpl#Authentication 应该在spring会话标识会话时填充。
背景:环境 spring-boot 应用程序实际上并不处理登录、注销或创建会话本身。会话是在外部非spring微服务中创建的,并通过mongodb共享。共享和Map会话信息是有效的,并且在Spring Security 之前没有任何问题。
工作原理:
spring会话正确解析会话id
spring会话从会话存储库(mongo)检索会话(使用会话id),并填充属性
请求有一个已填充的会话对象,包括所有属性
什么不起作用:
使用 http.authorizeRequests().antMatchers("admin/**").authenticated() 然后requestion和endpoint(使用会话cookie)绝不会填充 SecurityContext#Authenticated 可能的选择
a) 我明白,我可以实现一个定制 CustomUserNameFromSessionFilter 预先填充 Authenticated 在securecontext中(但authenticated=false),并将其放在securityfilter链的早期。此外,我还实现了一个自定义authenticationprovider CustomFromSessionAuthenticationProvider ,然后它将 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();
  }
ruoxqz4g

ruoxqz4g1#

感谢@steve riesenberg为我提供了足够的提示来找到正确的解决方案!
要了解我的解决方案并了解何时需要执行此额外路线,请首先解释默认集成:

spring security中的经典spring会话集成

当您通过spring boot应用程序使用Spring Security (包括身份验证)时,spring会话和Spring Security 的集成将变得自然,无需任何其他要求。
当您首次通过spring security的身份验证授权(登录)您的用户时,它将:
存储 Authentication 中的对象 SecurityContext 该请求的有效性然后 SecurityContext 然后将存储在 HttpSession (如果存在的话),在您将spring会话配置为(redis/mongo)的地方使用spring会话。
这个 SecurityContext 使用密钥的会话属性存储 SPRING_SECURITY_CONTEXT 在公共会话数据中的右侧(序列化)。
然后,当您获取此身份验证后提供给您的会话id并发出附加请求时,会发生以下情况
spring会话加载 HttpSession 从您的存储(包括 SecurityContext 在会话属性中使用键 SPRING_SECURITY_CONTEXT Spring 保安会打电话来 HttpSessionSecurityContextRepository 很早以前 SecurityFilter 链和检查 HttpSession 是否存在会话属性 SPRING_SECURITY_CONTEXT 如果 SecurityContext 找到了。如果是,它将使用此选项 SecurityContext 并将其作为 current 要求 SecurityContext . 因为此上下文包括已通过身份验证的 Authentication 对象,该 AuthenticationManager/Provider 将跳过身份验证,因为它已全部完成,并且您的请求将被视为已验证。
这是一种普通的方式,它有一个要求——身份验证过程(登录)需要编写 SecurityContext 进入 HttpSession 对象在登录过程中。

我的案例-外部登录过程

在我的例子中,一个外部的、非spring引导的微服务正在处理整个登录过程。尽管如此,它还是将会话存储在(外部)会话存储中,在我的例子中是mongodb。
spring会话已正确配置,可以使用会话cookie/会话id读取此会话,并加载外部创建的会话。
最大的“但是”是,这个外部登录不会存储任何 SecurityContext 在会话数据中,因为它不能这样做。此时,如果您有一个创建会话的外部登录服务,并且它是一个spring引导服务,请确保您编写了 SecurityContext 对。因此,所有其他微服务都可以使用会话id和默认的spring会话/安全集成来正确加载该会话(经过身份验证)。
由于这不是我的选择,如果您没有选择,以下解决方案似乎是spring boot/security 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

相关问题