我是Java和Spring 3的新手(过去8年主要使用PHP)。我已经获得了spring security 3来使用所有默认的userDetails和userDetailsService,我知道我可以使用以下命令访问控制器中登录用户的用户名:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName(); //get logged in username
但有两个问题我无法弄清楚:
我想在用户登录时存储许多其他用户详细信息(例如DOB,性别等),以后可以通过控制器访问。我需要做什么才能创建的userDetails对象包含我的自定义字段?
我已经调用了“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> </td><td><input type="checkbox" name="_spring_security_remember_me" /> Remember Me</td></tr>
<tr><td> </td><td><input name="submit" type="submit" value="Login" /></td></tr>
</table>
</form>
<tiles:insertDefinition name="postbody" />
<tiles:insertDefinition name="footer" />
答案 0 :(得分:19)
在这个问题上发生了很多事情。我会试着把它解决......
问题1:这里有几种可能的方法。
方法#1:如果您要将其他属性添加到UserDetails对象,那么您应该提供自己的UserDetails接口的备用实现,其中包括这些属性以及相应的getter和setter。这将要求您还提供自己的UserDetailsService接口的备用实现。此组件必须了解如何将这些附加属性持久保存到基础数据存储区,或者从该数据存储区读取时,必须了解如何填充这些其他属性。你可以这样连接所有这些:
<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());
问题2:Spring Security会自动将UserDetails存储在会话中(除非您已明确采取措施来覆盖该行为)。因此,您无需在每种控制器方法中自行完成此操作。您一直在处理的SecurityContextHolder对象实际上是使用SecurityContext填充(通过SS),包括每个请求开头的Authentication对象,UserDetails等。在每个请求结束时清除此上下文,但数据始终保留在会话中。
但值得注意的是,如果你可以避免它,那么在Spring MVC控制器中处理HttpServletRequest,HttpSession对象等并不是一个很好的做法。 Spring几乎总是提供更清晰,更惯用的方法来实现,而无需这样做。这样做的好处是控制器方法签名和逻辑不再依赖于在单元测试中难以模拟的东西(例如HttpSession)而不依赖于您自己的域对象(或那些域对象的存根/模拟) )。这极大地提高了控制器的可测试性......从而增加了实际测试控制器的可能性。 :)
希望这会有所帮助。
答案 1 :(得分:2)
在我看来,Custom UserDetails的实现很棒,但只应用于用户的不可变特性。
一旦您的自定义User对象覆盖UserDetails,就不容易更改。您必须使用修改后的详细信息创建一个全新的身份验证对象,并且不能只将修改后的UserDetails对象粘贴回安全上下文。
在我正在构建的应用程序中,我已经意识到这一点,并且必须重新构建它,以便在成功验证有关每个请求正在更改的用户的详细信息(但我不希望每次都从db重新加载)页面加载)需要单独保存在会话中,但仍然只能在身份验证检查后访问/更改。
试图弄清楚https://stackoverflow.com/a/8769670/1411545中提到的这个WebArgumentResolver是否是一个更好的解决方案。
答案 2 :(得分:1)
直接访问会话有点乱,并且可能容易出错。例如,如果使用remember-me或其他一些不涉及重定向的机制对用户进行身份验证,则会在该请求完成之后才会填充该会话。
我会使用自定义访问器接口来包含对SecurityContextHolder
的调用。请参阅my answer to this related question。