2012-05-15 20 views
8

Ich bin ziemlich neu in Java und Spring 3 (in den letzten 8 Jahren in erster Linie PHP verwendet). Ich habe Frühjahr Sicherheit 3 ​​bekommen mit allen Standard-Userdetails und userDetailsService zu arbeiten, und ich weiß, ich kann den Benutzernamen des angemeldeten Benutzers zuzugreifen in einer Steuerung durch den Einsatz:Spring Security: benutzerdefinierte Benutzerdetails

Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 
String username = auth.getName(); //get logged in username 

Aber es gibt zwei Probleme, die ich verstehe nicht out:

  1. Es gibt eine Menge anderer Benutzer Details würde ich gespeichert wie wenn sich ein Benutzer anmeldet (zB DOB, Geschlecht, etc.) und über die Regler später zugänglich zu sein. Was muss ich tun, damit das erstellte userDetails-Objekt meine benutzerdefinierten Felder enthält?

  2. Ich rufe bereits "HttpSession session = request.getSession (true);" an der Spitze jeder meiner Methoden in meinem Controller. Ist es möglich, die userDetails des angemeldeten Benutzers bei der Anmeldung in einer Sitzung zu speichern, so dass ich auch nicht "Authentication auth = SecurityContextHolder.getContext(). GetAuthentication();" aufrufen muss am Anfang jeder Methode?

Sicherheits applicationContext.xml:

<global-method-security secured-annotations="enabled"></global-method-security>  
<http auto-config='true' access-denied-page="/access-denied.html"> 
    <!-- NO RESTRICTIONS -->   
    <intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <intercept-url pattern="/*.html" access="IS_AUTHENTICATED_ANONYMOUSLY" /> 
    <!-- RESTRICTED PAGES --> 
    <intercept-url pattern="/admin/*.html" access="ROLE_ADMIN" /> 
    <intercept-url pattern="/member/*.html" access="ROLE_ADMIN, ROLE_STAFF" /> 

    <form-login login-page="/login.html" 
       login-processing-url="/loginProcess" 
       authentication-failure-url="/login.html?login_error=1" 
       default-target-url="/member/home.html" /> 
    <logout logout-success-url="/login.html"/> 
</http> 

<authentication-manager> 
    <authentication-provider> 
     <jdbc-user-service data-source-ref="dataSource" authorities-by-username-query="SELECT U.username, UR.authority, U.userid FROM users U, userroles UR WHERE U.username=? AND U.roleid=UR.roleid LIMIT 1" /> 
     <password-encoder hash="md5"/> 
    </authentication-provider> 
</authentication-manager> 

login.jsp:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> 
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> 
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%> 

<tiles:insertDefinition name="header" /> 
<tiles:insertDefinition name="menu" /> 
<tiles:insertDefinition name="prebody" /> 

<h1>Login</h1> 

<c:if test="${not empty param.login_error}"> 
    <font color="red"><c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>.<br /><br /></font> 
</c:if> 
<form name="f" action="<c:url value='/loginProcess'/>" method="POST"> 
    <table> 
     <tr><td>User:</td><td><input type='text' name='j_username' value='<c:if test="${not empty param.login_error}"><c:out value="${SPRING_SECURITY_LAST_USERNAME}"/></c:if>' /></td></tr> 
      <tr><td>Password:</td><td><input type='password' name='j_password' /></td></tr> 
      <tr><td>&nbsp;</td><td><input type="checkbox" name="_spring_security_remember_me" /> Remember Me</td></tr> 
      <tr><td>&nbsp;</td><td><input name="submit" type="submit" value="Login" /></td></tr> 
     </table> 
    </form> 

<tiles:insertDefinition name="postbody" /> 
<tiles:insertDefinition name="footer" /> 

Antwort

19

In dieser Frage geht sehr viel los. Ich werde versuchen, es in Stücke zu adressieren ...

Q # 1: Es gibt ein paar mögliche Ansätze hier.

Vorgehensweise # 1: Wenn Sie andere Attribute zu Ihrem UserDetails-Objekt hinzufügen möchten, sollten Sie eine eigene alternative Implementierung der UserDetails-Schnittstelle bereitstellen, die diese Attribute zusammen mit entsprechenden Getter und Setter enthält. Dazu müssten Sie auch Ihre eigene alternative Implementierung der UserDetailsService-Schnittstelle bereitstellen. Diese Komponente müsste verstehen, wie diese zusätzlichen Attribute für den zugrunde liegenden Datenspeicher beibehalten werden, oder ob beim Lesen aus diesem Datenspeicher das Vervollständigen dieser zusätzlichen Attribute zu verstehen ist. Sie würden all dies auf wie so verdrahten:

<beans:bean id="userDetailsService" class="com.example.MyCustomeUserDetailsService"> 
<!-- ... --> 
</beans:bean> 

<authentication-manager alias="authenticationManager"> 
    <authentication-provider ref="authenticationProvider"/> 
</authentication-manager> 

<beans:bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> 
    <beans:property name="userDetailsService" ref="userDetailsService"/> 
</beans:bean> 

Approache # 2: Wie ich, kann man (vor allem über den Zeitraum von mehreren Iterationen) finden, die Sie besser bedient sind domänenspezifische Benutzer zu halten/Konto Details separate von Spring Security spezifischen Benutzer/Konto Details. Dies kann für Sie der Fall sein oder nicht. Aber wenn Sie bei diesem Ansatz irgendeine Weisheit finden, dann bleiben Sie bei dem Setup, das Sie gerade haben, und fügen Sie ein zusätzliches Benutzer-/Account-Domain-Objekt, entsprechendes Repository/DAO usw. hinzu.Wenn Sie die domänenspezifische Benutzer/Konto abrufen möchten, können Sie dies wie folgt tun:

