使用静态方法与Spring Security获取当前用户详细信息

huangapple go评论88阅读模式
英文:

Using static methods with Spring Security to get current user details

问题

我有一个要求,需要获取已登录的当前用户的详细信息。要获取详细信息,我们可以使用 SecurityContextHolder.getContext() 并提取信息。根据,

SecurityContextHolder、SecurityContext 和 Authentication 对象

默认情况下,SecurityContextHolder 使用 ThreadLocal 来存储这些详细信息,这意味着安全上下文始终对同一执行线程中的方法可用。以这种方式使用 ThreadLocal非常安全的,只要在处理当前主体的请求后清除线程即可。当然,Spring Security会自动为您处理这一点,所以不需要担心。

在请求之间存储 SecurityContext

在Spring Security中,存储 SecurityContext 在请求之间的责任落到了 SecurityContextPersistenceFilter,默认情况下,它将上下文存储为 HTTP 请求 之间的 HttpSession 属性。它会在每个请求中将上下文恢复到 SecurityContextHolder,并且在请求完成时清除 SecurityContextHolder

许多其他类型的应用程序(例如,无状态的 RESTful Web 服务)不使用 HTTP 会话,并且会在每次请求时重新认证。然而,重要的是确保在每个请求后清除 SecurityContextHolder,因此仍然需要在链中包含 SecurityContextPersistenceFilter

问题是

我需要在服务层的多个位置获取用户详细信息,以根据用户权限返回信息,因此我们有一个静态方法来返回这些详细信息。该项目包括 REST API 和会话创建策略为 SessionCreationPolicy.STATELESSSecurityContextHolderStrategyThreadLocal。服务层包括 @Transactional

现在考虑对API的多个并发请求,

  • Spring Security如何管理 STATELESS 应用程序的 SecurityContextPersistenceFilter,是否需要手动配置?
  • 使用 SecurityContextHolderStrategy 作为 ThreadLocal 是否可以?
  • 由于 ThreadLocal 策略和静态方法,是否存在获取错误/无效用户详细信息的机会?

环境:

框架:Spring Boot <br>
ORM:Hibernate <br>
数据库:Postgres <br>
架构:单体应用程序(即将迁移到微服务)<be>

更新:

感谢 @Macro,如上所述,对于 SessionCreationPolicy.STATELESS

SessionManagementConfigurer 包括 isStateless() 方法,为无状态策略返回 true。根据此,HTTP 设置为使用 NullSecurityContextRepository 设置共享对象,并且对于请求缓存使用 NullRequestCache。因此,在 HttpSessionSecurityContextRepository 中可能不会出现与静态方法的用户无效/错误详细信息的问题。

  • SecurityContextHolderStrategy 对于 MODE_INHERITABLETHREADLOCAL, MODE_THREADLOCAL 是否会对用户详细信息产生影响,因为不会为 HttpSessionSecurityContextRepository 设置任何值?

代码:

如果是无状态的话:

if (stateless) {
    http.setSharedObject(SecurityContextRepository.class,
        new NullSecurityContextRepository());
}

if (stateless) {
    http.setSharedObject(RequestCache.class, new NullRequestCache());
}

代码:

获取用户详细信息的静态方法

public static Optional<String> getCurrentUserLogin() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
}

private static String extractPrincipal(Authentication authentication) {
    if (authentication == null) {
        return null;
    } else if (authentication.getPrincipal() instanceof UserDetails) {
        UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
        return springSecurityUser.getUsername();
    } else if (authentication.getPrincipal() instanceof String) {
        return (String) authentication.getPrincipal();
    }
    return null;
}

public static Optional<Authentication> getAuthenticatedCurrentUser() {
    log.debug("Request to get authentication for current user");
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return Optional.ofNullable(securityContext.getAuthentication());
}

sessionManagement

.sessionManagement()
     .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

注释

SecurityContextHolderStrategy 作为 ThreadLocal

服务层包括 @Transactional

英文:

I have a requirement to get the details of the current user who has been loggedIn. To get the details, we can use the SecurityContextHolder.getContext() and extract the details. According to,


SecurityContextHolder, SecurityContext and Authentication Objects

By default, the SecurityContextHolder uses a ThreadLocal to store these details, which means that the security context is always available to methods in the same thread of execution. Using a ThreadLocal in this way is quite safe if care is taken to clear the thread after the present principal’s request is processed. Of course, Spring Security takes care of this for you automatically so there is no need to worry about it.


Storing the SecurityContext between requests

In Spring Security, the responsibility for storing the SecurityContext between requests falls to the SecurityContextPersistenceFilter, which by default stores the context as an HttpSession attribute between HTTP requests. It restores the context to the SecurityContextHolder for each request and, crucially, clears the SecurityContextHolder when the request completes

