2012-05-15 90 views
8

我對Java和Spring 3(過去8年主要使用PHP)非常新穎。我已經得到了春季安全3所有默認爲userDetails和UserDetailsS​​ervice的工作,我知道我可以通過訪問登錄的用戶的用戶名控制器:Spring Security:自定義用戶代碼

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

但有兩個問題我想不通出:

  1. 有很多的其他用戶的信息我想存儲當在用戶登錄(如DOB,性別等),並且是經由控制器稍後訪問。我需要做什麼以便創建的userDetails對象包含我的自定義字段?

  2. 我已經調用了「HttpSession session = request.getSession(true);」在我的控制器中的每個方法的頂部。是否有可能在登錄時將登錄用戶的userDetails存儲在會話中,以便我不需要同時調用「Authentication auth = SecurityContextHolder.getContext()。getAuthentication();」在每種方法的開始?

安全-的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" /> 

回答

19

在這個問題上有很多事情要做。我會盡力解決它...

問題1:這裏有幾種可能的方法。方法#1:如果你有其他的屬性要添加到你的UserDetails對象中,那麼你應該提供你自己的UserDetails接口的替代實現,它包括這些屬性以及相應的getter和setter。這就要求你也提供你自己的UserDetailsS​​ervice接口的替代實現。此組件必須瞭解如何將這些附加屬性持久保存到基礎數據存儲中,或者在從該數據存儲中讀取時必須瞭解如何填充這些附加屬性。你會連線這一切像這樣:

<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:像我一樣,你可能會發現(特別是在多次迭代的跨度),你更好的服務,以保持特定的域用戶/賬戶詳細信息單獨的來自Spring Security特定用戶/賬戶的詳細信息。這可能或可能不是你的情況。但是如果你可以在這種方法中找到任何智慧,那麼你會堅持使用當前的設置,並添加一個額外的用戶/賬戶域對象,相應的存儲庫/ DAO等。如果你想獲取特定領域的用戶/帳戶,你可以這樣做如下:

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

,Q#2:Spring Security的自動存儲在會話中的UserDetails(除非您已明確採取措施覆蓋那種行爲)。所以你沒有必要在你的每個控制器方法中自己做這件事。您一直處理的SecurityContextHolder對象實際上是由SecurityContext(包括Authentication對象,UserDetails等)在每個請求的開始處填充(由SS)。該上下文在每個請求結束時被清除,但數據始終保留在會話中。

但值得注意的是,如果可以避免它,在Spring MVC控制器中處理HttpServletRequest,HttpSession對象等並不是一個好習慣。春天幾乎總是提供更清潔,更習慣的方式來實現沒有這樣做的需要。這樣做的好處是,控制器方法簽名和邏輯不再依賴於在單元測試中很難模擬的東西(例如HttpSession),而是依賴於你自己的域對象(或者這些域對象的存根/模仿)。這極大地提高了您的控制器的可測試性,從而增加了您實際將測試您的控制器的可能性。 :)

希望這有助於一些。

+0

謝謝Kent,這非常有幫助!對不起,第一次編碼Spring。我喜歡方法#2,我會在哪裏放置那行代碼,在每個方法的開始處?我想基於這個問題,如果我採用第二種方法,可以存儲用戶對象,是否需要創建一個新的用戶對象,並每次都去相同的數據分貝? – Felix

+1

答案是,「這取決於」。我發現通過方法#2,我很少需要訪問用戶的帳戶(從域角度來看)。也許我只是爲了顯示或編輯他們的「個人資料」而訪問它。如果是這樣的話,我會在需要時通讀數據存儲庫以獲取此信息。 但是,如果您需要經常訪問用戶的屬性(從域的角度來看,可能某些用戶對象的屬性需要出現在標題中,因此在每個請求中都需要),那麼您可能應該重新考慮方法#1。 –

+1

謝謝肯特。我最終做了一個混合型解決方案。當我需要登錄的用戶時,我在包裝控制器中運行一個方法來獲取數據。該方法檢查會話中是否包含用戶對象,如果是,則檢查該對象的用戶名是否等於securityContext用戶名的用戶名。如果不是(或者如果會話用戶對象爲空),則它從數據庫獲取用戶數據並將該對象存儲在會話中。兩個額外的if語句,但少一個數據庫調用。 – Felix

1

直接訪問會話是有點亂,而且容易出錯。例如,如果用戶使用記住我或其他一些不涉及重定向的機制進行身份驗證,則在該請求完成之前,會話將不會被填充。

我會使用一個自定義訪問器接口將調用包裝到SecurityContextHolder。見my answer to this related question

+0

良好的通話,從我使用會話開始已經有一段時間了。 – Felix

2

在我看來,Custom UserDetails實現很棒,但只能用於用戶的不可變特徵。

一旦您的自定義用戶對象覆蓋UserDetails,它不會輕易更改。您必須創建具有修改後的詳細信息的全新認證對象,並且不能將修改過的UserDetails對象返回到安全上下文中。

在我正在構建的應用程序中,我已經意識到這一點,並且必須重新構建它,以便在每次請求都更改用戶的成功身份驗證詳細信息時(但我不想從數據庫重新加載每頁加載)需要單獨保存在會話中,但在驗證檢查後仍然只能訪問/更改。

試圖找出在https://stackoverflow.com/a/8769670/1411545中提到的這個WebArgumentResolver是否是對我的情況更好的解決方案。

相關問題