在使用Spring Security时,如何以适当的方式获取bean中当前用户名(即SecurityContext)信息?

305

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

我不喜欢在控制器内调用静态方法-这破坏了Spring的整个目的,依我看来。 是否有一种方法可以配置应用程序以注入当前SecurityContext或当前Authentication?

  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }

为什么不将控制器(安全控制器)作为超类,从SecurityContext获取用户并将其设置为该类内的实例变量?这样,当您扩展安全控制器时,整个类都将可以访问当前上下文的用户主体。 - Dehan
17个回答

4
对于我最近编写的Spring MVC应用程序,我没有注入SecurityContext持有者,但是我确实有一个基本控制器,其中包含两个与此相关的实用方法... isAuthenticated() & getUsername()。在内部,它们执行了您描述的静态方法调用。
至少如果您需要进行后续重构,那么只需要在一个地方进行修改即可。

2
如果您正在使用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();
            }
            ...

    }

2
当参数已经是UsernamePasswordAuthenticationToken类型时,为什么要进行instanceof UsernamePasswordAuthenticationToken检查? - Scott Bale
(authToken instanceof UsernamePasswordAuthenticationToken) 是 if (authToken !=null) 的功能等效版本。后者可能更简洁,但除此之外没有任何区别。 - Mark

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

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

1

试试这个

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();


4
调用SecurityContextHolder.getContext()静态方法正是我在原问题中抱怨的内容。你还没有回答我的问题。 - Scott Bale
2
然而,这正是文档推荐的做法:http://static.springsource.org/spring-security/site/docs/3.0.x/reference/technical-overview.html那么,避免它能带来什么好处呢?你正在为一个简单的问题寻找一个复杂的解决方案。最好的情况下,你得到相同的行为。最坏的情况下,你会得到一个错误或安全漏洞。 - Bob Kerns
2
@BobKerns 为了测试,注入身份验证比将其放在线程本地更加清晰。 - user41871

1

我在@Controller类和@ControllerAdvicer注释的类中使用@AuthenticationPrincipal注释。例如:

@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
}

非常简单。

-1

我喜欢分享我的支持freemarker页面上用户详细信息的方法。 一切都非常简单,而且完美地运作!

您只需要在default-target-url(表单登录后的页面)上放置身份验证请求即可。 这是我用于该页面的控制器方法:

@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代码:

<@security.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER">
<p style="padding-right: 20px;">Logged in as ${username!"Anonymous" }</p>
</@security.authorize> 

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


谢谢您的回答,但是我想要避免使用静态方法SecurityContextHolder.getContext(),这也是我首先提出这个问题的原因。 - Scott Bale

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接