java全局获取用户名,java - 使用Spring Security时,获取bean中当前用户名(即SecurityContext)信息的正确方法是什么?...

java - 使用Spring Security时,获取bean中当前用户名(即SecurityContext)信息的正确方法是什么?

我有一个使用Spring Security的Spring MVC Web应用程序。 我想知道当前登录用户的用户名。 我正在使用下面给出的代码段。 这是接受的方式吗?

我不喜欢在这个控制器中调用静态方法 - 这违背了Spring的全部目的,恕我直言。 有没有办法配置应用程序以注入当前的SecurityContext或当前的身份验证?

@RequestMapping(method = RequestMethod.GET)

public ModelAndView showResults(final HttpServletRequest request...) {

final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();

...

}

17个解决方案

242 votes

如果您使用的是Spring 3,最简单的方法是:

@RequestMapping(method = RequestMethod.GET)

public ModelAndView showResults(final HttpServletRequest request, Principal principal) {

final String currentUser = principal.getName();

}

tsunade21 answered 2019-02-06T10:07:34Z

55 votes

自从回答这个问题后,Spring世界发生了很多变化。 Spring简化了当前用户在控制器中的使用。 对于其他bean,Spring采用了作者的建议并简化了“SecurityContextHolder”的注入。 更多细节在评论中。

这是我最终选择的解决方案。 我没有在我的控制器中使用SecurityContextHolderStrategy,而是想在引擎盖下注入一些使用SecurityContextHolder的内容,但是从我的代码中抽象掉了那个类似单例的类。 我发现除了滚动我自己的界面之外没办法做到这一点,如下所示:

public interface SecurityContextFacade {

SecurityContext getContext();

void setContext(SecurityContext securityContext);

}

现在,我的控制器(或任何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

}

}

并且,由于接口是解耦点,因此单元测试非常简单。 在这个例子中,我使用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();

}

}

接口的默认实现如下所示:

public class SecurityContextHolderFacade implements SecurityContextFacade {

public SecurityContext getContext() {

return SecurityContextHolder.getContext();

}

public void setContext(SecurityContext securityContext) {

SecurityContextHolder.setContext(securityContext);

}

}

最后,生产Spring配置如下所示:

...

Spring,一个所有东西的依赖注入容器,似乎没有提供注入类似东西的方法,这似乎有点愚蠢。 我理解SecurityContextHolderStrategy是从acegi继承的,但仍然。 问题是,他们是如此接近 - 如果只有SecurityContextHolder有一个getter来获取底层ThreadLocalSecurityContextHolderStrategy实例(这是一个接口),你可以注入它。 事实上,我甚至打开了一个Jira问题。

最后一件事 - 我刚刚改变了我之前的答案。 如果你很好奇,请查看历史记录但是,正如同事指出的那样,我之前的回答在多线程环境中不起作用。 SecurityContextHolder使用的底层SecurityContextHolderStrategy默认情况下是ThreadLocalSecurityContextHolderStrategy的实例,它在ThreadLocal中存储SecurityContext。因此,在初始化时将SecurityContext直接注入bean不一定是个好主意 - 可能需要检索它 从ThreadLocal每次,在多线程环境中,所以检索正确的一个。

Scott Bale answered 2019-02-06T10:08:44Z

21 votes

我同意不得不为当前用户查询SecurityContext,这似乎是一种处理这个问题的非Spring方式。

我写了一个静态的“帮助器”类来处理这个问题; 它很脏,因为它是一个全局和静态的方法,但我想这样,如果我们改变任何与安全相关的东西,至少我只需要在一个地方改变细节:

/**

* 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.

*

* Equivalent of calling:

*

* getCurrentUser() != null

*

* @return if user is logged in

*/

public static boolean isLoggedIn() {

return getCurrentUser() != null;

}

matt b answered 2019-02-06T10:09:12Z

21 votes

要使它只显示在JSP页面中,您可以使用Spring Security Tag Lib:

[http://static.springsource.org/spring-security/site/docs/3.0.x/reference/taglibs.html]

要使用任何标记,必须在JSP中声明安全性标记库:

然后在jsp页面中执行以下操作:

logged in as

not logged in

注意:如@ SBerg413的评论中所述,您需要添加

使用表达式=“真”

到security.xml配置中的“http”标记,以使其工作。

Brad Parks answered 2019-02-06T10:10:13Z

14 votes

如果您使用的是Spring Security ver> = 3.2,则可以使用CustomUser注释:

@RequestMapping(method = RequestMethod.GET)

public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {

String currentUsername = currentUser.getUsername();

// ...

}

这里,CustomUser是一个自定义对象,它实现了自定义UserDetailsService返回的UserDetails。

可以在Spring Security参考文档的@AuthenticationPrincipal章节中找到更多信息。

matsev answered 2019-02-06T10:10:47Z

13 votes

我通过身份验证用户HttpServletRequest.getUserPrincipal();

例:

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";

}

}

digz6666 answered 2019-02-06T10:11:11Z

8 votes

在Spring 3+中,您有以下选项。

选项1 :

@RequestMapping(method = RequestMethod.GET)

public String currentUserNameByPrincipal(Principal principal) {

return principal.getName();

}

选项2:

@RequestMapping(method = RequestMethod.GET)

public String currentUserNameByAuthentication(Authentication authentication) {

return authentication.getName();

}

选项3:

@RequestMapping(method = RequestMethod.GET)

public String currentUserByHTTPRequest(HttpServletRequest request) {

return request.getUserPrincipal().getName();

}

选项4:花哨的一个:查看更多细节

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {

...

}

Farm answered 2019-02-06T10:11:53Z

5 votes

是的,静态通常很糟糕 - 通常,但在这种情况下,静态是您可以编写的最安全的代码。 由于安全上下文将Principal与当前运行的线程相关联,因此最安全的代码将尽可能直接地从线程访问静态。 隐藏注入的包装类后面的访问权限会为攻击者提供更多攻击点。 他们不需要访问代码(如果jar被签名,他们将很难改变它们),他们只需要一种覆盖配置的方法,这可以在运行时完成或将一些XML滑入类路径。 即使在签名代码中使用注释注入也可以使用外部XML进行覆盖。 这样的XML可能会为正在运行的系统注入一个流氓主体。 这可能就是为什么Spring在这种情况下做了类似Spring的事情。

Michael Bushe answered 2019-02-06T10:12:17Z

5 votes

我会这样做:

request.getRemoteUser();

Dan answered 2019-02-06T10:12:39Z

4 votes

对于我写的最后一个Spring MVC应用程序,我没有注入SecurityContext持有者,但我确实有一个基本控制器,我有两个与此相关的实用方法... isAuthenticated()&getUsername()。 在内部,他们执行您描述的静态方法调用。

至少那时如果你需要稍后重构它只在一个地方。

RichH answered 2019-02-06T10:13:07Z

3 votes

你可以使用Spring AOP aproach。例如,如果您有某些服务,则需要了解当前主体。 您可以引入自定义注释,即@Principal,它表示此服务应该是主体依赖的。

public class SomeService {

private String principal;

@Principal

public setPrincipal(String principal){

this.principal=principal;

}

}

然后在您的建议中,我认为需要扩展MethodBeforeAdvice,检查特定服务是否具有@Principal注释并注入主体名称,或者将其设置为“ANONYMOUS”。

Pavel Rodionov answered 2019-02-06T10:13:36Z

2 votes

唯一的问题是即使在使用Spring Security进行身份验证之后,容器中也不存在用户/主体bean,因此依赖注入它将很困难。 在我们使用Spring Security之前,我们将创建一个具有当前Principal的会话范围的bean,将其注入“AuthService”,然后将该Service注入Application中的大多数其他服务。 所以这些服务只需调用authService.getCurrentUser()来获取对象。 如果您在代码中有一个位置,您在会话中获得对同一Principal的引用,则可以将其设置为会话范围的bean上的属性。

cliff.meyers answered 2019-02-06T10:13:59Z

1 votes

如果您使用Spring 3并且需要在控制器中使用经过身份验证的主体,那么最佳解决方案是执行以下操作:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.userdetails.User;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

@Controller

public class KnoteController {

@RequestMapping(method = RequestMethod.GET)

public java.lang.String list(Model uiModel, UsernamePasswordAuthenticationToken authToken) {

if (authToken instanceof UsernamePasswordAuthenticationToken) {

user = (User) authToken.getPrincipal();

}

...

}

Mark answered 2019-02-06T10:14:21Z

1 votes

我在org.springframework.security.core.userdetails.User类以及@ControllerAdvicer注释的类中使用UserActive注释。例:

@ControllerAdvice

public class ControllerAdvicer

{

private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);

@ModelAttribute("userActive")

public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)

{

return currentUser;

}

}

UserActive是我用于登录用户服务的类,并且从org.springframework.security.core.userdetails.User扩展。类似于:

public class UserActive extends org.springframework.security.core.userdetails.User

{

private final User user;

public UserActive(User user)

{

super(user.getUsername(), user.getPasswordHash(), user.getGrantedAuthorities());

this.user = user;

}

//More functions

}

真的很容易

EliuX answered 2019-02-06T10:14:56Z

0 votes

试试这个

验证身份验证=  。SecurityContextHolder.getContext()getAuthentication();

String userName = authentication.getName();

cherit answered 2019-02-06T10:15:26Z

0 votes

将Principal定义为控制器方法中的依赖项,spring将在调用时在方法中注入当前经过身份验证的用户。

Imrank answered 2019-02-06T10:15:48Z

-1 votes

我喜欢在freemarker页面上分享我支持用户详细信息的方式。一切都很简单,工作完美!

您只需要在default-target-url(表单登录后的页面)上放置身份验证重新请求这是该页面的Controler方法:

@RequestMapping(value = "/monitoring", method = RequestMethod.GET)

public ModelAndView getMonitoringPage(Model model, final HttpServletRequest request) {

showRequestLog("monitoring");

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

String userName = authentication.getName();

//create a new session

HttpSession session = request.getSession(true);

session.setAttribute("username", userName);

return new ModelAndView(catalogPath + "monitoring");

}

这是我的ftl代码:

Logged in as ${username!"Anonymous" }

@security.authorize>

就是这样,用户名将在授权后出现在每个页面上。

Serge answered 2019-02-06T10:16:29Z

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值