User user = userDao.getByUsername(SecurityContextHolder.getContext().getAuthentication().getName()); 

Q # 2: Spring Security speichert automatisch die Userdetails in der Sitzung (es sei denn, Sie explizit Schritte unternommen haben außer Kraft zu setzen dieses Verhalten). Sie müssen das also nicht bei jeder Ihrer Controller-Methoden selbst machen. Das SecurityContextHolder-Objekt, mit dem Sie es zu tun haben, wird tatsächlich (mit SS) mit SecurityContext aufgefüllt, einschließlich des Authentifizierungsobjekts, UserDetails usw. zu Beginn jeder Anfrage. Dieser Kontext wird am Ende jeder Anfrage gelöscht, aber die Daten verbleiben immer in der Sitzung.

Es ist jedoch erwähnenswert, dass es nicht wirklich eine großartige Übung ist, sich mit HttpServletRequest-, HttpSession-Objekten usw. in einem Spring MVC-Controller zu befassen, wenn Sie es vermeiden können. Frühling bietet fast immer sauberere, idiomatische Mittel, Dinge zu erreichen ohne die Notwendigkeit dafür. Der Vorteil wäre, dass die Signaturen und Logik der Controller-Methoden nicht mehr von Dingen abhängig sind, die in einem Komponententest (zB der HttpSession) schwer nachzuahmen sind und nicht von Ihren eigenen Domain-Objekten (oder Stubs/Mocks dieser Domain-Objekte) abhängig sind). Dies erhöht die Testfähigkeit Ihrer Controller drastisch und erhöht somit die Wahrscheinlichkeit, dass Sie Ihre Controller tatsächlich testen. :)

Hoffe das hilft einige.

+0

Danke Kent, das war sehr hilfreich! Sorry für das Durcheinander, zum ersten Mal Codierung Frühling. Ich mag Approach # 2, wo würde ich diese Codezeile am Anfang jeder Methode platzieren? Und ich denke, basierend auf dieser Frage, wenn ich mit diesem zweiten Ansatz fortfahre, ist es möglich, das Benutzerobjekt so zu speichern, t müssen Sie ein neues Benutzerobjekt erstellen und jedes Mal zur Datenbank für die gleichen Daten wechseln? – Felix

+1

Die Antwort lautet: "Es kommt darauf an." Ich finde, dass ich mit Ansatz Nr. 2 sehr selten auf ein Benutzerkonto zugreifen muss (aus einer Domänenperspektive). Vielleicht kann ich nur darauf zugreifen, um ihr "Profil" anzuzeigen oder zu bearbeiten. Wenn dies der Fall ist, würde ich diese Informationen nur dann im Datenspeicher durchlesen, wenn sie benötigt werden. Wenn Sie jedoch häufig auf Attribute des Benutzers zugreifen müssen (aus einer Domänenperspektive; möglicherweise muss ein Attribut des Benutzerobjekts in der Kopfzeile angezeigt werden und wird daher in jeder Anforderung benötigt), sollten Sie dies entweder erneut prüfen Annäherung # 1. –

+1

Danke Kent. Am Ende habe ich eine hybride Lösung gemacht. Wenn ich den angemeldeten Benutzer brauche, führe ich eine Methode in einem Wrapper-Controller aus, um die Daten zu erhalten. Die Methode überprüft, ob die Sitzung ein Benutzerobjekt enthält. Wenn dies der Fall ist, wird überprüft, ob der Benutzername des Objekts dem des SecurityContext-Benutzernamens entspricht. Wenn nicht (oder wenn das Sitzungsbenutzerobjekt null ist), ruft es die Benutzerdaten aus der Datenbank ab und speichert das Objekt in der Sitzung. Zwei zusätzliche if-Anweisungen, aber 1 weniger Datenbankaufruf. – Felix

1

die Sitzung Zugriff ist direkt ein bisschen chaotisch, und anfällig für Fehler sein kann. Wenn der Benutzer z. B. mit remember-me oder einem anderen Mechanismus authentifiziert wird, der keine Umleitung enthält, wird die Sitzung erst nach Abschluss dieser Anforderung ausgefüllt.

Ich würde eine benutzerdefinierte Accessor-Schnittstelle verwenden, um die Anrufe an die SecurityContextHolder zu wickeln. Siehe my answer to this related question.

+0

Guter Anruf, es ist eine Weile her, seit ich Sitzungen verwendet habe. – Felix

2

Meiner Meinung nach ist Custom UserDetails Implementierung groß, sollte aber nur für unveränderliche Eigenschaften Ihres Benutzers verwendet werden.

Sobald Ihr benutzerdefiniertes Benutzerobjekt UserDetails überschreibt, kann es nicht einfach geändert werden. Sie müssen ein vollständig neues Authentifizierungsobjekt mit den geänderten Details erstellen und das geänderte Objekt UserDetails nicht einfach in den Sicherheitskontext kleben.

In der Anwendung, die ich baue, habe ich dies realisiert und musste es neuarchitektonieren, so dass nach erfolgreicher Authentifizierung Details über den Benutzer, die mit jeder Anfrage ändern (aber ich möchte nicht von der DB auf neu laden Jede Seitenladung) muss separat in der Sitzung aufbewahrt werden, aber nach einer Authentifizierungsüberprüfung immernoch zugänglich/änderbar.

Der Versuch, herauszufinden, ob dieser WebArgumentResolver in https://stackoverflow.com/a/8769670/1411545 erwähnt wird, ist eine bessere Lösung für meine Situation.

Verwandte Themen