14

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

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

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

有谁知道 Spring Security 如何建议更改Principal身份验证令牌?

我当前的解决方法是,我将UsernamePaswordAuthenticationTokenand替换为RememberMeAuthenticationToken具有附加的非最终主体字段的子类,并覆盖该getPrincipal()方法以返回此附加主体而不是原始主体。然后,我还对生成此标记的两个类进行了子类化,以创建我的标记而不是原始标记。---但我觉得这是一个很大的hack。

4

3 回答 3

10

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

Authentication request = new UsernamePasswordAuthenticationToken(user.getUsername(), password);
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
于 2012-12-24T09:39:05.987 回答
9

为什么要使用令牌Authentication子类?在您的情况下不Authentication.getPrincipal()返回一个实例UserDetails

如果您在验证您在家时提供了自己的UserDetails实现(带有方法),如果我正确理解您的情况。setUsername()

于 2012-12-23T21:28:49.270 回答
5

我已经实现了 Marcel Stör 提出的想法。

为什么要使用令牌,即身份验证子类?在您的情况下,Authentication.getPrincipal() 不会返回 UserDetails 的实例吗?

如果您在验证您在家时提供了自己的 UserDetails 实现(带有 setUsername() 方法),如果我正确理解您的情况。

我想分享一下实现:

这是具有可修改用户名的 UserDetails 对象。我将它设为一个子类,org.springframework.security.core.userdetails.User因为我将它与通常创建此类的 Jdbc 用户详细信息服务一起使用。

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);
    }
}

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

于 2013-01-05T17:28:09.210 回答