2

我有一个设计问题,我不知道如何解决。我正在使用 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));

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

我在所有页面上都有一个标题,其中包含指向用户个人资料的链接,如下所示:

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

问题是现在<%= 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;
    }
}

我还添加了

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

}

安全.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>

这是我实现 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;

    }
}

仍然给我同样的错误,我需要在某处添加 UserDetailsS​​ervice 吗?与我最初的问题相比,这开始变得不同了,我也许应该开始另一个问题。

对不起,我缺乏这方面的经验。我必须阅读。

4

2 回答 2

6

如果您使用的是 Spring Security,那么放弃 scriptlet 并使用 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 = ?"/>

您正在使用所谓的“快捷方式”来获取UserDetailsSpring 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;
    }
}

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

于 2013-10-27T18:42:38.627 回答
0

我对 spring 一无所知,但根据他们如何扩展身份主体,可能还有其他字段,例如您可以获取的用户 ID。您还可以在 spring 中配置安全性并将您自己的项目添加到身份集合中。如果这不可行,那么考虑使用基于电子邮件地址(在某些存储库中)查找用户 ID 并填充您的模型/视图包的操作过滤器。装饰每个需要它的控制器或动作。

于 2013-10-27T18:20:16.147 回答