Wenn Spring Security verwenden, was ist der richtige Weg aktuelle Benutzername (das heißt Security) Informationen in einer Bean zu erhalten?

StackOverflow https://stackoverflow.com/questions/248562

Frage

Ich habe eine Spring MVC Web-App, die Spring Security verwendet. Ich möchte den Benutzernamen des aktuell angemeldeten Benutzers kennen. Ich verwende den Code-Schnipsel unten. Ist dies die Art und Weise akzeptiert?

Ich mag es nicht einen Aufruf eine statische Methode in diesem Controller mit -, die den ganzen Zweck des Frühlings, IMHO besiegt. Gibt es eine Möglichkeit, die App zu konfigurieren, dass die aktuelle Security haben oder aktuelle Authentifizierung, injizierte statt?

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

Lösung

Wenn Sie mit Frühling 3 , der einfachste Weg ist:

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

     final String currentUser = principal.getName();

 }

Andere Tipps

Es hat sich viel in der Frühlings-Welt verändert, seit diese Frage beantwortet wurde. Der Frühling ist vereinfacht in einem Controller den aktuellen Benutzer zu bekommen. Für andere Bohnen, hat Frühling die Vorschläge des Autors übernommen und vereinfacht die Injektion von ‚SecurityContextHolder‘. Weitere Details sind in den Kommentaren.


Dies ist die Lösung, die ich beendet habe oben gehen mit. Anstelle der Verwendung von SecurityContextHolder in meinem Controller, möchte ich etwas injizieren, die SecurityContextHolder unter der Haube verwendet, aber abstrahiert, dass Singleton-ähnliche Klasse von meinem Code entfernt. Ich habe keine Möglichkeit gefunden diese andere zu tun, als meine eigene Schnittstelle rollen, etwa so:

public interface SecurityContextFacade {

  SecurityContext getContext();

  void setContext(SecurityContext securityContext);

}

Nun, mein Controller (oder was auch immer POJO) würde wie folgt aussehen:

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
  }

}

Und weil die Schnittstelle ein Punkt der Entkopplung ist, Unit-Tests sind sehr einfach. In diesem Beispiel verwende ich 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();
  }

}

Die Standardimplementierung der Schnittstelle sieht wie folgt aus:

public class SecurityContextHolderFacade implements SecurityContextFacade {

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

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

}

Und schließlich die Produktion Frühling Config sieht wie folgt aus:

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

Es scheint mehr als nur ein wenig albern, dass Frühling, ein Dependency Injection Container aller Dinge, hat etwas ähnliches nicht einen Weg geliefert zu injizieren. Ich verstehe SecurityContextHolder wurde von Acegi geerbt, aber immer noch. Die Sache ist, sie sind so nah - wenn auch nur SecurityContextHolder einen Getter hat die zugrunde liegende SecurityContextHolderStrategy Instanz zu erhalten (das ist eine Schnittstelle), könnten Sie, dass injizieren. In der Tat, ich öffnete sogar eine Jira Ausgabe in diesem Sinne.

Eine letzte Sache - ich habe gerade geändert im Wesentlichen die Antwort, die ich hier vorher hatte. Schauen Sie sich die Geschichte, wenn Sie neugierig sind, aber, wie ein Kollege mich darauf hingewiesen, meine Antwort wäre in einer Multithreadumgebung nicht funktionieren. Der zugrunde liegende SecurityContextHolderStrategy durch SecurityContextHolder verwendet ist, standardmäßig eine Instanz von ThreadLocalSecurityContextHolderStrategy, die in einem SecurityContexts ThreadLocal speichert. Daher ist es nicht unbedingt eine gute Idee, um die SecurityContext direkt in eine Bohne bei der Initialisierung zu injizieren - es muß möglicherweise in einer Multi-Threaded-Umgebung aus dem ThreadLocal jedes Mal, abgerufen werden, so dass die richtigen abgerufen

Ich bin damit einverstanden, dass die Security für den aktuellen Benutzer stinken abfragen müssen, ist es eine sehr un-Spring Art und Weise scheint dieses Problem zu behandeln.

Ich schrieb eine statische „Helfer“ -Klasse mit diesem Problem zu befassen; es ist in schmutzig, dass es eine globale und statische Methode, aber ich dachte, auf diese Weise, wenn wir etwas in Bezug auf Sicherheit zu ändern, zumindest habe ich nur die Details an einer Stelle ändern:

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

Um es zeigt sich nur in Ihren JSP-Seiten, können Sie die Spring Security Tag Lib verwenden:

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

eine der Tags zu verwenden, müssen Sie die Sicherheit haben TagLib in Ihrer JSP erklärt:

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

Dann in einer jsp Seite etwas tun, wie folgt aus:

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

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

Hinweis: Wie @ SBerg413 in den Kommentaren erwähnt, werden Sie hinzufügen müssen

  

use-Ausdrücke = "true"

, um das "http" -Tag in der security.xml Config für diese zu arbeiten.

