首页 > 解决方案 > Spring Security 中的身份验证问题(只检查用户名而不是密码?)

问题描述

这是我的第一个 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;
    }


}

标签: springspring-bootauthenticationspring-security

解决方案


Spring Security 不应该自己完成所有的身份验证工作吗?

是的,Spring Security 使用AuthenticationManager.

我看到有人建议实现身份验证管理器或类似的东西,但我不明白为什么以及如何将它插入我的代码中。

您实际上已经有一个AuthenticationManager, 因为您在configure()方法中构建了一个:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
}

那么,您可能会问,这不起作用的确切原因是什么。好吧,AuthenticationManager您提供的内容包含两部分:

  1. 获取用户信息的部分 ( CustomUserDetailsService)
  2. 检查密码的另一部分(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 所述,在撰写本文时没有删除这些编码器的计划:

基于摘要的密码编码不被认为是安全的。而是使用自适应单向函数,如BCryptPasswordEncoderPbkdf2PasswordEncoderSCryptPasswordEncoder。更好的使用DelegatingPasswordEncoder它支持密码升级。没有取消此支持的计划。不推荐使用它来表明这是一个遗留实现并且使用它被认为是不安全的。

(重点是我自己的)

如果您可以自由选择您选择的实现,那么 Spring 建议使用JavadocBCryptPasswordEncoder中提到的:

用于编码密码的服务接口。首选实现是BCryptPasswordEncoder.


推荐阅读