如何使用Spring Security 3.1更改当前用户的登录名?

时间:2012-12-23 10:45:26

标签: java spring spring-security

我要求每个用户在登录时都可以更改自己的用户名。问题是如何在Spring Security的身份验证令牌中更新用户名(Principal)?

我必须更新它,因为我使用身份验证令牌中的原始名称来识别某些业务用例中的用户。

我使用基于表单和cookie记忆我的登录,因此我的身份验证令牌为UsernamePaswordAuthenticationTokenRememberMeAuthenticationToken。两者都有一个字段principal,其中存储了登录名。不幸的是,这个变量是final,所以我不能改变它的值。

是否有人知道Spring Security如何推荐更改身份验证令牌中的Principal

我当前的工作原理是我将UsernamePaswordAuthenticationTokenRememberMeAuthenticationToken替换为具有附加非最终主要字段的子类,并覆盖getPrincipal()方法以返回此附加主体原来的。然后我还将生成此标记的两个类子类化,以创建我的标记而不是原始标记。 ---但我觉得这是一个很大的黑客。

3 个答案:

答案 0 :(得分:8)

为什么选择令牌,即Authentication子类? Authentication.getPrincipal()不会在您的案例中返回UserDetails的实例吗?

如果您提供了自己的UserDetails实施方案(使用setUsername()方法的实施方案),那么如果我能正确理解您的情况,您可以免费为您提供免费认证。

答案 1 :(得分:7)

我做了类似的事情,这有点像黑客,但我所做的是更改并保存新的UserDetails,然后为会话添加一个新的身份验证令牌以获取更新的凭据:

Authentication request = new UsernamePasswordAuthenticationToken(user.getUsername(), password);
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);

答案 2 :(得分:5)

我实施了MarcelStör提出的想法。

  

为什么选择令牌,即验证子类? Authentication.getPrincipal()是否在您的情况下返回UserDetails的实例?

     

如果你提供了自己的UserDetails实现(一个带有setUsername()方法),那么如果我能正确理解你的情况,你可以免费认证你。

我想分享实施:

这是具有可修改用户名的UserDetails对象。我把它作为org.springframework.security.core.userdetails.User的子类,因为我将它与Jdbc User Details Service一起使用,它正常地创建了这个类。

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
/**
 * Extension of {@link User} where it is possible to change the username.
 */
public class UpdateableUserDetails extends User {

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 9034840503529809003L;

    /**
     * The user name that can be modified.
     * It "overrides" the username field from class {@link User}.
     */
    private String modfiableUsername;

    /**
     * Construct the <code>User</code> with the details required by
     * {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider}.
     *
     * @param username the username presented to the
     *        <code>DaoAuthenticationProvider</code>
     * @param password the password that should be presented to the
     *        <code>DaoAuthenticationProvider</code>
     * @param enabled set to <code>true</code> if the user is enabled
     * @param accountNonExpired set to <code>true</code> if the account has not
     *        expired
     * @param credentialsNonExpired set to <code>true</code> if the credentials
     *        have not expired
     * @param accountNonLocked set to <code>true</code> if the account is not
     *        locked
     * @param authorities the authorities that should be granted to the caller
     *        if they presented the correct username and password and the user
     *        is enabled. Not null.
     *
     * @throws IllegalArgumentException if a <code>null</code> value was passed
     *         either as a parameter or as an element in the
     *         <code>GrantedAuthority</code> collection
     */
    public UpdateableUserDetails(final String username, final String password, final boolean enabled,
            final boolean accountNonExpired, final boolean credentialsNonExpired, final boolean accountNonLocked,
            final Collection<? extends GrantedAuthority> authorities) throws IllegalArgumentException {
        super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
        this.modfiableUsername = username;
    }