Wenn Sie Spring Security verwenden ver> = 3.2, können Sie die @AuthenticationPrincipal Anmerkung verwenden:

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

Hier CustomUser ist ein benutzerdefiniertes Objekt, das UserDetails implementiert, die von einem benutzerdefinierten UserDetailsService zurückgeführt wird.

Weitere Informationen finden Sie in der @ AuthenticationPrincipal Kapitel der Spring Security Referenz docs.

ich authentifizierten Benutzer durch HttpServletRequest.getUserPrincipal ();

Beispiel:

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

Im Frühjahr 3+ Sie haben folgende Möglichkeiten.

Option 1:

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

Option 2:

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

Option 3:

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

}

Option 4: Fancy ein: diese um weitere Informationen Check out

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

Ja, Statik ist in der Regel schlecht - in der Regel, aber in diesem Fall ist die statische der sicherste Code, den Sie schreiben können. Da der Sicherheitskontext eines Principals mit dem aktuell laufenden Thread zuordnet, wäre die sicherste Code die statische aus dem so direkt wie möglich Thread zuzugreifen. Ausblenden der Zugang hinter einer Wrapper-Klasse, die injiziert wird, bietet einen Angreifer mit mehr Punkten zum Angriff. Sie würden keinen Zugriff auf den Code benötigen (was sie eine harte Zeit zu ändern, wenn das Glas unterzeichnet hätte), sie müssen nur einen Weg, um die Konfiguration zu überschreiben, die zur Laufzeit durchgeführt werden können oder eine XML auf den Classpath rutscht. Auch würde mit Annotations Injektion in dem signierten Code mit externem XML-overridable sein. Solche XML könnte das laufende System mit einem Schelm Haupt injizieren. Dies ist wahrscheinlich, warum Frühling etwas so un-Spring-wie in diesem Fall tut.

Ich würde dies nur tun:

request.getRemoteUser();

Für die letzte Spring MVC app, die ich geschrieben habe, habe ich nicht den Securityhalter injizieren, aber ich habe einen Basis-Controller, die ich zwei Hilfsmethoden hatte im Zusammenhang mit dieser ... IsAuthenticated () & getUsername (). Intern sie die statische Methode nennt man beschrieben.

Spätestens dann ist es nur in einmal in Verbindung, wenn Sie später Refactoring benötigen.

Sie könnten Frühling AOP aproach verwenden. Wenn Sie zum Beispiel einen Dienst haben, der die aktuellen Haupt wissen muss. Sie könnten benutzerdefinierte Anmerkung d @Principal, die darauf hindeuten, vorstellen, dass dieser Service Haupt abhängig sein sollte.

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

Dann in Ihrem Rat, die ich Bedürfnisse denke MethodBeforeAdvice zu erweitern, prüfen, ob bestimmte Service @Principal Annotation und injiziert Haupt Namen hat, oder setzen Sie sich auf ‚ANONYMOUS‘ statt.

Das einzige Problem ist, dass selbst mit Spring Security nach der Authentifizierung der Benutzer / Haupt Bohne nicht in dem Behälter vorhanden ist, so abhängigkeits Injektion wird es schwierig sein. Bevor wir Spring Security verwendet würden wir eine Session-scoped Bean erstellen, die das aktuelle Haupt hatten, injizieren, die in eine „AuthService“ und dann diesen Dienst in den meisten anderen Diensten in der Anwendung injizieren. So würden diese Dienste rufen Sie einfach authService.getCurrentUser (), um das Objekt zu erhalten. Wenn Sie einen Platz in Ihrem Code, wo Sie einen Verweis auf das gleiche Prinzip in der Sitzung erhalten, können Sie einfach legen Sie es als eine Eigenschaft auf Ihrer sitzungs scoped Bohne.

Versuchen Sie, diese

  

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

Die beste Lösung, wenn Sie mit Spring 3 und müssen das authentifizierten Haupt in Ihrem Controller ist so etwas wie dies zu tun:

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

    }

Ich bin mit der @AuthenticationPrincipal Annotation in @Controller Klassen sowie in @ControllerAdvicer kommentierte diejenigen. Bsp.:

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


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

Wo UserActive ist die Klasse i für angemeldete Benutzer Dienste nutzen, und erstreckt sich von org.springframework.security.core.userdetails.User. So etwas wie:

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
}

Wirklich einfach.

definiert Principal als Abhängigkeit in Ihrer Controller-Methode und Feder die aktuellen authentifizierten Benutzer in Ihrer Methode bei Aufruf injizieren.

Ich mag meine Art teilen von Benutzerdaten auf Seite Freemarker unterstützen. Alles ist sehr einfach und gut funktionierend!

Sie müssen nur Authentifizierung rerequest auf default-target-url (Seite nach Formular-Login) platzieren Das ist meine Controler Methode für diese Seite:

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

Und das ist mein FTL-Code:

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

Und das ist es, den Benutzernamen wird auf jeder Seite nach der Autorisierung erscheinen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top