我有一个设计问题,我不知道如何解决。我正在使用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?
我一直在考虑两种解决方案,但我不确定:
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文件并尝试通过
来获取id<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吗? 与我最初的问题相比,这已经开始成为别的东西,我应该开始另一个问题。
很抱歉我缺乏这方面的经验。我必须阅读。
答案 0 :(得分: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&#39; s {{ 1}} interface(应该如此),那么您应该能够使用以下标记访问UserDetails
属性:
id
您可以将其内联使用或将值分配给另一个变量:
<security:authentication property="principal.id" />
这适用于<security:authentication property="principal.id" var="accountId" />
...
${accountId}
对象上的任何属性(只要用户经过身份验证,很明显。)
更新问题后编辑:
确保Account
的实施正在返回UserDetailsService
的实例。听起来你实际上并没有使用Account
类型作为你的校长。
Account
在问题更新后编辑#2
好问题就在这里:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = ... // fetch your Account
return account;
}
您正在使用&#34;快捷方式&#34;可以这么说来获取Spring Security提供的<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
。但是,您希望您的主体属于org.springframework.security.core.userdetails.User
类型。这不会自动发生在你身上。您应该提供自己的Account
实现,而不是使用jdbc-user-service
元素。像这样:
UserDetailsService
...使用<authentication-manager ...>
<authentication-provider user-service-ref="myUserDetailsServiceImpl"/>
</authentication-manager>
实现,例如:
UserDetailsService
这应该让你朝着正确的方向前进。
答案 1 :(得分:0)
我对春天一无所知,但取决于他们如何扩展身份主体,可能还有其他字段,例如您可以抓取的用户ID。您也可以在spring中配置安全性,并将自己的项目添加到标识集合中。如果这不可行,那么考虑一个actionfilter,它根据电子邮件地址(在某个存储库中)查找用户ID并填充您的模型/视图包。装饰需要它的每个控制器或动作。