При использовании Spring Security, каков правильный способ получить текущее имя пользователя (например, SecurityContext) в bean?

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

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

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

Ответ 1

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

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

     final String currentUser = principal.getName();

 }

Ответ 2

В мире Spring многое изменилось, поскольку на этот вопрос был дан ответ. Spring упростил получение текущего пользователя в контроллере. Для других beans, 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 имел getter, чтобы получить базовый экземпляр SecurityContextHolderStrategy (который является интерфейсом), вы можете это добавить. На самом деле, я даже открыл вопрос Jira в этом отношении.

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

Ответ 3

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

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

Чтобы использовать любой из тегов, у вас должен быть указан тег безопасности, указанный в вашем 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, чтобы это работало.

Ответ 4

Я согласен с тем, что при запросе SecurityContext для текущего пользователя воняет, кажется, это очень не-w760 > способ справиться с этой проблемой.

Я написал статический "вспомогательный" класс для решения этой проблемы; он грязный в том, что это глобальный и статический метод, но я понял это, если мы изменили что-либо, связанное с безопасностью, по крайней мере, мне нужно только изменить детали в одном месте:

/**
* 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;
}

Ответ 5

Я получаю аутентифицированный пользователь 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";
    }
}

Ответ 6

Если вы используете 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.

Ответ 7

В 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) {
  ...
}

Ответ 8

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

Ответ 9

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

request.getRemoteUser();

Ответ 10

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

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

Ответ 11

Вы можете использовать Spring AOP aproach. Например, если у вас есть какая-то услуга, которая должна знать текущий директор. Вы можете ввести пользовательскую аннотацию, то есть @Principal, которая указывает, что эта служба должна быть главной зависимой.

public class SomeService {
    private String principal;
    @Principal
    public setPrincipal(String principal){
        this.principal=principal;
    }
}

Затем в вашем совете, который, как мне кажется, необходимо расширить MethodBeforeAdvice, проверьте, что конкретный сервис имеет @Principal аннотацию и вводит основное имя, или вместо этого назначайте его "ANONYMOUS".

Ответ 12

Единственная проблема заключается в том, что даже после аутентификации с помощью Spring Безопасность пользователь/главный bean не существует в контейнере, поэтому вложение в него будет сложным. Прежде чем мы использовали Spring Security, мы создали бы bean с сеансом связи с текущим Принципалом, добавив его в "AuthService", а затем добавим эту Службу в большинство других сервисов приложения. Таким образом, эти Сервисы просто вызовет authService.getCurrentUser(), чтобы получить объект. Если у вас есть место в вашем коде, где вы получаете ссылку на одного и того же Принципала в сеансе, вы можете просто установить его как свойство в своем классе bean.

Ответ 13

Лучшее решение, если вы используете 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();
            }
            ...

    }

Ответ 14

Я использую аннотацию @AuthenticationPrincipal в классах @Controller, а также в аннотированных @ControllerAdvicer. Пример:.

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

Действительно легко.

Ответ 15

Попробуйте это

Аутентификация аутентификации =. SecurityContextHolder.getContext() getAuthentication();
Строка userName = authentication.getName();

Ответ 16

Определите Principal как зависимость в вашем методе контроллера, а spring будет вводить текущий аутентифицированный пользователь в ваш метод при вызове.

Ответ 17

Это старый вопрос, но у нас есть другой способ получить имя пользователя в течение июля-2016 года, используя Spring 4, Boot, Security, OAuth2 и Angular2 Пожалуйста, поправьте меня, если я ошибаюсь, потому что я ищу простое решение.

@RequestMapping("/user")
    public Principal user(Principal principal) {
        if (principal != null) {
            System.out.println("#### Sample Debug Message for token = "
                    + ((OAuth2AuthenticationDetails) ((OAuth2Authentication) principal).getDetails())
                    .getTokenValue());

            String name = (
                    ((Map)((Map.Entry)((LinkedHashMap)((UsernamePasswordAuthenticationToken)((OAuth2Authentication)principal).getUserAuthentication()).getDetails()).entrySet().toArray()[2]).getValue()).entrySet().toArray()[6]).toString();
            System.out.println("##### Debug user name = " + name);
        } else {
            System.out.println("##### found principal = null");
        }
        return principal;
    }

Ответ 18

Мне нравится делиться своим способом поддержки деталей пользователя на странице 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-код:

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

И что он, имя пользователя появится на каждой странице после авторизации.