Получите идентификатор пользователя, не пропуская его через каждый контроллер

У меня есть проблема дизайна, которую я не знаю, как решить. Я использую Spring 3.2.4 и Spring 3.1.4.

У меня есть таблица учетных записей в моей базе данных, которая выглядит следующим образом:

create table Account (id identity,
                        username varchar unique,
                        password varchar not null,
                        firstName varchar not null, 
                        lastName varchar not null,
                        university varchar not null,
                        primary key (id));

До недавнего времени моим именем пользователя было только имя пользователя, но вместо этого я изменил его на адрес электронной почты, поскольку многие пользователи хотят вместо этого войти в систему.

У меня есть заголовок, который я включаю на всех своих страницах, где есть ссылка на профиль пользователя, например:

<a href="/project/users/<%= request.getUserPrincipal().getName()%>" class="navbar-link"><strong><%= request.getUserPrincipal().getName()%></strong></a>

Проблема в том, что <%= request.getUserPrincipal().getName()%> возвращает письмо сейчас, я не хочу связывать пользователя с их письмами. Вместо этого я хочу использовать идентификатор, чтобы каждый пользователь имел ссылку на профиль.

Как мне получить идентификаторы пользователей на каждой странице?

Я думал о двух решениях, но я не уверен:

  1. Измените принципал, чтобы он также содержал идентификатор, не знаю, как это сделать, и у вас возникли проблемы с поиском полезной информации по теме.
  2. Добавьте атрибут модели ко всем моим контроллерам, которые содержат всего пользователя, но это было бы очень уродливо, как это.

Account account = entityManager.find(Account.class, email);
model.addAttribute("account", account);

Есть и другие пути, и я понятия не имею, какой из них выбрать.

Я надеюсь, что это достаточно ясно, и спасибо за любую помощь в этом.

====== Редактировать в соответствии с ответом =======

Я отредактировал Account для реализации UserDetails, теперь он выглядит так (исправлю автоматически сгенерированные вещи позже):

@Entity
@Table(name="Account")
public class Account implements UserDetails {

    @Id
    private int id;

    private String username;

    private String password;

    private String firstName;

    private String lastName;

    @ManyToOne
    private University university;

    public Account() {

    }

