Come ottenere i dettagli utente dell'utente attivo


170

Nei miei controller, quando ho bisogno dell'utente attivo (connesso), sto facendo quanto segue per ottenere la mia UserDetailsimplementazione:

User activeUser = (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.debug(activeUser.getSomeCustomField());

Funziona bene, ma penso che la primavera potrebbe semplificare la vita in un caso come questo. C'è un modo per avere l' UserDetailsautowired nel controller o nel metodo?

Ad esempio, qualcosa del tipo:

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

Ma invece di ottenere il UsernamePasswordAuthenticationToken, ottengo UserDetailsinvece un ?

Sto cercando una soluzione elegante. Qualche idea?

Risposte:


226

Preambolo: da Spring-Security 3.2 c'è una bella annotazione @AuthenticationPrincipaldescritta alla fine di questa risposta. Questo è il modo migliore per utilizzare Spring-Security> = 3.2.

Quando tu:

  • utilizzare una versione precedente di Spring-Security,
  • è necessario caricare l'oggetto utente personalizzato dal database mediante alcune informazioni (come il login o l'id) archiviate nell'oggetto principale o
  • vuoi imparare come a HandlerMethodArgumentResolvero WebArgumentResolverpuoi risolverlo in modo elegante, o semplicemente vuoi imparare lo sfondo dietro @AuthenticationPrincipale AuthenticationPrincipalArgumentResolver(perché è basato su a HandlerMethodArgumentResolver)

poi continua a leggere - altrimenti usa @AuthenticationPrincipale ringrazia solo Rob Winch (autore di @AuthenticationPrincipal) e Lukas Schmelzeisen (per la sua risposta).

(A proposito: la mia risposta è un po 'più vecchia (gennaio 2012), quindi è stato Lukas Schmelzeisen a presentarsi come il primo con la @AuthenticationPrincipalsoluzione di annotazione basata su Spring Security 3.2.)


Quindi è possibile utilizzare nel controller

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

Va bene se ne hai bisogno una volta. Ma se ne hai bisogno più volte è brutto perché inquina il tuo controller con i dettagli dell'infrastruttura, che normalmente dovrebbe essere nascosto dal framework.

Quindi quello che potresti davvero desiderare è avere un controller come questo:

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

Pertanto è sufficiente implementare a WebArgumentResolver. Ha un metodo

Object resolveArgument(MethodParameter methodParameter,
                   NativeWebRequest webRequest)
                   throws Exception

Questo ottiene la richiesta web (secondo parametro) e deve restituire Userif se si sente responsabile dell'argomento del metodo (il primo parametro).

Dalla primavera 3.1 esiste un nuovo concetto chiamato HandlerMethodArgumentResolver. Se usi Spring 3.1+, dovresti usarlo. (È descritto nella prossima sezione di questa risposta))

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

È necessario definire l'annotazione personalizzata: è possibile ignorarla se ogni istanza dell'utente deve essere sempre presa dal contesto di sicurezza, ma non è mai un oggetto comando.

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

Nella configurazione devi solo aggiungere questo:

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

@Vedi: impara a personalizzare gli argomenti del metodo Spring MVC @Controller

Va notato che se si utilizza Spring 3.1, raccomandano HandlerMethodArgumentResolver su WebArgumentResolver. - vedi commento di Jay


Lo stesso con HandlerMethodArgumentResolverSpring 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;
          }
     }
}

Nella configurazione, è necessario aggiungere questo

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

@Vedi Sfruttare l'interfaccia Spring MVC 3.1 HandlerMethodArgumentResolver


Soluzione Spring-Security 3.2

Spring Security 3.2 (non confondere con Spring 3.2) ha la propria soluzione build in: @AuthenticationPrincipal( org.springframework.security.web.bind.annotation.AuthenticationPrincipal). Questo è ben descritto nella risposta di Lukas Schmelzeisen

Sta solo scrivendo

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