    /**
     * Calls the more complex constructor with all boolean arguments set to {@code true}.
     * @param username the username presented to the
     *        <code>DaoAuthenticationProvider</code>
     * @param password the password that should be presented to the
     *        <code>DaoAuthenticationProvider</code>
      * @param authorities the authorities that should be granted to the caller
     *        if they presented the correct username and password and the user
     *        is enabled. Not null.
     */
    public UpdateableUserDetails(final String username, final String password,
            final Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
        this.modfiableUsername = username;
    }

    /**
     * Return the modifiable username instead of the fixed one.
     *
     * @return the username
     */
    @Override
    public String getUsername() {
        return this.modfiableUsername;
    }

    public void setUsername(final String username) {
        this.modfiableUsername = username;
    }

    /**
     * Returns {@code true} if the supplied object is a {@code User} instance with the
     * same {@code username} value.
     * <p>
     * In other words, the objects are equal if they have the same user name, representing the
     * same principal.
     *
     * @param rhs the other object
     * @return true if equals
     */
    @Override
    public boolean equals(final Object rhs) {
        if (rhs instanceof User) {
            return this.modfiableUsername.equals(((User) rhs).getUsername());
        }
        return false;
    }

    /**
     * Returns the hashcode.
     * 
     * In order not to get any problems with any hash sets that based on the fact that this hash is not changed
     * over livetime and not to fail one of the constraints for {@link Object#hashCode()},
     * this method always returns the same constant hash value.
     * 
     * I expect that this is no such deal, because we expect not to have so many logged in users, so the hash sets
     * that use this as an key will not get so slow.  
     *
     * @return the hash
     */
    @Override
    public int hashCode() {
        return 1;
    }

    /**
     * Like {@link User#toString()}, but print the modifiable user name.
     *
     * @return the string representation with all details
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString()).append(": ");
        sb.append("Username: ").append(this.modfiableUsername).append("; ");
        sb.append("Password: [PROTECTED]; ");
        sb.append("Enabled: ").append(isEnabled()).append("; ");
        sb.append("AccountNonExpired: ").append(isAccountNonExpired()).append("; ");
        sb.append("credentialsNonExpired: ").append(isCredentialsNonExpired()).append("; ");
        sb.append("AccountNonLocked: ").append(isAccountNonLocked()).append("; ");

        if (!getAuthorities().isEmpty()) {
            sb.append("Granted Authorities: ");

            boolean first = true;
            for (GrantedAuthority auth : getAuthorities()) {
                if (!first) {
                    sb.append(",");
                }
                first = false;

                sb.append(auth);
            }
        } else {
            sb.append("Not granted any authorities");
        }
        return sb.toString();
    }    
}

UserDetailsService

的子类
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
/**
 * Create {@link UpdateableUserDetails} instead of {@link org.springframework.security.core.userdetails.User} user details.
 */
public class JdbcDaoForUpdatableUsernames extends JdbcDaoImpl {

    /**
     * Instantiates a new jdbc dao for updatable usernames impl.
     *
     * @param privilegesService the privileges service
     */
    public JdbcDaoForUpdatableUsernames(final PrivilegesService privilegesService) {
        super(privilegesService);
    }

    /**
     * Can be overridden to customize the creation of the final UserDetailsObject which is
     * returned by the <tt>loadUserByUsername</tt> method.
     *
     * @param username the name originally passed to loadUserByUsername
     * @param userFromUserQuery the object returned from the execution of the
     * @param combinedAuthorities the combined array of authorities from all the authority loading queries.
     * @return the final UserDetails which should be used in the system.
     */
    @Override
    protected UserDetails createUserDetails(final String username, final UserDetails userFromUserQuery,
            final List<GrantedAuthority> combinedAuthorities) {
        String returnUsername = userFromUserQuery.getUsername();

        if (!isUsernameBasedPrimaryKey()) {
            returnUsername = username;
        }

        return new UpdateableUserDetails(returnUsername,
                userFromUserQuery.getPassword(),
                userFromUserQuery.isEnabled(),
                true,
                true,
                true,
                combinedAuthorities);
    }
}

我希望有人也可以使用它。