Spring Security:自定义用户详细信息
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10607696/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Security: custom userdetails
提问by Felix
I'm pretty new to Java and Spring 3 (used primarily PHP the past 8 years). I've gotten spring security 3 to work with all the default userDetails and userDetailsService and I know I can access the logged in user's username in a controller by using:
我对 Java 和 Spring 3 很陌生(过去 8 年主要使用 PHP)。我已经让 spring security 3 与所有默认的 userDetails 和 userDetailsService 一起工作,我知道我可以使用以下方法访问控制器中登录用户的用户名:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName(); //get logged in username
But there are two problems I can't figure out:
但是有两个问题我想不通:
There are a lot of other user details I would like stored when a user logs in (such as DOB, gender, etc.) and to be accessible via the controllers later on. What do I need to do so that the userDetails object that is created contains my custom fields?
I'm already calling "HttpSession session = request.getSession(true);" at the top of each of my methods in my controller. Is it possible to store the logged in user's userDetails in a session upon login so that I don't need to also call "Authentication auth = SecurityContextHolder.getContext().getAuthentication();" at the beginning of every method?
我希望在用户登录时存储许多其他用户详细信息(例如出生日期、性别等),以便稍后通过控制器访问。我需要做什么才能让创建的 userDetails 对象包含我的自定义字段?
我已经在调用“HttpSession session = request.getSession(true);” 在我的控制器中每个方法的顶部。是否可以在登录时将登录用户的 userDetails 存储在会话中,这样我就不需要调用“Authentication auth = SecurityContextHolder.getContext().getAuthentication();” 在每个方法的开头?
Security-applicationContext.xml:
安全-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:
登录.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" />
回答by Kent Rancourt
There's an awful lot going on in this question. I'll try to address it in pieces...
这个问题有很多事情要做。我会尝试分块解决它...
Q#1: There are a couple possible approaches here.
Q#1:这里有几种可能的方法。
Approach #1: If you have other attributes that you want to add to your UserDetails object, then you should provide your own alternate implementation of the UserDetails interface that includes those attributes along with corresponding getters and setters. This would require that you also provide your own alternate implementation of the UserDetailsService interface. This component would have to understand how to persist these additional attributes to the underlying datastore, or when reading from that datastore, would have to understand how to populate those additional attributes. You'd wire all of this up like so:
方法#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>
Approache #2: Like me, you may find (especially over the span of several iterations) that you're better served to keep domain-specific user/account details separatefrom Spring Security specific user/account details. This may or may not be the case for you. But if you can find any wisdom in this approach, then you'd stick with the setup you have currently and add an additional User/Account domain object, corresponding repository/DAO, etc. If you want to retrieve the domain-specific user/account, you can do so as follows:
方法#2:像我一样,您可能会发现(尤其是在多次迭代的范围内)最好将特定于域的用户/帐户详细信息与Spring Security 特定的用户/帐户详细信息分开。这对你来说可能是也可能不是。但是如果你能在这种方法中找到任何智慧,那么你会坚持你当前的设置并添加一个额外的用户/帐户域对象、相应的存储库/DAO 等。如果你想检索特定于域的用户/帐户,您可以这样做:
User user = userDao.getByUsername(SecurityContextHolder.getContext().getAuthentication().getName());
Q#2: Spring Security automatically stores the UserDetails in the session (unless you've explicitly taken steps to override that behavior). So there's no need for you to do this yourself in each of your controller methods. The SecurityContextHolder object you've been dealing with is actually populated (by SS) with SecurityContext including the Authentication object, UserDetails, etc. at the beginning of every request. This context is cleared at the end of each request, but the data always remains in the session.
Q#2:Spring Security 会自动将 UserDetails 存储在会话中(除非您已明确采取措施来覆盖该行为)。因此,您无需在每个控制器方法中自己执行此操作。您一直在处理的 SecurityContextHolder 对象实际上(由 SS)填充了 SecurityContext,包括在每个请求开始时的 Authentication 对象、UserDetails 等。在每个请求结束时清除此上下文,但数据始终保留在会话中。
It's worth noting, however, that it's not really a great practice to be dealing with HttpServletRequest, HttpSession objects, etc. in a Spring MVC controller if you can avoid it. Spring almost always offers cleaner, more idiomatic means of achieving things withoutthe need for doing so. The advantage to that would be that controller method signatures and logic cease to be dependent on things that are difficult to mock in a unit test (e.g. the HttpSession) and instead of dependent on your own domain objects (or stubs/mocks of those domain objects). This drastically increases the testability of your controllers... and thus increases the liklihood that you actually WILL test your controllers. :)
然而,值得注意的是,如果可以避免的话,在 Spring MVC 控制器中处理 HttpServletRequest、HttpSession 对象等并不是一个很好的做法。Spring 几乎总是提供更简洁、更惯用的方法来实现目标,而无需这样做。这样做的好处是控制器方法签名和逻辑不再依赖于在单元测试中难以模拟的东西(例如 HttpSession),而不是依赖于您自己的域对象(或那些域对象的存根/模拟) )。这极大地提高了您的控制器的可测试性……从而增加了您实际测试控制器的可能性。:)
Hope this helps some.
希望这对一些人有所帮助。
回答by Marc Johnson
In my opinion, Custom UserDetails implementation is great but should only be used for immutable characteristics of your user.
在我看来,自定义 UserDetails 实现很棒,但应该只用于用户的不可变特征。
Once your custom User object overrides UserDetails, it's not easily changed. You have to create a whole new authentication object with the modified details and cannot just stick the modified UserDetails object back into the security context.
一旦您的自定义 User 对象覆盖 UserDetails,它就不会轻易更改。您必须使用修改后的详细信息创建一个全新的身份验证对象,而不能只是将修改后的 UserDetails 对象重新放入安全上下文中。
In application that I'm building I've realized this and had to rearchitect it so that upon successful authentication details about the user that are changing with every request (but that I don't want to reload from the db on every page load) are going to need to be kept in the session separately, but still only accessible/changeable after an authentication check.
在我正在构建的应用程序中,我已经意识到这一点,并且必须重新构建它,以便在成功验证每个请求时更改的用户详细信息后(但我不想在每个页面加载时从数据库重新加载)将需要单独保存在会话中,但仍然只能在身份验证检查后访问/更改。
Trying to figure out if this WebArgumentResolver mentioned in https://stackoverflow.com/a/8769670/1411545is a better solution for my situation.
试图弄清楚https://stackoverflow.com/a/8769670/1411545 中提到的这个 WebArgumentResolver是否适合我的情况。
回答by Shaun the Sheep
Accessing the session directly is a bit messy, and can be error prone. For example, if the user is authenticated using remember-me or some other mechanism which doesn't involve a redirect, the session won't be populated until after that request completes.
直接访问会话有点麻烦,而且容易出错。例如,如果用户使用记住我或其他不涉及重定向的机制进行身份验证,则在该请求完成之前不会填充会话。
I would use a custom accessor interface to wrap the calls to the SecurityContextHolder. See my answer to this related question.
我会使用自定义访问器接口来包装对SecurityContextHolder. 请参阅我对这个相关问题的回答。

