При использовании Spring Security, как правильно получить информацию о текущем имени пользователя (то есть SecurityContext) в бине? - PullRequest
272 голосов
/ 30 октября 2008

У меня есть веб-приложение Spring MVC, которое использует Spring Security. Я хочу знать имя пользователя, вошедшего в систему. Я использую фрагмент кода, приведенный ниже. Это принятый способ?

Мне не нравится иметь вызов статического метода внутри этого контроллера - это противоречит всей цели Spring, IMHO Есть ли способ настроить приложение таким образом, чтобы вместо него вводили текущий SecurityContext или текущую аутентификацию?

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

Ответы [ 17 ]

245 голосов
/ 18 марта 2011

Если вы используете Spring 3 , самый простой способ:

 @RequestMapping(method = RequestMethod.GET)   
 public ModelAndView showResults(final HttpServletRequest request, Principal principal) {

     final String currentUser = principal.getName();

 }
58 голосов
/ 20 мая 2009

Многое изменилось в весеннем мире с тех пор, как на этот вопрос был дан ответ. Spring упростил получение текущего пользователя в контроллере. Для других бинов Spring принял предложения автора и упростил внедрение SecurityContextHolder. Подробнее в комментариях.

<Ч />

Это решение, в котором я остановился. Вместо использования SecurityContextHolder в моем контроллере я хочу внедрить что-то, что использует 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 выглядит так:

<bean id="myController" class="com.foo.FooController">
     ...
  <constructor-arg index="1">
    <bean class="com.foo.SecurityContextHolderFacade">
  </constructor-arg>
</bean>

Кажется более чем глупым, что Spring, контейнер для инъекций зависимостей всех вещей, не предоставил способ внедрить нечто подобное. Я так понял SecurityContextHolder унаследовал от acegi, но все же. Дело в том, что они так близки - если бы у SecurityContextHolder был геттер для получения базового экземпляра SecurityContextHolderStrategy (который является интерфейсом), вы могли бы внедрить его. На самом деле, я даже открыл для этого вопрос Jira .

И последнее: я просто существенно изменил ответ, который у меня был здесь раньше. Проверьте историю, если вам интересно, но, как указал мне коллега, мой предыдущий ответ не будет работать в многопоточной среде. Базовый SecurityContextHolderStrategy, используемый SecurityContextHolder, по умолчанию является экземпляром ThreadLocalSecurityContextHolderStrategy, в котором SecurityContext хранится в ThreadLocal. Следовательно, не обязательно хорошая идея внедрять SecurityContext непосредственно в bean-компонент во время инициализации - его может потребоваться извлекать из ThreadLocal каждый раз в многопоточной среде, так что получается правильный .

21 голосов
/ 26 января 2009

Я согласен с тем, что при запросе 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.
 * <p>
 * Equivalent of calling:
 * <p>
 * <code>getCurrentUser() != null</code>
 * 
 * @return if user is logged in
 */
public static boolean isLoggedIn() {
    return getCurrentUser() != null;
}
21 голосов
/ 19 марта 2012

Чтобы это отображалось на страницах JSP, вы можете использовать Spring Security Tag Lib:

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

Чтобы использовать любой из тегов, у вас должен быть тег безопасности liblib, объявленный в вашем JSP:

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

Затем на странице jsp сделайте что-то вроде этого:

<security:authorize access="isAuthenticated()">
    logged in as <security:authentication property="principal.username" /> 
</security:authorize>

<security:authorize access="! isAuthenticated()">
    not logged in
</security:authorize>

ПРИМЕЧАНИЕ. Как упоминается в комментариях @ SBerg413, вам необходимо добавить

потребительные выражения = "истина"

для тега "http" в конфигурации security.xml, чтобы это работало.

14 голосов
/ 25 мая 2016

Если вы используете Spring Security ver> = 3.2, вы можете использовать аннотацию @AuthenticationPrincipal:

@RequestMapping(method = RequestMethod.GET)
public ModelAndView showResults(@AuthenticationPrincipal CustomUser currentUser, HttpServletRequest request) {
    String currentUsername = currentUser.getUsername();
    // ...
}

Здесь CustomUser - это пользовательский объект, который реализует UserDetails, возвращаемый пользовательским UserDetailsService.

Дополнительную информацию можно найти в главе @ AuthenticationPrincipal справочного документа Spring Security.

13 голосов
/ 08 июля 2010

Я получаю аутентифицированного пользователя 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";
    }
}
8 голосов
/ 03 апреля 2014

В 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) {
  ...
}
5 голосов
/ 01 апреля 2010

Я бы просто сделал это:

request.getRemoteUser();
5 голосов
/ 04 февраля 2010

Да, статика, как правило, плохая, как правило, но в этом случае статика - самый безопасный код, который вы можете написать. Поскольку контекст безопасности связывает принципала с текущим выполняющимся потоком, наиболее безопасный код будет обращаться к статическому потоку из потока как можно напрямую. Скрытие доступа за внедренным классом-оболочкой дает атакующему больше очков для атаки. Им не понадобится доступ к коду (который им будет сложно изменить, если jar был подписан), им просто нужен способ переопределить конфигурацию, что можно сделать во время выполнения или вставить какой-то XML в путь к классам. Даже использование внедрения аннотаций в подписанном коде может быть заменено внешним XML. Такой XML может внедрить в работающую систему мошеннического принципала. Возможно, именно поэтому Spring делает что-то не похожее на Spring в этом случае.

4 голосов
/ 31 октября 2008

В последнем приложении Spring MVC, которое я написал, я не вставлял держатель SecurityContext, но у меня был базовый контроллер, у меня было два вспомогательных метода, связанных с этим ... isAuthenticated () и getUsername (). Внутренне они выполняют статический вызов метода, который вы описали.

По крайней мере, тогда это будет только в одном месте, если вам понадобится последующий рефакторинг.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...