Java 使用 Spring Security 时,在 bean 中获取当前用户名(即 SecurityContext)信息的正确方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/248562/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When using Spring Security, what is the proper way to obtain current username (i.e. SecurityContext) information in a bean?
提问by Scott Bale
I have a Spring MVC web app which uses Spring Security. I want to know the username of the currently logged in user. I'm using the code snippet given below . Is this the accepted way?
我有一个使用 Spring Security 的 Spring MVC Web 应用程序。我想知道当前登录用户的用户名。我正在使用下面给出的代码片段。这是公认的方式吗?
I don't like having a call to a static method inside this controller - that defeats the whole purpose of Spring, IMHO. Is there a way to configure the app to have the current SecurityContext, or current Authentication, injected instead?
我不喜欢在这个控制器中调用静态方法 - 这违背了 Spring 的全部目的,恕我直言。有没有办法将应用程序配置为注入当前的 SecurityContext 或当前的身份验证?
@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(final HttpServletRequest request...) {
final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
...
}
采纳答案by tsunade21
回答by RichH
For the last Spring MVC app I wrote, I didn't inject the SecurityContext holder, but I did have a base controller that I had two utility methods related to this ... isAuthenticated() & getUsername(). Internally they do the static method call you described.
对于我写的最后一个 Spring MVC 应用程序,我没有注入 SecurityContext 持有者,但我确实有一个基本控制器,我有两个与此相关的实用程序方法...... isAuthenticated() & getUsername()。在内部,他们执行您描述的静态方法调用。
At least then it's only in once place if you need to later refactor.
至少,如果您以后需要重构,它只会出现一次。
回答by Pavel Rodionov
You could use Spring AOP aproach. For example if you have some service, that needs to know current principal. You could introduce custom annotation i.e. @Principal , which indicate that this Service should be principal dependent.
您可以使用 Spring AOP 方法。例如,如果您有一些服务,则需要知道当前的委托人。您可以引入自定义注释,即 @Principal ,这表明该服务应该是主体相关的。
public class SomeService {
private String principal;
@Principal
public setPrincipal(String principal){
this.principal=principal;
}
}
Then in your advice, which I think needs to extend MethodBeforeAdvice, check that particular service has @Principal annotation and inject Principal name, or set it to 'ANONYMOUS' instead.
然后在我认为需要扩展 MethodBeforeAdvice 的建议中,检查特定服务是否具有 @Principal 注释并注入主体名称,或者将其设置为“ANONYMOUS”。
回答by cliff.meyers
The only problem is that even after authenticating with Spring Security, the user/principal bean doesn't exist in the container, so dependency-injecting it will be difficult. Before we used Spring Security we would create a session-scoped bean that had the current Principal, inject that into an "AuthService" and then inject that Service into most of the other services in the Application. So those Services would simply call authService.getCurrentUser() to get the object. If you have a place in your code where you get a reference to the same Principal in the session, you can simply set it as a property on your session-scoped bean.
唯一的问题是,即使在使用 Spring Security 进行身份验证后,容器中也不存在用户/主体 bean,因此依赖注入它会很困难。在我们使用 Spring Security 之前,我们将创建一个具有当前 Principal 的 session-scoped bean,将它注入到“AuthService”中,然后将该 Service 注入到应用程序中的大多数其他服务中。因此,这些服务将简单地调用 authService.getCurrentUser() 来获取对象。如果您的代码中有一个地方可以在会话中获得对同一 Principal 的引用,则只需将其设置为会话范围 bean 上的属性即可。
回答by matt b
I agree that having to query the SecurityContext for the current user stinks, it seems a very un-Spring way to handle this problem.
我同意必须查询当前用户的 SecurityContext 很糟糕,这似乎是处理这个问题的一种非常非 Spring 的方式。
I wrote a static "helper" class to deal with this problem; it's dirty in that it's a global and static method, but I figured this way if we change anything related to Security, at least I only have to change the details in one place:
我写了一个静态的“helper”类来处理这个问题;它很脏,因为它是一种全局和静态方法,但我认为如果我们更改与安全相关的任何内容,至少我只需要在一个地方更改细节:
/**
* Returns the domain User object for the currently logged in user, or null
* if no User is logged in.
*
* @return User object for the currently logged in user, or null if no User
* is logged in.
*/
public static User getCurrentUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal()
if (principal instanceof MyUserDetails) return ((MyUserDetails) principal).getUser();
// principal object is either null or represents anonymous user -
// neither of which our domain User object can represent - so return null
return null;
}
/**
* Utility method to determine if the current user is logged in /
* authenticated.
* <p>
* Equivalent of calling:
* <p>
* <code>getCurrentUser() != null</code>
*
* @return if user is logged in
*/
public static boolean isLoggedIn() {
return getCurrentUser() != null;
}
回答by Scott Bale
A lot has changed in the Spring world since this question was answered. Spring has simplified getting the current user in a controller. For other beans, Spring has adopted the suggestions of the author and simplified the injection of 'SecurityContextHolder'. More details are in the comments.
自从回答这个问题以来,Spring 世界发生了很多变化。Spring 简化了在控制器中获取当前用户的过程。对于其他bean,Spring采纳了作者的建议,简化了'SecurityContextHolder'的注入。更多细节在评论中。
This is the solution I've ended up going with. Instead of using SecurityContextHolder
in my controller, I want to inject something which uses SecurityContextHolder
under the hood but abstracts away that singleton-like class from my code. I've found no way to do this other than rolling my own interface, like so:
这是我最终采用的解决方案。SecurityContextHolder
我不想在我的控制器中使用,而是想注入一些SecurityContextHolder
在后台使用但从我的代码中抽象出类似单例的类的东西。除了滚动我自己的界面之外,我发现没有办法做到这一点,如下所示:
public interface SecurityContextFacade {
SecurityContext getContext();
void setContext(SecurityContext securityContext);
}
Now, my controller (or whatever POJO) would look like this:
现在,我的控制器(或任何 POJO)看起来像这样:
public class FooController {
private final SecurityContextFacade securityContextFacade;
public FooController(SecurityContextFacade securityContextFacade) {
this.securityContextFacade = securityContextFacade;
}
public void doSomething(){
SecurityContext context = securityContextFacade.getContext();
// do something w/ context
}
}
And, because of the interface being a point of decoupling, unit testing is straightforward. In this example I use Mockito:
而且,由于接口是一个解耦点,单元测试很简单。在这个例子中,我使用 Mockito:
public class FooControllerTest {
private FooController controller;
private SecurityContextFacade mockSecurityContextFacade;
private SecurityContext mockSecurityContext;
@Before
public void setUp() throws Exception {
mockSecurityContextFacade = mock(SecurityContextFacade.class);
mockSecurityContext = mock(SecurityContext.class);
stub(mockSecurityContextFacade.getContext()).toReturn(mockSecurityContext);
controller = new FooController(mockSecurityContextFacade);
}
@Test
public void testDoSomething() {
controller.doSomething();
verify(mockSecurityContextFacade).getContext();
}
}
The default implementation of the interface looks like this:
接口的默认实现如下所示:
public class SecurityContextHolderFacade implements SecurityContextFacade {
public SecurityContext getContext() {
return SecurityContextHolder.getContext();
}
public void setContext(SecurityContext securityContext) {
SecurityContextHolder.setContext(securityContext);
}
}
And, finally, the production Spring config looks like this:
最后,生产 Spring 配置如下所示:
<bean id="myController" class="com.foo.FooController">
...
<constructor-arg index="1">
<bean class="com.foo.SecurityContextHolderFacade">
</constructor-arg>
</bean>
It seems more than a little silly that Spring, a dependency injection container of all things, has not supplied a way to inject something similar. I understand SecurityContextHolder
was inherited from acegi, but still. The thing is, they're so close - if only SecurityContextHolder
had a getter to get the underlying SecurityContextHolderStrategy
instance (which is an interface), you could inject that. In fact, I even opened a Jira issueto that effect.
Spring,一个所有事物的依赖注入容器,没有提供一种注入类似东西的方法,这似乎有点愚蠢。我知道SecurityContextHolder
是从acegi继承的,但仍然如此。问题是,它们非常接近——如果只有SecurityContextHolder
一个 getter 来获取底层SecurityContextHolderStrategy
实例(这是一个接口),你就可以注入它。事实上,我什至为此打开了一个 Jira 问题。
One last thing - I've just substantially changed the answer I had here before. Check the history if you're curious but, as a coworker pointed out to me, my previous answer would not work in a multi-threaded environment. The underlying SecurityContextHolderStrategy
used by SecurityContextHolder
is, by default, an instance of ThreadLocalSecurityContextHolderStrategy
, which stores SecurityContext
s in a ThreadLocal
. Therefore, it is not necessarily a good idea to inject the SecurityContext
directly into a bean at initialization time - it may need to be retrieved from the ThreadLocal
each time, in a multi-threaded environment, so the correct one is retrieved.
最后一件事 - 我刚刚大大改变了我之前在这里的答案。如果您好奇,请查看历史记录,但正如一位同事向我指出的那样,我之前的答案在多线程环境中不起作用。默认情况下,SecurityContextHolderStrategy
所使用的底层SecurityContextHolder
是 的一个实例ThreadLocalSecurityContextHolderStrategy
,它将SecurityContext
s存储在 a 中ThreadLocal
。因此,SecurityContext
在初始化时将直接注入 bean不一定是一个好主意-ThreadLocal
在多线程环境中,可能需要每次从 中检索它,以便检索正确的。
回答by Michael Bushe
Yes, statics are generally bad - generally, but in this case, the static is the most secure code you can write. Since the security context associates a Principal with the currently running thread, the most secure code would access the static from the thread as directly as possible. Hiding the access behind a wrapper class that is injected provides an attacker with more points to attack. They wouldn't need access to the code (which they would have a hard time changing if the jar was signed), they just need a way to override the configuration, which can be done at runtime or slipping some XML onto the classpath. Even using annotation injection in the signed code would be overridable with external XML. Such XML could inject the running system with a rogue principal. This is probably why Spring is doing something so un-Spring-like in this case.
是的,静态通常是不好的 - 通常,但在这种情况下,静态是您可以编写的最安全的代码。由于安全上下文将 Principal 与当前运行的线程相关联,因此最安全的代码将尽可能直接地从线程访问静态。将访问隐藏在注入的包装类后面,为攻击者提供了更多攻击点。他们不需要访问代码(如果 jar 被签名,他们将很难更改代码),他们只需要一种覆盖配置的方法,这可以在运行时完成或将一些 XML 滑到类路径上。即使在签名代码中使用注解注入也可以被外部 XML 覆盖。这样的 XML 可能会向正在运行的系统注入流氓主体。
回答by Dan
I would just do this:
我只会这样做:
request.getRemoteUser();
回答by digz6666
I get authenticated user by HttpServletRequest.getUserPrincipal();
我通过 HttpServletRequest.getUserPrincipal(); 获得经过身份验证的用户;
Example:
例子:
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.support.RequestContext;
import foo.Form;
@Controller
@RequestMapping(value="/welcome")
public class IndexController {
@RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model, HttpServletRequest request) {
if(request.getUserPrincipal() != null) {
String loginName = request.getUserPrincipal().getName();
System.out.println("loginName : " + loginName );
}
model.addAttribute("form", new Form());
return "welcome";
}
}
回答by cherit
Try this
尝试这个
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();
认证身份验证 = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();