Many other types of applications (for example, a stateless RESTful web service) do not use HTTP sessions and will re-authenticate on every request. However, it is still important that the SecurityContextPersistenceFilter is included in the chain to make sure that the SecurityContextHolder is cleared after each request.


Question is

I required the user details at multiple places at the service layer to return the information based on the user authority, So we have one static method which returns these details. The project consists of REST APIs and session creation policy as SessionCreationPolicy.STATELESS with SecurityContextHolderStrategy as ThreadLocal. The service layer consists of @Transactional.

Now consider the multiple concurrent requests to the APIs,

  • How Spring Security will manage SecurityContextPersistenceFilter for STATELESS applications, is manual configurations are required?
  • Is it okay to use SecurityContextHolderStrategy as ThreadLocal?
  • Are there any chances to get wrong/invalid user details due to the ThreadLocal strategy and static method?

Environment:

Framework: Spring Boot <br>
ORM: Hibernate <br>
Database: Postgres <br>
Architecture: Monolithic (which is going to migrate to microservices) <be>

I will add more details if required


UPDATE:

Thanks @Macro, as mentioned, for SessionCreationPolicy.STATELESS,

SessionManagementConfigurer consist of isStateless() method which return true for stateless policy. Based on that http set the shared object with NullSecurityContextRepository and for request cache NullRequestCache. Hence no value will be available within HttpSessionSecurityContextRepository. So there might not be issue with invalid/wrong details for user with static method

  • Does SecurityContextHolderStrategy have any impact on the user details with MODE_INHERITABLETHREADLOCAL, MODE_THREADLOCAL as no value will be set to shared object with HttpSessionSecurityContextRepository?

Code:

        if (stateless) {
			http.setSharedObject(SecurityContextRepository.class,
					new NullSecurityContextRepository());
		}

        if (stateless) {
			http.setSharedObject(RequestCache.class, new NullRequestCache());
		}

Code:

static method to get user details

    public static Optional&lt;String&gt; getCurrentUserLogin() {
        SecurityContext securityContext = SecurityContextHolder.getContext();
        return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication()));
    }

    private static String extractPrincipal(Authentication authentication) {
        if (authentication == null) {
            return null;
        } else if (authentication.getPrincipal() instanceof UserDetails) {
            UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
            return springSecurityUser.getUsername();
        } else if (authentication.getPrincipal() instanceof String) {
            return (String) authentication.getPrincipal();
        }
        return null;
    }


    public static Optional&lt;Authentication&gt; getAuthenticatedCurrentUser() {
        log.debug(&quot;Request to get authentication for current user&quot;);
        SecurityContext securityContext = SecurityContextHolder.getContext();
        return Optional.ofNullable(securityContext.getAuthentication());
    }

sessionManagement

.sessionManagement()
     .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

Notes

SecurityContextHolderStrategy as ThreadLocal

The service layer consists of @Transactional.

答案1

得分: 2

设置

.sessionManagement()
    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

将会导致Spring Security使用NullSecurityContextRepository,而不是默认的HttpSessionSecurityContextRepository

这是一个简单的实现,它只会在HTTP会话中简单地不保存任何内容,并且对于每个请求,创建一个完全的“新的和空的SecurityContext”,因此没有存储的身份验证等信息。

所以,回答你的第一个问题:

SecurityContextPersistenceFilter将会像使用HttpSessionRepository一样工作,没有其他手动配置需要。

回答你的第二个和第三个问题,关于Threadlocals。

是的,这就是使用它们的整个目的,而且这是默认的策略。因此,每个线程都有自己的SecurityContext / Authentication对象。你正在通过静态方法访问ThreadLocal,因此没有机会访问“错误”的用户详细信息。在你的用例中使用“GlocalSecurityContextHolderStrategy”将会是一个问题,但这不是你正在做的事情。

还要记住,所有这些都与@Transactional无关。

英文:

Setting

.sessionManagement()
     .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

will lead to Spring Security using a NullSecurityContextRepository, instead of the default HttpSessionSecurityContextRepository.

It is a simple implementation, in that it will simply not save anything to the HTTP Session and, for every request, create a completely new and empty SecurityContext, hence with no stored authentication etc.

So, answering your first question:

> SecurityContextPersistenceFilter will just work as with the
> HttpSessionRepository, there's no other manual configuration required.

To answer your second and third question, regarding Threadlocals.

> Yes, that's the whole point of using them and it's the default
> strategy anyway. So, every thread has its own SecurityContext / Authentication objects. You are accessing the ThreadLocal with your static method, hence there's no chance of accessing "wrong" user details. It would be a problem to use the "GlocalSecurityContextHolderStrategy" in your use-case, but that's not what you are doing, anyway.

Also, keep in mind that all of this has nothing to do with @Transactionals.

huangapple
  • 本文由 发表于 2020年5月2日 14:54:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/61555559.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定