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



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



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



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

17 ответов:

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

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

     final String currentUser = principal.getName();

 }

многое изменилось в весеннем мире с тех пор, как был дан ответ на этот вопрос. Весна упростила получение текущего пользователя в контроллере. Для других бобов Весна приняла предложения автора и упростила инъекцию "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);
  }

}

и, наконец, конфигурация рабочей пружины выглядит так:

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

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

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

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

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

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

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

use-expressions= "true"

к тегу "http" в безопасности.в xml файле для этой работы.

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

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

весной 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: Fancy one:проверьте это для более подробной информации

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
  ...
}

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

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

request.getRemoteUser();

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

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

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

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

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

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

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

    }

Я использую @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
}

очень легко.

попробуй такое

проверка подлинности проверка подлинности = SecurityContextHolder.getContext ().getAuthentication ();
Строка userName = аутентификация.getName ();

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

Я хотел бы поделиться своим способом поддержки сведений о пользователе на странице freemarker. Все очень просто и работает отлично!

вы просто должны разместить rerequest проверки подлинности на 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");
}

и это мой сверхсветовой код:

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

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

Comments

    Ничего не найдено.