chl*_*hln 4 authentication spring spring-security spring-boot
这是我使用 Spring 的第一个项目,我刚刚开始使用 Spring Security 创建登录。我希望某些页面只能由管理员访问,而不能由玩家访问。我在网上找到了一些例子,这个机制运行得很好,我有这个受登录保护的安全页面,当用户没有 ROLE_ADMIN 时,它是被禁止的。
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping("/secured/all")
public String securedHello() {
return "Secured Hello";
}
Run Code Online (Sandbox Code Playgroud)
问题是,在测试我的代码时,我发现 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;
}
}
Run Code Online (Sandbox Code Playgroud)
Spring Security 不应该自己完成所有身份验证工作吗?
是的,Spring Security 使用AuthenticationManager.
我看到有人建议实现身份验证管理器或类似的东西,但我不明白为什么以及如何将其插入我的代码中。
实际上你已经有了一个AuthenticationManager, ,因为你在configure()方法中构建了一个 :
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}
Run Code Online (Sandbox Code Playgroud)
那么,您可能会问这不起作用的确切原因是什么。嗯,AuthenticationManager您提供的包含两部分:
CustomUserDetailsService)getPasswordEncoder())。屏幕后面发生的事情是 Spring 调用您CustomUserDetailsService来获取您的用户信息,包括您的(散列的)密码。获取该信息后,它会调用您的PasswordEncoder.matches()函数来验证输入的原始密码是否与CustomUserDetailsService.
在您的情况下,您的PasswordEncoder.matches()函数如下所示:
@Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
Run Code Online (Sandbox Code Playgroud)
这意味着无论您提供什么密码,它都会返回true. 这正是您所经历的,因为任何密码都可以使用。
那么,你如何解决这个问题呢?好吧,您PasswordEncoder实际上应该对原始密码进行哈希处理,并将其与正在传递的哈希密码进行比较,例如:
@Override
public boolean matches(CharSequence rawPassword, String hashedPassword) {
String hashedPassword2 = null; // hash your rawPassword here
return hashedPassword2.equals(hashedPassword);
}
Run Code Online (Sandbox Code Playgroud)
此方法的实现取决于您在数据库中存储密码的方式。Spring Security 已经附带了一些实现,包括BcryptPasswordEncoder、、、...。其中一些实现已被弃用,主要是为了表明这些编码器使用的哈希机制被认为是不安全的。正如 Javadoc 中提到的,目前还没有计划删除这些编码器:StandardPasswordEncoderMessageDigestPasswordEncoder
基于摘要的密码编码不被认为是安全的。相反,请使用自适应单向函数,例如
BCryptPasswordEncoder、Pbkdf2PasswordEncoder、 或SCryptPasswordEncoder。更好用的DelegatingPasswordEncoder是支持密码升级。目前还没有计划取消这种支持。不推荐使用它来表明这是一个遗留实现,并且使用它被认为是不安全的。
(重点是我自己的)
如果您可以自由选择您选择的实现,那么 Spring 建议使用JavadocBCryptPasswordEncoder中提到的:
用于编码密码的服务接口。首选实现是
BCryptPasswordEncoder.
| 归档时间: |
|
| 查看次数: |
7572 次 |
| 最近记录: |