    public Account(String username, String password, String firstName, String lastName, University university) {
        this.username = username;
        this.password = password;
        this.firstName = firstName;
        this.lastName = lastName;
        this.university = university;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

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

    public void setPassword(String password) {
        this.password = password;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public University getUniversity() {
        return university;
    }

    public void setUniversity(University university) {
        this.university = university;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isEnabled() {
        // TODO Auto-generated method stub
        return true;
    }
}

Я также добавил

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

К моим JSP-файлам и пытаясь получить идентификатор по

<sec:authentication property="principal.id" />

Это дает мне следующее

org.springframework.beans.NotReadablePropertyException: Invalid property 'principal.id' of bean class [org.springframework.security.authentication.UsernamePasswordAuthenticationToken]: Bean property 'principal.id' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

====== Редактировать 2 в соответствии с ответом =======

Я основал свое приложение на весенних социальных примерах, и мне никогда не приходилось ничего менять до сих пор.

Это файлы, которые я считаю актуальными, пожалуйста, скажите мне, если есть что-то, что вам нужно увидеть, кроме этого.

AccountRepository.java

public interface AccountRepository {

    void createAccount(Account account) throws UsernameAlreadyInUseException;

    Account findAccountByUsername(String username);

}

JdbcAccountRepository.java

@Repository
public class JdbcAccountRepository implements AccountRepository {

    private final JdbcTemplate jdbcTemplate;

    private final PasswordEncoder passwordEncoder;

    @Inject
    public JdbcAccountRepository(JdbcTemplate jdbcTemplate, PasswordEncoder passwordEncoder) {
        this.jdbcTemplate = jdbcTemplate;
        this.passwordEncoder = passwordEncoder;
    }

    @Transactional
    public void createAccount(Account user) throws UsernameAlreadyInUseException {
        try {
            jdbcTemplate.update(
                    "insert into Account (firstName, lastName, username, university, password) values (?, ?, ?, ?, ?)",
                    user.getFirstName(), user.getLastName(), user.getUsername(), user.getUniversity(),
                    passwordEncoder.encode(user.getPassword()));
        } catch (DuplicateKeyException e) {
            throw new UsernameAlreadyInUseException(user.getUsername());
        }
    }

    public Account findAccountByUsername(String username) {
        return jdbcTemplate.queryForObject("select username, firstName, lastName, university from Account where username = ?",
                new RowMapper<Account>() {
                    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
                        return new Account(rs.getString("username"), null, rs.getString("firstName"), rs.getString("lastName"), new University("test"));
                    }
                }, username);
    }

}

security.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <http pattern="/resources/**" security="none" />
    <http pattern="/project/" security="none" />

    <http use-expressions="true">
        <!-- Authentication policy -->
        <form-login login-page="/signin" login-processing-url="/signin/authenticate" authentication-failure-url="/signin?error=bad_credentials" />
        <logout logout-url="/signout" delete-cookies="JSESSIONID" />
        <intercept-url pattern="/addcourse" access="isAuthenticated()" />
        <intercept-url pattern="/courses/**/**/edit" access="isAuthenticated()" />
        <intercept-url pattern="/users/**/edit" access="isAuthenticated()" />
    </http>

    <authentication-manager alias="authenticationManager">
        <authentication-provider>
            <password-encoder ref="passwordEncoder" />
            <jdbc-user-service data-source-ref="dataSource" 
                            users-by-username-query="select username, password, true from Account where username = ?"
                            authorities-by-username-query="select username, 'ROLE_USER' from Account where username = ?"/>
        </authentication-provider>
        <authentication-provider>
            <user-service>
                <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

</beans:beans>

И это моя попытка реализации UserDetailsService

public class RepositoryUserDetailsService implements UserDetailsService {

    private final AccountRepository accountRepository;

    @Autowired
    public RepositoryUserDetailsService(AccountRepository repository) {
        this.accountRepository = repository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account user = accountRepository.findAccountByUsername(username);

        if (user == null) {
            throw new UsernameNotFoundException("No user found with username: " + username);
        }

        return user;

    }
}

Все равно выдает мне ту же ошибку, мне нужно где-нибудь добавить UserDetailsService? Это начинает быть чем-то другим по сравнению с моим первоначальным вопросом, возможно, мне следует начать другой вопрос.

Извините за отсутствие опыта в этом. Я должен прочитать.

2 ответа

Решение

Если вы используете Spring Security, может быть полезно отказаться от скриптлетов и вместо этого использовать пользовательские теги Spring Security:

http://docs.spring.io/spring-security/site/docs/3.1.4.RELEASE/reference/taglibs.html

Пока у вас есть id атрибут с getId() метод на вашем Account модель, и ваш Account модель реализует Spring Security UserDetails интерфейс (как и должно быть), то вы сможете получить доступ к id атрибут с использованием следующего тега:

<security:authentication property="principal.id" />

Вы можете использовать его внутри строки или присвоить значение другой переменной:

<security:authentication property="principal.id" var="accountId" />
...
${accountId}

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

РЕДАКТИРОВАТЬ ПОСЛЕ ВОПРОСА ОБНОВЛЕНО:

Убедитесь, что ваша реализация UserDetailsService возвращает экземпляр Account, Похоже, вы на самом деле не используете Account введите как ваш основной.

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        
    Account account = ... // fetch your Account
    return account;
}

РЕДАКТИРОВАТЬ № 2 ПОСЛЕ ВОПРОСА ОБНОВЛЕНО

Хорошо, проблема здесь:

<jdbc-user-service data-source-ref="dataSource" 
    users-by-username-query="select username, password, true from Account where username = ?"
    authorities-by-username-query="select username, 'ROLE_USER' from Account where username = ?"/>

Вы используете так называемый "ярлык", чтобы получить реализацию по умолчанию UserDetails интерфейс, предоставленный Spring Security, в частности org.springframework.security.core.userdetails.User, Тем не менее, вы хотите, чтобы ваш принципал был типа Account, Это не произойдет автоматически для вас. Вместо использования jdbc-user-service элемент, вы должны предоставить собственную реализацию UserDetailsService, Что-то вроде этого:

<authentication-manager ...>
    <authentication-provider user-service-ref="myUserDetailsServiceImpl"/>
</authentication-manager>

... с UserDetailsService реализация как:

@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
    ...
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = ...// load your Account
        return account;
    }
}

Это должно привести вас в правильном направлении.

Я ничего не знаю о Spring, но в зависимости от того, как они расширили принцип идентификации, могут быть другие поля, такие как идентификатор пользователя, который вы можете получить. Вы также можете настроить безопасность весной и добавить свои собственные элементы в коллекцию удостоверений. Если это невозможно, рассмотрите фильтр действий, который ищет идентификатор пользователя на основе адреса электронной почты (в некоторых репозиториях) и заполняет вашу модель / сумку. Украсьте каждый контроллер или действие, которое нуждается в этом.

Другие вопросы по тегам