Podczas korzystania z Spring Security, jaki jest właściwy sposób na uzyskanie informacji o bieżącej nazwie użytkownika (np. SecurityContext)w fasolce?

Mam aplikację webową Spring MVC, która używa Spring Security. Chcę znać nazwę użytkownika aktualnie zalogowanego użytkownika. Używam poniższego fragmentu kodu . Czy to akceptowana droga?

Nie podoba mi się wywołanie statycznej metody wewnątrz tego kontrolera - to niszczy cały cel Wiosny, IMHO. Czy istnieje sposób, aby skonfigurować aplikację tak, aby zamiast tego wstrzykiwano bieżący SecurityContext lub bieżące uwierzytelnianie?
  @RequestMapping(method = RequestMethod.GET)
  public ModelAndView showResults(final HttpServletRequest request...) {
    final String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
    ...
  }
Author: dur, 2008-10-30

17 answers

Jeśli używasz Spring 3 , najprostszym sposobem jest:

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

     final String currentUser = principal.getName();

 }
 239
Author: tsunade21,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-29 12:10:15

Wiele się zmieniło w świecie wiosny od czasu odpowiedzi na to pytanie. Spring uprościł pobranie bieżącego użytkownika do kontrolera. W przypadku innych fasoli Spring przyjął sugestie autora i uprościł zastrzyk "SecurityContextHolder". Więcej szczegółów w komentarzach.


To jest rozwiązanie, które wybrałem. Zamiast używać SecurityContextHolder w moim kontrolerze, chcę wstrzyknąć coś, co używa SecurityContextHolder pod maskę, ale usuwa to singleton-jak klasa z mojego kodu. Nie znalazłem innego sposobu, aby to zrobić, niż zwijanie własnego interfejsu, w ten sposób:

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

Teraz mój kontroler (czy jak tam tam POJO) wyglądałby tak:

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
  }

}

A ze względu na to, że interfejs jest punktem odsprzęgania, testy jednostkowe są proste. W tym przykładzie używam 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();
  }

}

Domyślna implementacja interfejsu wygląda następująco:

public class SecurityContextHolderFacade implements SecurityContextFacade {

  public SecurityContext getContext() {
    return SecurityContextHolder.getContext();
  }

  public void setContext(SecurityContext securityContext) {
    SecurityContextHolder.setContext(securityContext);
  }

}

I wreszcie konfiguracja production Spring wygląda jak to:

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

Wydaje się bardziej niż trochę głupie, że Spring, Pojemnik iniekcji zależności wszystkich rzeczy, nie dostarczył sposobu, aby wstrzyknąć coś podobnego. Rozumiem, że została odziedziczona po acegi, ale i tak. Rzecz w tym, że są tak blisko-gdyby tylko SecurityContextHolder miał getter, aby uzyskać podstawową instancję SecurityContextHolderStrategy (która jest interfejsem), można by to wstrzyknąć. W rzeczywistości, nawet otworzyłem problem Jira w tym celu.

Jeszcze jedna rzecz - po prostu zmieniłem odpowiedź, którą miałem tu wcześniej. Sprawdź historię, jeśli jesteś ciekawy, ale, jak wskazał mi współpracownik, moja poprzednia odpowiedź nie zadziała w środowisku wielowątkowym. Bazowa SecurityContextHolderStrategy używana przez SecurityContextHolder jest domyślnie instancją ThreadLocalSecurityContextHolderStrategy, która przechowuje SecurityContext s w ThreadLocal. Dlatego nie jest koniecznie dobrym pomysłem wstrzykiwanie SecurityContext bezpośrednio do fasoli w czasie inicjalizacji - może być konieczne pobranie z ThreadLocal za każdym razem, w środowisku wielowątkowym, więc poprawny został odzyskany.

 55
Author: Scott Bale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-16 18:09:19

Zgadzam się, że odpytywanie SecurityContext dla bieżącego użytkownika śmierdzi, wydaje się bardzo Nie-wiosennym sposobem radzenia sobie z tym problemem.

Napisałem statyczną klasę" helper", aby poradzić sobie z tym problemem; jest brudny, ponieważ jest to metoda globalna i statyczna, ale pomyślałem, że jeśli zmienimy coś związanego z bezpieczeństwem, przynajmniej będę musiał zmienić szczegóły w jednym miejscu:

/**
* 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
Author: matt b,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-26 16:57:37

Aby po prostu pokazać się na stronach JSP, możesz użyć znacznika Spring Security Lib:

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

Aby użyć któregokolwiek z tagów, musisz mieć zadeklarowany tag bezpieczeństwa w JSP:

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

Następnie na stronie jsp zrób coś takiego:

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

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

Uwaga: Jak wspomniano w komentarzach przez @SBerg413, musisz dodać

Use-expressions= "true"

Do tagu "http" w zabezpieczeniach.XML config to działa.

 21
Author: Brad Parks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-21 14:00:56

I get authenticated user by HttpServletRequest.getUserPrincipal ();

Przykład:

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";
    }
}
 13
Author: digz6666,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-08 03:39:46

Jeśli używasz Spring Security Ver >= 3.2, możesz użyć adnotacji @AuthenticationPrincipal:

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

Tutaj, {[2] } jest obiektem niestandardowym, który implementuje UserDetails, który jest zwracany przez niestandardowy UserDetailsService.

Więcej informacji można znaleźć w rozdziale @ AuthenticationPrincipal W dokumentach źródłowych Spring Security reference docs.

 13
Author: matsev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-01 20:32:27

W Spring 3+ masz następujące opcje.

Wariant 1:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserNameByPrincipal(Principal principal) {
    return principal.getName();
}

Opcja 2:

@RequestMapping(method = RequestMethod.GET)
public String currentUserNameByAuthentication(Authentication authentication) {
    return authentication.getName();
}

Wariant 3:

@RequestMapping(method = RequestMethod.GET)    
public String currentUserByHTTPRequest(HttpServletRequest request) {
    return request.getUserPrincipal().getName();

}

Opcja 4: Fancy one: sprawdź to, aby uzyskać więcej szczegółów

public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
  ...
}
 8
Author: Farm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:59

Tak, statyka jest ogólnie zła-ogólnie, ale w tym przypadku statyka jest najbezpieczniejszym kodem, jaki możesz napisać. Ponieważ kontekst bezpieczeństwa wiąże głównego z aktualnie działającym wątkiem, najbezpieczniejszy kod uzyskałby dostęp do statyki z wątku tak bezpośrednio, jak to możliwe. Ukrywanie dostępu za klasą opakowującą, która jest wstrzykiwana, zapewnia atakującemu więcej punktów do ataku. Nie potrzebowaliby dostępu do kodu (który trudno by im było zmienić, gdyby jar był signed), potrzebują tylko sposobu, aby nadpisać konfigurację, co można zrobić w czasie wykonywania lub wsunąć jakiś XML na classpath. Nawet użycie adnotacji w podpisanym kodzie byłoby nadpisywalne za pomocą zewnętrznego XML. Taki XML mógłby wprowadzić działający system z nieuczciwym principalem. Pewnie dlatego Wiosna robi coś tak nie-wiosennego jak w tym przypadku.

 5
Author: Michael Bushe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-04 13:03:56

Zrobiłbym to:

request.getRemoteUser();
 5
Author: Dan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-22 08:09:19

Dla ostatniej aplikacji Spring MVC, którą napisałem, nie wstrzyknąłem posiadacza SecurityContext, ale miałem kontroler bazowy, że miałem dwie metody użytkowe związane z tym ... isAuthenticated () & getUsername (). Wewnętrznie wykonują opisane przez Ciebie statyczne wywołanie metody.

Przynajmniej wtedy jest tylko w jednym miejscu, jeśli trzeba później refaktor.

 4
Author: RichH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-30 23:08:59

Przydałby się wiosenny aproach. Na przykład, jeśli masz jakąś usługę, która musi znać obecnego dyrektora. Możesz wprowadzić niestandardowe adnotacje, np. @ Principal, które wskazują, że ta usługa powinna być zależna od głównego.

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

Następnie w poradach, które moim zdaniem wymagają rozszerzenia MethodBeforeAdvice, sprawdź, czy dana usługa ma adnotację @ Principal i wprowadź nazwę główną, lub ustaw ją na "anonimową".

 3
Author: Pavel Rodionov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-31 13:03:48

Jedyny problem polega na tym, że nawet po uwierzytelnieniu Spring Security, user / principal bean nie istnieje w kontenerze, więc wstrzykiwanie zależności będzie trudne. Zanim użyliśmy Spring Security, stworzyliśmy fasolę o zasięgu sesji, która miała aktualną funkcję główną, wstrzyknęliśmy ją do "AuthService", a następnie wstrzyknęliśmy tę usługę do większości innych usług w aplikacji. Więc te usługi po prostu nazywają authService.getCurrentUser (), aby uzyskać obiekt. Jeśli masz umieść w swoim kodzie odniesienie do tego samego zleceniodawcy w sesji, możesz po prostu ustawić go jako właściwość na swojej sesji-scoped bean.

 2
Author: cliff.meyers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-29 07:41:18

Najlepszym rozwiązaniem, jeśli używasz Springa 3 i potrzebujesz uwierzytelnionego głównego w kontrolerze, jest zrobienie czegoś takiego:

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();
            }
            ...

    }
 1
Author: Mark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-09 22:34:10

Używam @AuthenticationPrincipal adnotacji w klasach @Controller jak również w klasach @ControllerAdvicer adnotowanych. Ex.:

@ControllerAdvice
public class ControllerAdvicer
{
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerAdvicer.class);


    @ModelAttribute("userActive")
    public UserActive currentUser(@AuthenticationPrincipal UserActive currentUser)
    {
        return currentUser;
    }
}

Gdzie UserActive jest klasą, której używam dla usług zalogowanych użytkowników i rozciąga się od org.springframework.security.core.userdetails.User. Coś w stylu:

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
}
Naprawdę proste.
 1
Author: EliuX,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-30 23:40:27

Try this

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

 0
Author: cherit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-18 09:00:36

Zdefiniuj Principal jako zależność w metodzie kontrolera, a spring wprowadzi bieżącego uwierzytelnionego użytkownika do metody podczas wywołania.

 0
Author: Imrank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-10 11:29:11

Lubię dzielić się moim sposobem wspierania danych użytkownika na stronie freemarker. Wszystko jest bardzo proste i działa idealnie!

Wystarczy umieścić Authentication rerequest na default-target-url (strona po formularzu-logowanie) To jest moja metoda kontrolera dla tej strony:

@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");
}

A to jest mój kod ftl:

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

I to wszystko, nazwa użytkownika pojawi się na każdej stronie po autoryzacji.

 -1
Author: Serge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-20 11:46:17