Per farlo funzionare è necessario registrare il AuthenticationPrincipalArgumentResolver( org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver): "attivando" @EnableWebMvcSecurityo registrando questo bean all'interno mvc:argument-resolvers- allo stesso modo in cui l'ho descritto con la soluzione Spring 3.1 sopra.

@Vedi Riferimenti su Spring Security 3.2, capitolo 11.2. @AuthenticationPrincipal


Soluzione Spring-Security 4.0

Funziona come la soluzione di 3.2 primavera, ma in Spring 4.0 la @AuthenticationPrincipale AuthenticationPrincipalArgumentResolverè stato "trasferito" a un altro pacchetto:

(Ma le vecchie classi nei suoi vecchi pacchetti esistono ancora, quindi non mescolarle!)

Sta solo scrivendo

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

Per farlo funzionare è necessario registrare il ( org.springframework.security.web.method.annotation.) AuthenticationPrincipalArgumentResolver: "attivando" @EnableWebMvcSecurityo registrando questo bean all'interno mvc:argument-resolvers- allo stesso modo in cui l'ho descritto con la soluzione Spring 3.1 sopra.

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

@Vedere Riferimenti a Spring Security 5.0, Capitolo 39.3 @AuthenticationPrincipal


In alternativa, basta creare un bean con un metodo getUserDetails () e @Autowire nel controller.
sourcedelica,

Implementando questa soluzione, ho impostato un punto di interruzione nella parte superiore di resolArgument () ma la mia app non entra mai nel risolutore di argomenti Web. La tua configurazione di primavera nel contesto servlet non è il contesto root, giusto?
Jay,

@Jay: questa configurazione fa parte del contesto root, non del contesto servlet. - sembra che ho dimenticato di specificare l'id (id="applicationConversionService")nell'esempio
Ralph,

11
Va notato che se si utilizza Spring 3.1, raccomandano HandlerMethodArgumentResolver su WebArgumentResolver. Ho fatto funzionare HandlerMethodArgumentResolver configurandolo con <annotation-driven> nel contesto servlet. A parte questo, ho implementato la risposta come pubblicata qui e tutto funziona alla grande
Jay

@sourcedelica Perché non creare semplicemente un metodo statico?
Alex78191,

66

Mentre Ralphs Answer fornisce una soluzione elegante, con Spring Security 3.2 non è più necessario implementare la propria ArgumentResolver.

Se hai UserDetailsun'implementazione CustomUser, puoi semplicemente farlo:

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

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

Vedi la documentazione sulla sicurezza di primavera: @AuthenticationPrincipal


2
per coloro a cui non piace leggere i link forniti, questo deve essere abilitato da @EnableWebMvcSecurityo in XML:<mvc:annotation-driven> <mvc:argument-resolvers> <bean class="org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
sodik

Come verificare se customUserè nullo o no?
Sajad,

if (customUser != null) { ... }
T3rm1,

27

Spring Security è progettato per funzionare con altri framework non Spring, quindi non è strettamente integrato con Spring MVC. Spring Security restituisce l' Authenticationoggetto dal HttpServletRequest.getUserPrincipal()metodo per impostazione predefinita, quindi è quello che ottieni come principale. Puoi ottenere il tuo UserDetailsoggetto direttamente da questo usando

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

Si noti inoltre che i tipi di oggetto possono variare a seconda del meccanismo di autenticazione utilizzato ( UsernamePasswordAuthenticationTokenad esempio, non è possibile ottenere un ) e Authenticationche non è necessario che contenga un UserDetails. Può essere una stringa o qualsiasi altro tipo.

Se non vuoi chiamare SecurityContextHolderdirettamente, l'approccio più elegante (che seguirei) è quello di iniettare la tua interfaccia di accesso al contesto di sicurezza personalizzata che è personalizzata per soddisfare le tue esigenze e i tipi di oggetti utente. Creare un'interfaccia, con i metodi pertinenti, ad esempio:

interface MySecurityAccessor {

    MyUserDetails getCurrentUser();

    // Other methods
}

