如何使用Spring Security在用户初始登录时强制更改密码 [英] How to Enforce Change Password on User's initial login using Spring Security
问题描述
使用Spring Security初次登录时实施强制密码更改的最优雅方法是什么?
What would be the most elegant way of implementing a force password change upon user's initial login using Spring Security?
我尝试实现自定义 AuthenticationSuccessHandler
如此处所述,但正如 rodrigoap 。 / p>
I tried implementing a custom AuthenticationSuccessHandler
as mentioned here, but as mentioned by rodrigoap, if a user manually inputs the URL at the address bar, the user will still be able to proceed to that page even if he didn't change his password.
我用过滤器ForceChangePasswordFilter做了这个。因为如果用户手动键入url,他们可以绕过更改密码表单。使用过滤器,请求总是被拦截。
I did this with a filter ForceChangePasswordFilter. Because if the user types the url by hand they can bypass the change password form. With the filter the request always get intercepted.
因此,我继续实施自定义过滤器。
As such, I proceeded with implementing a custom filter.
我的问题是,当我实现自定义过滤器并在其中发送重定向时,它再次通过过滤器导致无限重定向循环,如上所述here 。
我尝试通过在security-context.xml中声明两个http标签来实现所提到的解决方案,第一个标签具有 pattern
属性,但它仍然通过我的自定义过滤器:
My question is this, when I implement a custom filter and send a redirect inside it, it goes through the filter again causing an infinite redirect loop as mentioned here.
I tried implementing the solution mentioned by declaring two http tags in my security-context.xml with the first tag having the pattern
attribute as such but it still goes through my custom filter:
<http pattern="/resources" security="none"/>
<http use-expressions="true" once-per-request="false"
auto-config="true">
<intercept-url pattern="/soapServices/**" access="permitAll" requires-channel="https"/>
...
<custom-filter position="LAST" ref="passwordChangeFilter" />
</http>
...
<beans:bean id="passwordChangeFilter"
class="my.package.ForcePasswordChangeFilter"/>
<beans:bean id="customAuthenticationSuccessHandler"
class="my.package.CustomAuthenticationSuccessHandler" >
</beans:bean>
<beans:bean id="customAuthenticationFailureHandler"
class="my.package.CustomAuthenticationFailureHandler" >
<beans:property name="defaultFailureUrl" value="/login"/>
</beans:bean>
我当前的实现是什么(有效):
What my current implementation is (which works) is:
- 在我的自定义身份验证成功处理程序中,我设置了会话属性
isFirstLogin
- In我的ForcePasswordChangeFilter,我检查会话
isFirstLogin
是否设置为
- 如果是,那么我发送一个重定向到我的强制密码更改
- 否则,我打电话给
chain.doFilter()
- Inside my custom authentication success handler, I set a session attribute
isFirstLogin
- In my ForcePasswordChangeFilter, I check if the session
isFirstLogin
is set- If it is, then I send a redirect to my force password change
- Else, I call
chain.doFilter()
我对此实现的问题是访问我的资源文件夹也会通过此过滤器导致我的页面失真(因为* .js和* .css未成功检索)。
这就是我在我的安全应用程序context.xml中尝试使用两个< http>
标签的原因(这不起作用)。My problem with this implementation is that access to my resources folder also goes through this filter which causes my page to be distorted (because *.js and *.css are not successfully retrieved). This is the reason I tried having two
<http>
tags in my security app context.xml (which didn't work).因此,如果servletPath启动或包含/ resources,我最终必须手动过滤请求。
我不希望它像这样 - 必须手动过滤请求路径 - 但是现在它就是我拥有的。As such, I ended up having to manually filter the request if the servletPath starts or contains "/resources". I didn't want it to be like this - having to manually filter the request path - but for now it's what I have.
什么更优雅这样做的方式?
What's the more elegant way of doing this?
推荐答案
我通过为用户提供状态值解决了这个问题,
I solved this issue by providing a status value for the user,
- status = -1;初次登录
- status = 0; deactive account
- status = 1;活动帐户
和
security.xml
中的2个自定义身份验证控制器。首先检查用户名,传递和秒,以获取其他控件,如初始登录,密码到期策略。and 2 custom authentication controller in the
security.xml
. First for to check username, pass and second for the additional controls like initial login, password expiration policy.如果是首次登录,请提供正确的用户名和密码值,第一个控制器(
user-service-ref =jdbcUserService
)无法验证用户身份(因为用户的status = -1
)比第二个控制器(ref =myAuthenticationController
)捕获请求。在此控制器中,抛出DisabledException
。In case of first login, providing correct values of username and password, first controller (
user-service-ref="jdbcUserService"
) fails to authenticate user(because user'sstatus=-1
) than second controller(ref="myAuthenticationController"
) catches the request. In this controllerDisabledException
is thrown.最后,您可以将用户重定向到
AuthenticationFailureListener
的onAuthenticationFailure上的密码更改页面
方法。Finally, you can redirect user to password-change page on
AuthenticationFailureListener
'sonAuthenticationFailure
method.security.xml的一部分
<authentication-manager alias="authenticationManager"> <authentication-provider user-service-ref="jdbcUserService"> <password-encoder ref="passwordEncoder" /> </authentication-provider> <authentication-provider ref="myAuthenticationController" /> </authentication-manager> <beans:bean id="jdbcUserService" class="org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"> <beans:property name="rolePrefix" value="ROLE_" /> <beans:property name="dataSource" ref="dataSource" /> <beans:property name="usersByUsernameQuery" value="SELECT user_name as userName, PASSWORD as password, STATUS as status FROM USER WHERE user_name = ? AND STATUS=1" /> <beans:property name="authoritiesByUsernameQuery" value="SELECT user_name as userName, ROLE as authority FROM USER WHERE user_name = ?" /> </beans:bean> <beans:bean id="myAuthenticationController" class="com.test.myAuthenticationController"> <beans:property name="adminUser" value="admin" /> <beans:property name="adminPassword" value="admin" /> </beans:bean> <!--Custom authentication success handler for logging/locking/redirecting--> <beans:bean id="authSuccessHandler" class="com.test.AuthenticationSuccessListener"/> <!--Custom authentication failure handler for logging/locking/redirecting--> <beans:bean id="authFailureHandler" class="com.test.AuthenticationFailureListener"/>
@Service("myAuthenticationController") public class MyAuthenticationController extends AbstractUserDetailsAuthenticationProvider { private final Logger logger = Logger.getLogger(getClass()); @Autowired private WfmUserValidator userValidator; private String username; private String password; @Required public void setAdminUser(String username) { this.username = username; } @Required public void setAdminPassword(String password) { this.password = password; } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { return; } @Override protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { String password = (String) authentication.getCredentials(); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); String userRole = ""; if (status = -1) { throw new DisabledException("It is first login. Password change is required!"); } else if (password expired) { throw new CredentialsExpiredException("Password is expired. Please change it!"); } return new User(userName, password, true, // enabled true, // account not expired true, // credentials not expired true, // account not locked authorities); } }
public class AuthenticationFailureListener implements AuthenticationFailureHandler { private static Logger logger = Logger.getLogger(AuthenticationFailureListener.class); private static final String BAD_CREDENTIALS_MESSAGE = "bad_credentials_message"; private static final String CREDENTIALS_EXPIRED_MESSAGE = "credentials_expired_message"; private static final String DISABLED_MESSAGE = "disabled_message"; private static final String LOCKED_MESSAGE = "locked_message"; @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse res, AuthenticationException ex) throws IOException, ServletException { // TODO Auto-generated method stub String userName = req.getParameter("j_username"); logger.info("[AuthenticationFailure]:" + " [Username]:" + userName + " [Error message]:" + ex.getMessage()); if (ex instanceof BadCredentialsException) { res.sendRedirect("../pages/login.jsf?message=" + MessageFactory.getMessageValue(BAD_CREDENTIALS_MESSAGE)); } else if (ex instanceof CredentialsExpiredException) { res.sendRedirect("../pages/changecredentials.jsf?message=" + MessageFactory.getMessageValue(CREDENTIALS_EXPIRED_MESSAGE)); } else if (ex instanceof DisabledException) { res.sendRedirect("../pages/changecredentials.jsf?message=" + MessageFactory.getMessageValue(DISABLED_MESSAGE)); } else if (ex instanceof LockedException) { res.sendRedirect("../pages/login.jsf?message=" + MessageFactory.getMessageValue(LOCKED_MESSAGE)); } } }
这篇关于如何使用Spring Security在用户初始登录时强制更改密码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!