diff --git a/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsService.java b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsService.java
new file mode 100644
index 0000000000..bb8da5295a
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsService.java
@@ -0,0 +1,58 @@
+package org.springframework.security.userdetails.decorator;
+
+import org.springframework.security.userdetails.UserDetailsService;
+import org.springframework.security.userdetails.UserDetails;
+import org.springframework.security.userdetails.UsernameNotFoundException;
+import org.springframework.security.LockedException;
+import org.springframework.security.DisabledException;
+import org.springframework.security.AccountExpiredException;
+import org.springframework.security.CredentialsExpiredException;
+import org.springframework.security.SpringSecurityMessageSource;
+import org.springframework.security.AuthenticationException;
+import org.springframework.dao.DataAccessException;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.util.Assert;
+
+/**
+ * Decorates a {@link UserDetailsService}, making it throw an exception if the account is locked, disabled etc. This
+ * removes the need for separate account status checks in classes which make use of a UserDetailsService.
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class StatusCheckingUserDetailsService implements UserDetailsService {
+ private UserDetailsService delegate;
+
+ protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
+
+ public StatusCheckingUserDetailsService(UserDetailsService userDetailsService) {
+ this.delegate = userDetailsService;
+ }
+
+ public UserDetails loadUserByUsername(String username) throws AuthenticationException, DataAccessException {
+
+ UserDetails user = delegate.loadUserByUsername(username);
+
+ Assert.notNull(user, "UserDetailsService returned null user, an interface violation.");
+
+ if (!user.isAccountNonLocked()) {
+ throw new LockedException(messages.getMessage("UserDetailsService.locked", "User account is locked"));
+ }
+
+ if (!user.isEnabled()) {
+ throw new DisabledException(messages.getMessage("UserDetailsService.disabled", "User is disabled"));
+ }
+
+ if (!user.isAccountNonExpired()) {
+ throw new AccountExpiredException(messages.getMessage("UserDetailsService.expired",
+ "User account has expired"));
+ }
+
+ if (!user.isCredentialsNonExpired()) {
+ throw new CredentialsExpiredException(messages.getMessage("UserDetailsService.credentialsExpired",
+ "User credentials have expired"));
+ }
+
+ return user;
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsServiceTests.java b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsServiceTests.java
new file mode 100644
index 0000000000..9146e82b1d
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/userdetails/decorator/StatusCheckingUserDetailsServiceTests.java
@@ -0,0 +1,69 @@
+package org.springframework.security.userdetails.decorator;
+
+import org.springframework.security.userdetails.UserDetailsService;
+import org.springframework.security.userdetails.UserDetails;
+import org.springframework.security.userdetails.UsernameNotFoundException;
+import org.springframework.security.userdetails.User;
+import org.springframework.security.GrantedAuthority;
+import org.springframework.security.GrantedAuthorityImpl;
+import org.springframework.security.LockedException;
+import org.springframework.security.DisabledException;
+import org.springframework.security.CredentialsExpiredException;
+import org.springframework.security.AccountExpiredException;
+
+import org.springframework.dao.DataAccessException;
+
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class StatusCheckingUserDetailsServiceTests {
+ private StatusCheckingUserDetailsService us = new StatusCheckingUserDetailsService(new MockUserDetailsService());
+
+ @Test
+ public void validAccountIsSuccessfullyLoaded() throws Exception {
+ us.loadUserByUsername("valid");
+ }
+
+ @Test(expected = LockedException.class)
+ public void lockedAccountThrowsLockedException() throws Exception {
+ us.loadUserByUsername("locked");
+ }
+
+ @Test(expected = DisabledException.class)
+ public void disabledAccountThrowsDisabledException() throws Exception {
+ us.loadUserByUsername("disabled");
+ }
+
+ @Test(expected = CredentialsExpiredException.class)
+ public void credentialsExpiredAccountThrowsCredentialsExpiredException() throws Exception {
+ us.loadUserByUsername("credentialsExpired");
+ }
+
+ @Test(expected = AccountExpiredException.class)
+ public void expiredAccountThrowsAccountExpiredException() throws Exception {
+ us.loadUserByUsername("expired");
+ }
+
+ class MockUserDetailsService implements UserDetailsService {
+ private Map users = new HashMap ();
+ private GrantedAuthority[] auths = new GrantedAuthority[] {new GrantedAuthorityImpl("A")};
+
+ MockUserDetailsService() {
+ users.put("valid", new User("valid", "",true,true,true,true,auths));
+ users.put("locked", new User("locked", "",true,true,true,false,auths));
+ users.put("disabled", new User("disabled", "",false,true,true,true,auths));
+ users.put("credentialsExpired", new User("credentialsExpired", "",true,true,false,true,auths));
+ users.put("expired", new User("expired", "",true,false,true,true,auths));
+ }
+
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
+ return users.get(username);
+ }
+ }
+}