È quindi possibile implementarlo accedendo SecurityContextHolderall'implementazione standard, quindi disaccoppiando completamente il codice da Spring Security. Quindi iniettare questo nei controller che hanno bisogno di accedere alle informazioni di sicurezza o alle informazioni sull'utente corrente.

L'altro vantaggio principale è che è facile realizzare implementazioni semplici con dati fissi per i test, senza doversi preoccupare di popolare i thread-local e così via.


Stavo considerando questo approccio, ma non ero sicuro a) come fare esattamente nel modo giusto (tm) eb) se ci fossero problemi di threading. Sei sicuro che non ci sarebbero problemi lì? Sto andando con il metodo di annotazione pubblicato sopra, ma penso che questo sia ancora un modo decente per farlo. Grazie per la pubblicazione. :)
The Awnry Bear

4
È tecnicamente uguale all'accesso a SecurityContextHolder direttamente dal controller, quindi non dovrebbero esserci problemi di threading. Mantiene semplicemente la chiamata in un unico posto e consente di iniettare facilmente alternative per i test. Puoi anche riutilizzare lo stesso approccio in altre classi non web che necessitano dell'accesso alle informazioni di sicurezza.
Shaun the Sheep,

Gotcha ... questo è quello che stavo pensando. Sarà una buona idea a cui tornare se ho problemi con i test unitari con il metodo di annotazione o se voglio ridurre l'accoppiamento con Spring.
The Awnry Bear

@LukeTaylor puoi per favore spiegare ulteriormente questo approccio, sono un po 'nuovo nella sicurezza primaverile e primaverile, quindi non capisco come implementarlo. Dove posso implementarlo per accedervi? al mio UserServiceImpl?
Cu7l4ss,

9

Implementare l' HandlerInterceptorinterfaccia, quindi iniettare UserDetailsin ogni richiesta che ha un modello, come segue:

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

1
Grazie @atrain, è utile ed elegante. Inoltre, ho dovuto aggiungere il <mvc:interceptors>file di configurazione dell'applicazione.
Eric

È meglio ottenere l'utente in via template autorizzati spring-security-taglibs: stackoverflow.com/a/44373331/548473
Grigory Kislin

9

A partire da Spring Security versione 3.2, la funzionalità personalizzata che è stata implementata da alcune delle risposte più vecchie, è pronta all'uso sotto forma di @AuthenticationPrincipalannotazione supportata da AuthenticationPrincipalArgumentResolver.

Un semplice esempio del suo utilizzo è:

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

CustomUser deve essere assegnabile da authentication.getPrincipal()

Ecco i Javadocs corrispondenti di AuthenticationPrincipal e AuthenticationPrincipalArgumentResolver


1
@nbro Quando ho aggiunto la soluzione specifica per versione, nessuna delle altre soluzioni è stata aggiornata per tener conto di questa soluzione
geoand

AuthenticationPrincipalArgumentResolver è ora obsoleto
Igor Donin

5
@Controller
public abstract class AbstractController {
    @ModelAttribute("loggedUser")
    public User getLoggedUser() {
        return (User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }
}

0

E se hai bisogno di un utente autorizzato nei modelli (ad es. JSP), usa

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

insieme a

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-taglibs</artifactId>
        <version>${spring-security.version}</version>
    </dependency>

0

Puoi provare questo: Usando Authentication Object di Spring possiamo ottenere i dettagli dell'utente da esso nel metodo del controller. Di seguito è riportato l'esempio, passando l'oggetto di autenticazione nel metodo del controller insieme all'argomento. Una volta che l'utente è autenticato, i dettagli vengono popolati nell'oggetto di autenticazione.

@GetMapping(value = "/mappingEndPoint") <ReturnType> methodName(Authentication auth) {
   String userName = auth.getName(); 
   return <ReturnType>;
}

Per favore, elabora la tua risposta.
Nikolai Shevchenko,

Ho modificato la mia risposta, potremmo usare Authentication Object che ha i dettagli utente dell'utente che ha già autenticato.
Mirza Shujathullah,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.