到达用户ID而不通过每个控制器

nil*_*lsi 2 spring spring-mvc spring-security principal

我有一个设计问题,我不知道如何解决.我正在使用Spring 3.2.4和Spring security 3.1.4.

我的数据库中有一个Account表,如下所示:

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));
Run Code Online (Sandbox Code Playgroud)

直到最近,我的用户名只是一个用户名,但我将其更改为电子邮件地址,因为许多用户想要使用该用户名登录.

我有一个标题,我在我的所有页面上都包含了一个链接到用户配置文件,如下所示:

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

问题是现在<%= request.getUserPrincipal().getName()%>返回电子邮件,我不想将用户链接到他们的电子邮件.相反,我想使用每个用户必须链接到配置文件的ID.

如何从每个页面访问用户ID?

我一直在考虑两种解决方案,但我不确定:

  1. 更改主体以包含id,不知道如何执行此操作并在查找有关该主题的良好信息时遇到问题.
  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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我还补充道

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
Run Code Online (Sandbox Code Playgroud)

到我的jsp文件并尝试通过

<sec:authentication property="principal.id" />
Run Code Online (Sandbox Code Playgroud)

这给了我以下内容

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?
Run Code Online (Sandbox Code Playgroud)

======根据回答编辑2 =======

我的应用程序基于春季社交样本,直到现在我从未改变过任何东西.

这是我认为相关的文件,请告诉我除了这个之外你还需要看到什么.

AccountRepository.java

public interface AccountRepository {

    void createAccount(Account account) throws UsernameAlreadyInUseException;

    Account findAccountByUsername(String username);

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

这是我尝试实现UserDetailsS​​ervice

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;

    }
}
Run Code Online (Sandbox Code Playgroud)

仍然给我同样的错误,我需要在某处添加UserDetailsS​​ervice吗?与我最初的问题相比,这开始成为别的东西,我应该开始另一个问题.

对不起,我缺乏这方面的经验.我得读了.

Mat*_*ter 6

如果您使用的是Spring Security,那么抛弃scriptlet并使用Spring Security自定义taglibs可能是有益的:

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" />
Run Code Online (Sandbox Code Playgroud)

您可以使用内联或将值分配给另一个变量:

<security:authentication property="principal.id" var="accountId" />
...
${accountId}
Run Code Online (Sandbox Code Playgroud)

这适用于Account对象上的任何属性(显然,只要用户通过身份验证).

问题更新后的编辑:

确保您的实现UserDetailsService正在返回一个实例Account.听起来你实际上并没有使用该Account类型作为你的主体.

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

问题更新后编辑#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 = ?"/>
Run Code Online (Sandbox Code Playgroud)

您正在使用一个"快捷方式",以获取UserDetailsSpring Security提供的接口的默认实现,特别是org.springframework.security.core.userdetails.User.但是,您希望您的主体属于类型Account.这不会自动发生在您身上.jdbc-user-service您应该提供自己的实现,而不是使用元素UserDetailsService.像这样的东西:

<authentication-manager ...>
    <authentication-provider user-service-ref="myUserDetailsServiceImpl"/>
</authentication-manager>
Run Code Online (Sandbox Code Playgroud)

...... UserDetailsService实现如下:

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

这应该让你朝着正确的方向前进.