【问题标题】:Spring Security: custom userdetailsSpring Security:自定义用户详细信息
【发布时间】:2012-05-23 09:00:57
【问题描述】:

我对 Java 和 Spring 3 很陌生(过去 8 年主要使用 PHP)。我已经让 spring security 3 与所有默认的 userDetails 和 userDetailsS​​ervice 一起工作,我知道我可以通过以下方式访问控制器中登录用户的用户名:

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();”在每个方法的开头?

Security-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" />

【问题讨论】:

    标签: spring spring-security


    【解决方案1】:

    在这个问题上发生了很多事情。我会尽量解决它...

    Q#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>
    

    方法 #2:像我一样,您可能会发现(尤其是在多次迭代中)最好将特定域的用户/帐户详细信息与 Spring Security 特定用户分开 /帐户详细资料。这对你来说可能是也可能不是。但是,如果您能在这种方法中找到任何智慧,那么您将坚持当前的设置并添加额外的用户/帐户域对象、相应的存储库/DAO 等。如果您想检索特定于域的用户/帐户,您可以这样做:

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

    Q#2:Spring Security 自动将 UserDetails 存储在会话中(除非您已明确采取措施覆盖该行为)。因此,您无需在每个控制器方法中自己执行此操作。您一直在处理的 SecurityContextHolder 对象实际上(由 SS)在每个请求开始时使用 SecurityContext 填充,包括 Authentication 对象、UserDetails 等。每次请求结束时都会清除此上下文,但数据始终保留在会话中。

    然而,值得注意的是,如果可以避免的话,在 Spring MVC 控制器中处理 HttpServletRequest、HttpSession 对象等并不是一个好习惯。 Spring 几乎总是提供更简洁、更惯用的方法来实现,而无需这样做。这样做的好处是控制器方法签名和逻辑不再依赖于在单元测试中难以模拟的东西(例如 HttpSession),而不是依赖于您自己的域对象(或那些域对象的存根/模拟) )。这极大地增加了控制器的可测试性……从而增加了您实际测试控制器的可能性。 :)

    希望这会有所帮助。

    【讨论】:

    • 感谢肯特,这很有帮助!对不起,第一次编写 Spring 代码。我喜欢方法#2,我将把那行代码放在每个方法的开头?我猜基于这个问题,如果我采用第二种方法,是否可以存储用户对象,这样我就不会每次都需要创建一个新的用户对象并去数据库获取相同的数据吗?
    • 答案是“视情况而定”。我确实发现使用方法#2,我很少需要访问用户的帐户(从域的角度来看)。也许我访问它只是为了显示或编辑他们的“个人资料”。如果是这种情况,我会在需要时通读数据存储以获取此信息。但是,如果您需要经常访问用户的属性(从域的角度来看;也许用户对象的某些属性需要出现在标头中,因此在每个请求中都需要),那么您可能应该重新考虑方法1.
    • 谢谢肯特。我最终做了一个混合类型的解决方案。当我需要登录用户时,我在包装控制器中运行一个方法来获取数据。该方法检查会话是否包含用户对象,如果包含,则检查对象的用户名是否等于 securityContext 用户名的用户名。如果不是(或者如果会话用户对象为空),它将从数据库中获取用户数据并将对象存储在会话中。两个额外的 if 语句,但少了 1 个数据库调用。
    • 如果我正确阅读了您的最后一条评论,那么您就是在防止 SS 用户和会话中的域用户不匹配的情况。因为两者都存储在会话中,所以对两者的访问都需要一个会话 cookie。不应该有这些不匹配的情况。您可以消除该冗余检查。例外情况是,如果您使用诸如 SiteMinder 之类的“预身份验证”解决方案,它会将自己的 cookie 纳入等式。然后,SiteMinder 会话可能会在 Web 会话保持活动状态时结束,反之亦然。在你的情况下不适用。
    • 啊,明白了,这简化了事情!谢谢肯特。
    【解决方案2】:

    在我看来,自定义 UserDetails 实现很棒,但只应用于用户的不可变特征。

    一旦您的自定义 User 对象覆盖 UserDetails,就不容易更改。您必须使用修改后的详细信息创建一个全新的身份验证对象,并且不能将修改后的 UserDetails 对象重新粘贴到安全上下文中。

    在我正在构建的应用程序中,我已经意识到这一点,并且必须重新构建它,以便在成功的身份验证后,用户的详细信息会随着每个请求而变化(但我不想每次都从数据库重新加载)页面加载)需要单独保存在会话中,但仍然只能在身份验证检查后访问/更改。

    试图弄清楚https://stackoverflow.com/a/8769670/1411545 中提到的这个 WebArgumentResolver 是否适合我的情况。

    【讨论】:

      【解决方案3】:

      直接访问会话有点混乱,而且容易出错。例如,如果用户使用记住我或其他不涉及重定向的机制进行身份验证,则在该请求完成之前不会填充会话。

      我会使用自定义访问器接口来包装对SecurityContextHolder 的调用。见my answer to this related question

      【讨论】:

      • 好电话,我已经有一段时间没有使用会话了。
      猜你喜欢
      • 2021-09-09
      • 2017-06-05
      • 2017-12-11
      • 2013-12-13
      • 2011-06-24
      • 2012-07-20
      • 2013-07-23
      • 2011-01-15
      • 2019-01-01
      相关资源
      最近更新 更多