这是我在Spring的第一个项目,而我刚开始使用Spring Security创建登录名。我希望某些页面只能供管理员访问,而不能供玩家访问。我在网络上找到了一些示例,该机制运行良好,我拥有受登录保护的安全页面,并且当用户没有ROLE_ADMIN时将其禁止。
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping("/secured/all")
public String securedHello() {
return "Secured Hello";
}
问题是测试我的代码后,我发现Spring仅对用户名进行验证,从而对管理员(以及用户)进行身份验证。如果我输入了错误的密码,它仍然允许我输入。我不明白这是怎么可能的,Spring Security难道不是所有的身份验证都自己完成吗?我见过有人建议实施身份验证管理器或类似的方法,但是我不明白为什么以及如何将其插入我的代码中。自两天以来,我一直在坚持这一观点,对您的任何建议都非常感谢。 这些是我的课程:
package model;
import java.io.IOException;
import javax.naming.AuthenticationException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.fasterxml.jackson.databind.ObjectMapper;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@EnableJpaRepositories(basePackageClasses = PlayersRepository.class)
@ComponentScan(basePackageClasses= CustomUserDetailsService.class)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(getPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//http.csrf().disable();
http.authorizeRequests()
.antMatchers("**/secured/**").access("hasAuthority('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().permitAll();
}
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
}
package model;
import java.util.ArrayList;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
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 CustomUserDetailsService implements UserDetailsService {
@Autowired
private PlayersRepository usersRepository;
@Autowired
private RoleRepository rolesRepository;
public CustomUserDetailsService(PlayersRepository usersRepository, RoleRepository rolesRepository) {
this.usersRepository=usersRepository;
this.rolesRepository=rolesRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Player> optionalUser = usersRepository.findByUsername(username);
optionalUser
.orElseThrow(() -> new UsernameNotFoundException("Username not found"));
Player user= optionalUser.get();
System.out.println(user);
return toUserDetails(new UserObject(user.getUsername(),user.getPassword(),user.getRole()));
}
private UserDetails toUserDetails(UserObject userObject) {
return User.withUsername(userObject.name)
.password(userObject.password)
.roles(userObject.role).build();
}
private static class UserObject {
private String name;
private String password;
private String role;
public UserObject(String name, String password, String role) {
this.name = name;
this.password = password;
this.role = role;
}
}
}
package model;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class CustomUserDetails extends Player implements UserDetails {
String role;
public CustomUserDetails(final Player user) {
super(user);
}
public CustomUserDetails(Optional<Player> user, String role) {
super(user);
this.role=role;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
list.add(new SimpleGrantedAuthority("ROLE_"+ role));
System.out.println(list);
return list;
}
@Override
public String getPassword() {
return super.getPassword();
}
@Override
public String getUsername() {
return super.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
答案 0 :(得分:1)
我刚刚进行了快速扫描,发现了这个问题
private PasswordEncoder getPasswordEncoder() {
return new PasswordEncoder() {
@Override
public String encode(CharSequence charSequence) {
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
在您的matches
中,您始终返回true。
我想您应该在此处输入用于检查密码是否相等的逻辑,例如
@Override
public boolean matches(CharSequence charSequence, String s) {
return charSequence.toString.equals(s);
}
我建议您使用类似
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
答案 1 :(得分:1)
Spring Security不能独自完成所有身份验证吗?
是的,Spring Security使用AuthenticationManager
为您做到了。
我已经看到有人建议实施身份验证管理器或类似的东西,但是我不明白为什么以及如何将其插入我的代码中。
您实际上已经有了一个AuthenticationManager
,因为您是在configure()
方法中构建的:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
所以,您可能会问,这不起作用的确切原因是什么。好吧,您提供的AuthenticationManager
包含两个部分:
CustomUserDetailsService
)的部分getPasswordEncoder()
)。在屏幕后面发生的事情是,Spring调用您的CustomUserDetailsService
来获取您的用户信息,包括您的(哈希)密码。提取信息后,它将调用您的PasswordEncoder.matches()
函数,以验证原始输入的密码是否与CustomUserDetailsService
提供的哈希密码相符。
在您的情况下,您的PasswordEncoder.matches()
函数如下所示:
@Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
这意味着无论您提供什么密码,它都将返回true
。这正是您遇到的事情,因为任何密码都可以使用。
那么,您如何解决这个问题?好吧,您的PasswordEncoder
应该实际上对您的原始密码进行哈希处理并将其与正在传递的哈希密码进行比较,例如:
@Override
public boolean matches(CharSequence rawPassword, String hashedPassword) {
String hashedPassword2 = null; // hash your rawPassword here
return hashedPassword2.equals(hashedPassword);
}
此方法的实现取决于您如何在数据库中存储密码。 Spring Security已经提供了一些实现,包括BcryptPasswordEncoder
,StandardPasswordEncoder
,MessageDigestPasswordEncoder
,...。不建议使用其中的某些实现,主要是为了表明那些编码器使用的哈希机制被认为是不安全的。如Javadoc所述,目前尚无删除这些编码器的计划:
基于摘要的密码编码不安全。而是使用自适应单向函数,例如
BCryptPasswordEncoder
,Pbkdf2PasswordEncoder
或SCryptPasswordEncoder
。最好使用支持密码升级的DelegatingPasswordEncoder
。 没有计划删除此支持。不推荐使用,表明这是旧版实现,使用它被认为是不安全的。
(强调是我自己的)
如果您可以自由选择选择哪种实现,那么Spring建议使用Javadoc中提到的BCryptPasswordEncoder
:
用于编码密码的服务接口。首选的实现方式是
BCryptPasswordEncoder
。