Jak uzyskać aktywny UserDetails

W moich kontrolerach, gdy potrzebuję aktywnego (zalogowanego) użytkownika, wykonuję następujące czynności, aby uzyskać implementację UserDetails:

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());
Działa dobrze, ale myślę, że wiosna może ułatwić życie w takim przypadku. Czy istnieje sposób na automatyczne podłączenie UserDetails do kontrolera lub metody?

Na przykład coś w stylu:

public ModelAndView someRequestHandler(Principal principal) { ... }

Ale zamiast dostać UsernamePasswordAuthenticationToken, dostaję UserDetails zamiast?

Szukam eleganckiego rozwiązania. Dowolne pomysły?
Author: PraveenKumar Lalasangi, 2012-01-06

8 answers

Preambuła: od Spring-Security 3.2 jest ładna adnotacja @AuthenticationPrincipal opisana na końcu tej odpowiedzi. Jest to najlepszy sposób, gdy używasz Spring-Security > = 3.2.

Kiedy:

  • użyj starszej wersji Spring-Security,
  • trzeba załadować obiekt użytkownika z bazy danych za pomocą niektórych informacji (takich jak login lub id) przechowywanych w głównym lub
  • chcesz dowiedzieć się, jak HandlerMethodArgumentResolver lub WebArgumentResolver może rozwiązać ten problem w elegancki sposób, lub po prostu chcę poznać tło za @AuthenticationPrincipal i AuthenticationPrincipalArgumentResolver (ponieważ opiera się na HandlerMethodArgumentResolver)

To czytaj dalej-w przeciwnym razie użyj @AuthenticationPrincipal i podziękuj Robowi Winchowi (Autor @AuthenticationPrincipal) i Lukasowi Schmelzeisenowi (za jego odpowiedź).

(BTW: moja odpowiedź jest nieco starsza (styczeń 2012), więc to Lukas Schmelzeisen pojawił się jako pierwszy z bazą @AuthenticationPrincipal adnotation solution na Spring Security 3.2.)


Wtedy możesz użyj w kontrolerze

public ModelAndView someRequestHandler(Principal principal) {
   User activeUser = (User) ((Authentication) principal).getPrincipal();
   ...
}

To jest w porządku, jeśli potrzebujesz go raz. Ale jeśli potrzebujesz go kilka razy jego brzydki, ponieważ zanieczyszcza kontroler ze szczegółami infrastruktury, które normalnie powinny być ukryte przez framework.

Więc to, czego naprawdę chcesz, to mieć taki kontroler:]}
public ModelAndView someRequestHandler(@ActiveUser User activeUser) {
   ...
}

Dlatego wystarczy zaimplementować WebArgumentResolver. Posiada metodę

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

, który pobiera web request (drugi parametr) i musi zwrócić User jeśli its czuje się odpowiedzialny za argument metody (pierwszy parametr).

od wiosny 3.1 pojawiła się nowa koncepcja o nazwie HandlerMethodArgumentResolver. Jeśli używasz Springa 3.1+, powinieneś go użyć. (Opisane jest ono w następnym rozdziale niniejszej odpowiedzi))

public class CurrentUserWebArgumentResolver implements WebArgumentResolver{

   Object resolveArgument(MethodParameter methodParameter, NativeWebRequest webRequest) {
        if(methodParameter is for type User && methodParameter is annotated with @ActiveUser) {
           Principal principal = webRequest.getUserPrincipal();
           return (User) ((Authentication) principal).getPrincipal();
        } else {
           return WebArgumentResolver.UNRESOLVED;
        }
   }
}

Musisz zdefiniować niestandardową adnotację -- możesz ją pominąć, jeśli każda instancja użytkownika powinna być zawsze wzięta z kontekstu bezpieczeństwa, ale nigdy nie jest obiektem polecenia.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ActiveUser {}

W konfiguracja wystarczy tylko dodać:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"
    id="applicationConversionService">
    <property name="customArgumentResolver">
        <bean class="CurrentUserWebArgumentResolver"/>
    </property>
</bean>

@Zobacz: Naucz się dostosowywać argumenty metody Spring MVC @ Controller

należy zauważyć, że jeśli używasz Spring 3.1, polecają HandlerMethodArgumentResolver nad WebArgumentResolver. - zobacz komentarz by Jay


To samo z HandlerMethodArgumentResolver na wiosnę 3.1+

public class CurrentUserHandlerMethodArgumentResolver
                               implements HandlerMethodArgumentResolver {

     @Override
     public boolean supportsParameter(MethodParameter methodParameter) {
          return
              methodParameter.getParameterAnnotation(ActiveUser.class) != null
              && methodParameter.getParameterType().equals(User.class);
     }

     @Override
     public Object resolveArgument(MethodParameter methodParameter,
                         ModelAndViewContainer mavContainer,
                         NativeWebRequest webRequest,
                         WebDataBinderFactory binderFactory) throws Exception {

          if (this.supportsParameter(methodParameter)) {
              Principal principal = webRequest.getUserPrincipal();
              return (User) ((Authentication) principal).getPrincipal();
          } else {
              return WebArgumentResolver.UNRESOLVED;
          }
     }
}

W konfiguracji, musisz dodać to

<mvc:annotation-driven>
      <mvc:argument-resolvers>
           <bean class="CurrentUserHandlerMethodArgumentResolver"/>         
      </mvc:argument-resolvers>
 </mvc:annotation-driven>

@See 3.1 interfejs HandlerMethodArgumentResolver


Spring-Security 3.2 Solution

Spring Security 3.2 (nie mylić z Spring 3.2) ma własne rozwiązanie wbudowane: @AuthenticationPrincipal (org.springframework.security.web.bind.annotation.AuthenticationPrincipal) . To jest ładnie opisane w odpowiedź Lukasa Schmelzeisena

