Как изменить имя пользователя для текущего пользователя с помощью Spring Security 3.1?

У меня есть требование, чтобы каждый пользователь мог изменить свое имя пользователя во время входа в систему. Проблема заключается в том, как обновить имя пользователя (Principal) в Spring токене аутентификации безопасности?

(Я должен обновить его, потому что я использую имя prinicpal из токена аутентификации, чтобы идентифицировать пользователя в некоторых случаях использования в бизнесе.)

Я использую основанную на форме и cookie память для входа в систему, поэтому мои токены аутентификации UsernamePaswordAuthenticationToken и RememberMeAuthenticationToken. Оба имеют поле Principal, где хранится имя входа. К сожалению, эта переменная final, поэтому я не могу изменить ее значение.

Кто-нибудь имеет представление о том, как Spring Security рекомендует изменить Principal в токене аутентификации?

Мой текущий рабочий стол заключается в том, что я заменил подклассы UsernamePaswordAuthenticationToken и RememberMeAuthenticationToken подклассами, у которых есть дополнительное не окончательное основное поле и переопределить метод getPrincipal(), чтобы вернуть этот дополнительный главный, а не оригинальный. Затем я также подклассифицировал два класса, которые генерируют эти токены для создания моих токенов вместо исходного. --- Но я чувствую, что это большой взлом.

Ответ 1

Зачем идти с токеном i.e. Authentication подклассы? Не возвращает Authentication.getPrincipal() экземпляр UserDetails в вашем случае?

Если вы предоставили свою собственную реализацию UserDetails (один с методом setUsername()), а при аутентификации вы свободны дома, если я правильно понимаю ваш случай.

Ответ 2

Я сделал что-то подобное, и это немного взломало, но то, что я сделал, это изменение и сохранение новых UserDetails, а затем добавление нового нового токена аутентификации в сеанс для обновленных учетных данных:

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

Ответ 3

Я реализовал идею, предложенную Марселем Штер.

Зачем идти с токеном, то есть подклассы аутентификации? Не проверяет подлинность 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);
    }
}

Я надеюсь, что кто-то тоже сможет его использовать.