我正在开发一个Spring Boot应用程序,但我无法使安全性正常工作。我有一个自定义的用户类,这些实例保存在数据库中。
当第一个用户登录时,一切正常,但是当第二个用户登录时,第二个用户将替换所有连接设备中的第一个。我已在本地和Pivotal Cloud Foundry的安装中进行了测试,结果相同。
这是WebSecurityConfigurerAdapter实现:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http)throws Exception{
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin/**").hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.deleteCookies("JSESSIONID")
.and()
.rememberMe();
}
@Autowired
private AuthenticationProvider authenticationProvider;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer{}
}
这是UserService实现:
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
RentalWebsUser userInfo = userRepository.getUserByUsername(username);
List<GrantedAuthority> authorities = roleRepository.getRolesByUsername(username);
if(userInfo != null && !authorities.isEmpty()){
User user = new User(userInfo.getUsername(), userInfo.getPassword(), true, true, true, true, authorities);
RentalWebsUser userDetails = new RentalWebsUser(user, userInfo.getIdweb(), userInfo.getName(), userInfo.getSurname());
return userDetails;
} else throw new UsernameNotFoundException("Wrong user/password");
}
}
这是AuthenticationProvider实现:
@Component
public class RwAuthenticationProvider implements AuthenticationProvider {
RentalWebsUser userDetails;
@Autowired
UserDetailsService userService;
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
User user = null;
Authentication auth = null;
String username = authentication.getName();
String password = authentication.getCredentials().toString();
userDetails = (RentalWebsUser) userService.loadUserByUsername(username);
if(userDetails != null){
user = userDetails.getUser();
} else throw new UsernameNotFoundException("Wrong user/password");
if(password == null || !password.equals(user.getPassword())) throw new UsernameNotFoundException("Wrong user/password");
if(user != null){
auth = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getAuthorities());
} else throw new UsernameNotFoundException("Wrong user/password");
return auth;
}
public RentalWebsUser getUserDetails(){
return userDetails;
}
@Override
public boolean supports(Class<?> type) {
return true;
}
}
这是配置的摘录,但重要的是要注意,没有它,系统的行为方式相同:
public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SecurityConfig.class};
}
最后,我从PostgreSQL数据库中获取了一个实例RentalWebsUser
:
@Repository
public class UserRepositoryImpl implements UserRepository {
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public RentalWebsUser getUserByUsername(String username){
String SQL = "SELECT * from users WHERE username = :username";
SqlParameterSource param = new MapSqlParameterSource("username", username);
RentalWebsUser user = (RentalWebsUser) namedParameterJdbcTemplate.queryForObject(SQL, param, new UserMapper());
return user;
}
private static final class UserMapper implements RowMapper {
@Override
public Object mapRow(ResultSet rs, int i) throws SQLException {
RentalWebsUser user = new RentalWebsUser();
user.setIdweb(rs.getInt("idweb"));
user.setUsername(rs.getString("username"));
user.setPassword(rs.getString("password"));
user.setEnabled(rs.getBoolean("enabled"));
user.setName(rs.getString("name"));
user.setSurname(rs.getString("surname"));
user.setIdcountry(rs.getInt("idcountry"));
return user;
}
}
}
我花了好几个小时阅读Spring文档和教程,却找不到任何解决方案,所以任何帮助都会非常感激。提前谢谢。
答案 0 :(得分:1)
在这种情况下,不需要自定义AuthenticationProvider
- 实现。可用的Spring-Security组件应该足够了。我强烈建议坚持使用这些组件。
除此之外,目前的实施看起来已经破坏了#34; (例如,为什么UserDetails-Model实际上是一个服务?)。
让我们尝试解决问题...
(Mis-)使用AuthenticationProvider
(Singleton实例)作为身份验证持有者是上次成功登录的原因。这就是为什么单例实例基本上不应该保持状态。
要知道的重要事项是如何访问当前登录的用户:Spring-Security将Authentication
对象存储到SecurityContext
。它可以像这样访问(例如在你的Handler方法中):
import org.springframework.security.core.context.SecurityContextHolder;
...
@Controller
class SiteController {
@GetMapping("/admin")
String adminArea() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
MyUser principal = (MyUser) auth.getPrincipal();
// The object-type is the same that is returned by the `UserDetailsService#loadUserByName()` implementation
String username = user.getUsername();
...
}
}
或者,有一种方便的方法可以将当前登录的用户注入到这样的处理程序方法中(有关详细信息,请参阅@AuthenticationPrincipal):
@Controller
class SiteController {
@GetMapping("/admin")
String admin(@AuthenticationPrincipal MyUser user, Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
MyUser principal = (MyUser) auth.getPrincipal();
// `user` and `principal` is referencing the same object!
model.addAttribute("user", user);
model.addAttribute("principal", principal);
...
}
}
让我们看看获得一个有效例子所需的组件。
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.deleteCookies("JSESSIONID")
.and()
.rememberMe();
// @formatter:on
}
}
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserService implements UserDetailsService {
private MyUserRepository userRepository;
public UserService(MyUserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MyUser user = userRepository.findByUsername(username);
if (user != null) {
// if user already implements the `UserDetails`-interface, the object could be directly returned
// if not, wrap it into a pojo fulfilling the `UserDetails`-contract - eg.
// return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
return user;
}
throw new UsernameNotFoundException(username + " could not be found");
}
}
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
class LoginController {
@GetMapping("/login")
String loginForm() {
return "login";
}
}
resources / templates / login.html (thymeleaf)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="@{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
答案 1 :(得分:0)
@fateddy让我意识到我根本没有考虑范围,是解决问题的关键。
在我设置Spring Security的方式中,AuthenticationProvider
的实现似乎是登录过程的起点。设置会话范围已经解决了:
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RwAuthenticationProvider implements AuthenticationProvider {...
我在UserDetailsService
实现中有用户详细信息及其AuthenticationProvider
实现(RwAuthenticationProvider
),我从中检索它们以将它们设置为Controller Model,与这个班级:
@Service
public class UserDetailsModel {
Integer idproperty;
Integer idweb;
@Autowired
private RwAuthenticationProvider userDetails;
@Autowired
private PropertyRepository propertyRepository;
//Navigation bar - adminTmplt.htnl
public Model getUserDetailsModel(Model model){
model.addAttribute("userName", userDetails.getUserDetails().getName());
model.addAttribute("surname", userDetails.getUserDetails().getSurname());
model.addAttribute("email", userDetails.getUserDetails().getUser().getUsername());
model.addAttribute("property", userDetails.getUserDetails().getPropertyName());
model.addAttribute("idproperty", getIdProperty());
return model;
}
public Integer getIdProperty(){
idproperty = userDetails.getUserDetails().getIdproperty();
return idproperty;
}
public Integer getIdWeb(){
idweb = userDetails.getUserDetails().getIdweb();
return idweb;
}
public String getPropertyName(){
String propertyName = userDetails.getUserDetails().getPropertyName();
return propertyName;
}
public void setIdproperty(Integer idproperty){
userDetails.getUserDetails().setIdproperty(idproperty);
String propertyName = propertyRepository.getPropertyById(idproperty).getName();
userDetails.getUserDetails().setPropertyName(propertyName);
}
}
这是UserDetails
的自定义实现:
@Entity
@Table(name = "users")
public class RentalWebsUser implements UserDetails, Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "idweb")
private Integer idweb;
@Id
@Basic(optional = false)
@NotNull
@Column(name = "username")
private String username;
//Not persistent
private int idproperty;
private String propertyName;
private User user;
@NotNull
@Column(name = "password")
private String password;
@Column(name = "enabled")
private boolean enabled;
@NotNull
@Column(name = "name")
private String name;
@NotNull
@Column(name = "surname")
private String surname;
@NotNull
@Column(name = "idcountry")
private int idcountry;
//private List<GrantedAuthority> authorities;
public RentalWebsUser(){}
public RentalWebsUser(User user, int idweb, String name, String surname) {
this.idweb = idweb;
this.name = name;
this.surname = surname;
this.user = user;
}
...Getters and Setters
@Override
public boolean isAccountNonExpired() {
return this.user.isAccountNonExpired();
}
@Override
public boolean isAccountNonLocked() {
return this.user.isAccountNonLocked();
}
@Override
public boolean isCredentialsNonExpired() {
return this.user.isCredentialsNonExpired();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
throw new UnsupportedOperationException("Not supported yet.");
}
...hashCode, equals and toString methods omitted.
我担心我的设置不够准确,因此任何改进它们的建议都会受到欢迎。
非常感谢,再次感谢你们。
答案 2 :(得分:-1)
我认为当您使用其他用户登录而不首先注销时会出现问题。
如果是这种情况,则会发生这种情况,因为在成功登录后不会续订实际会话。这应该由一个eventListener完成,如果它已正确设置,则由AuthenticationManager调用。
我认为我们在这里有2个AuthenticationManagers - 一个由WebSecurityConfigurerAdapter
正确设置,另一个由构建器创建的内容传递给configureGlobalSecurity
方法并且实际上稍后使用。
很久以前我也遇到过类似的问题。
尝试以下方法:
将以下行添加到configuration
方法中:
http.authenticationProvider(authenticationProvider);
注释掉configureGlobalSecurity
方法
哦,还有一件事:尝试扩展DaoAuthenticationProvider,而不是创建自己的。你以后会感谢它......