To tylko pisanie

ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
 }

Aby to działało musisz zarejestrować AuthenticationPrincipalArgumentResolver (org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver) : albo poprzez "aktywację" @EnableWebMvcSecurity albo poprzez zarejestrowanie tej fasoli w mvc:argument-resolvers - tak samo jak opisałem to z rozwiązaniem may Spring 3.1 powyżej.

@See Spring Security 3.2 Reference, Rozdział 11.2. @AuthenticationPrincipal


Spring-Security 4.0 Solution

Działa jak rozwiązanie Spring 3.2, ale wiosną 4.0 @AuthenticationPrincipal i AuthenticationPrincipalArgumentResolver zostały "przeniesione" do innego pakietu:

(ale stare klasy w swoich starych paczkach nadal istnieją, więc nie mieszaj ich!)

To tylko pisanie

import org.springframework.security.core.annotation.AuthenticationPrincipal;
ModelAndView someRequestHandler(@AuthenticationPrincipal User activeUser) {
    ...
}

Aby to działało musisz zarejestrować (org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver : albo "aktywując" @EnableWebMvcSecurity, albo rejestrując tę fasolę w mvc:argument-resolvers - tak samo jak opisałem to z rozwiązaniem may Spring 3.1 powyżej.

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
    </mvc:argument-resolvers>
</mvc:annotation-driven>

@See Spring Security 5.0 Reference, Chapter 39.3 @AuthenticationPrincipal

 231
Author: Ralph,
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
2018-04-23 20:34:28

Podczas Gdy Ralphs Answer zapewnia eleganckie rozwiązanie, dzięki Spring Security 3.2 nie musisz już implementować własnego ArgumentResolver.

Jeśli masz UserDetails implementację CustomUser, możesz to zrobić:

@RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {

    // .. find messages for this User and return them...
}

Zobacz Wiosenna Dokumentacja Bezpieczeństwa: @ AuthenticationPrincipal

 67
Author: Lukas Schmelzeisen,
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
2018-03-02 21:14:01

Spring Security jest przeznaczony do pracy z innymi frameworkami nie-Spring, dlatego nie jest ściśle zintegrowany z Spring MVC. Spring Security domyślnie zwraca obiekt {[2] } z metody HttpServletRequest.getUserPrincipal(), więc to otrzymujesz jako główny. Możesz uzyskać swój UserDetails obiekt bezpośrednio z tego za pomocą

UserDetails ud = ((Authentication)principal).getPrincipal()

Zauważ również, że typy obiektów mogą się różnić w zależności od zastosowanego mechanizmu uwierzytelniania (na przykład możesz nie otrzymać UsernamePasswordAuthenticationToken), a Authentication nie musi ściśle zawiera UserDetails. Może to być łańcuch lub dowolny inny typ.

Jeśli nie chcesz wywoływać bezpośrednio SecurityContextHolder, najbardziej eleganckim podejściem (które bym zastosował) jest wstrzyknięcie własnego interfejsu security context accessor, który jest dostosowany do Twoich potrzeb i typów obiektów użytkownika. Tworzenie interfejsu z odpowiednimi metodami, na przykład:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

Możesz zaimplementować to poprzez dostęp do SecurityContextHolder w standardowej implementacji, oddzielając w ten sposób Kod od zabezpieczeń Spring całkowicie. Następnie wstrzyknij to do kontrolerów, które potrzebują dostępu do informacji o zabezpieczeniach lub informacji o bieżącym użytkowniku.

Inną główną korzyścią jest to, że łatwo jest tworzyć proste implementacje ze stałymi danymi do testowania, bez martwienia się o zaludnienie miejsc wątków i tak dalej.

 27
Author: Shaun the Sheep,
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
2012-02-16 19:28:45

Zaimplementuj interfejs HandlerInterceptor, a następnie wprowadź UserDetails do każdego żądania, które ma Model, w następujący sposób:

@Component 
public class UserInterceptor implements HandlerInterceptor {
    ....other methods not shown....
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if(modelAndView != null){
            modelAndView.addObject("user", (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        }
}
 9
Author: atrain,
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
2012-01-09 15:08:45

Począwszy od Spring Security w wersji 3.2, niestandardowa funkcjonalność, która została zaimplementowana przez niektóre starsze odpowiedzi, istnieje po wyjęciu z pudełka w formie adnotacji @AuthenticationPrincipal, która jest wspierana przez AuthenticationPrincipalArgumentResolver.

Prosty przykład jego użycia to:

@Controller
public class MyController {
   @RequestMapping("/user/current/show")
   public String show(@AuthenticationPrincipal CustomUser customUser) {
        // do something with CustomUser
       return "view";
   }
}

CustomUser musi być przypisany z authentication.getPrincipal()

Oto odpowiednie Javadocs z AuthenticationPrincipal i AuthenticationPrincipalArgumentResolver

 9
Author: geoand,
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-02 08:35:43
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}
 5
Author: Comrade,
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
2014-02-19 13:52:24

I jeśli potrzebujesz autoryzowanego użytkownika w szablonach (np. JSP) użyj

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<sec:authentication property="principal.yourCustomField"/>

Razem z

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>
 0
Author: Grigory Kislin,
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-06-05 16:22:40

Możesz spróbować tego: Przy pomocy obiektu uwierzytelniania z Spring możemy uzyskać z niego dane użytkownika w metodzie kontrolera . Poniżej znajduje się przykład przekazania obiektu uwierzytelniania w metodzie kontrolera wraz z argumentem.Po uwierzytelnieniu użytkownika dane są uzupełniane w obiekcie uwierzytelniania.

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}
 0
Author: Mirza Shujathullah,
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
2019-09-27 16:18:53