Spring Security Custom Filter (Change Password)
I'm using Spring Security for securing HTTP requests to a website. The primary usage is for securing pages such that the user is redirected to the login page when trying to access those pages.
However, I have a further requirement. In my model, I can flag a user's password as being temporary such that, when they successfully login, they should be automatically forced to change their password. Once the password is changed, they should then be forwarded on to the page they were originally trying to access.
Has anyone used Spring Security for this purpose? Do I need to create my own开发者_开发问答 custom filter?
Thanks,
Andrew
In Spring Security 3.0 you can implement a custom AuthenticationSuccessHandler
.
In this handler you can redirect a user with temporary password to the password change page instead of the originally requested page. After password is changed, you may redirect user to the originally requested page using SavedRequestAwareAuthenticationSuccessHandler
, which is the default handler implementation.
public class MyHandler implements AuthenticationSuccessHandler {
private AuthenticationSuccessHandler target = new SavedRequestAwareAuthenticationSuccessHandler();
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication auth) {
if (hasTemporaryPassword(auth)) {
response.sendRedirect("/changePassword");
} else {
target.onAuthenticationSuccess(request, response, auth);
}
}
public void proceed(HttpServletRequest request,
HttpServletResponse response, Authentication auth) {
target.onAuthenticationSuccess(request, response, auth);
}
}
@Controller("/changePassword")
public class ChangePasswordController {
@Autowired
private MyHandler handler;
@RequestMapping(method = POST)
public void changePassword(HttpServletRequest request,
HttpServletResponse response,
@RequestParam(name = "newPassword") String newPassword) {
// handle password change
...
// proceed to the secured page
handler.proceed(request, response, auth);
}
// form display method, etc
...
}
A little late on this, but hopefully this can help others finding this link. If you use a custom UserDetailsService, you can set the User object's credentialsNonExpired to false, for example, to not allow access to any secure content until that field is set back to true.
Basically, when you have password expiration, you will set a field in your User model (passwordExpired maybe), and when your UserDetailsService pulls the user, your UserDetailsService will use that value to set credentialsNonExpired.
Then, all you need to do is add some config to your applicationContext-security.xml to setup authentication exception mappings. This will allow you to catch the exception thrown with expired credentials and force the user to a reset password page. You can additionally catch locked and disabled accounts using a similar method. The config example is shown below:
applicationContext-security.xml
<beans:bean id="exceptionTranslationFilter" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
<beans:property name="exceptionMappings">
<beans:props>
<beans:prop key="org.springframework.security.authentication.BadCredentialsException">/login_error</beans:prop>
<beans:prop key="org.springframework.security.authentication.CredentialsExpiredException">/password_expired</beans:prop>
<beans:prop key="org.springframework.security.authentication.LockedException">/locked</beans:prop>
<beans:prop key="org.springframework.secuirty.authentication.DisabledException">/disabled</beans:prop>
</beans:props>
</beans:property>
</beans:bean>
<http use-expressions="true">
<!-- ADD BLACKLIST/WHITELIST URL MAPPING -->
<form-login login-page="/login" default-target-url="/" authentication-failure-handler-ref="exceptionTranslationFilter" />
</http>
Then just make sure you have your controllers setup to serve those links with the appropriate content.
Yes, 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.
Very useful answer form jyore, it was exactly what I was looking for. In case you are using a custom class implementing UserDetailsService
, you can do it as the following along with the above bean definition in your applicationContext.xml
. One thing is that based on your CML header you might need to use <bean ....
or <prop ...
instead of <beans:bean ...
or <beans:prop ...
import ......
@Service("userService")
public class UserDetailsServiceImpl implements UserDetailsService {
private static Logger logger = LoggerFactory
.getLogger(UserDetailsServiceImpl.class);
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername( String username )
throws UsernameNotFoundException, DataAccessException , CredentialsExpiredException ,BadCredentialsException ,
LockedException , DisabledException , UsernameNotFoundException
{
User user = userDao.getUserByUsername( username );
System.out.println("User Found");
if( user == null ){
// System.out.println("User Not Found");
logger.error( "User Not Found");
throw new UsernameNotFoundException( username + " is not found." );
}
if( user.isEnabled() == false ){
// System.out.println("User not enabled");
logger.error( "User not enabled");
throw new DisabledException( "User not enabled" );
}
if( user.isLocked() == true ){
//System.out.println("User is Locked");
logger.error( "User is Locked");
throw new LockedException( "User is Locked" );
}
if( user.isPasswordExpired() == true ){
// System.out.println("Password Expired");
logger.error( "Password Expired");
throw new CredentialsExpiredException( "Password Expired" );
}
return user;
}
}
精彩评论