spring - Spring Security 中的身份验证问题(仅检查用户名而不检查密码?)

标签 spring spring-boot authentication spring-security

这是我使用 Spring 的第一个项目,我刚刚开始使用 Spring Security 创建登录。我希望某些页面仅供管理员访问,而不供玩家访问。我在网上找到了一些例子,这种机制运行良好,我有这个受登录保护的安全页面,当用户没有 ROLE_ADMIN 时,它是被禁止的。

@PreAuthorize("hasAuthority('ROLE_ADMIN')")
    @GetMapping("/secured/all")
    public String securedHello() {
        return "Secured Hello";
    } 

问题是测试我的代码时我发现 Spring 只检查用户名来验证管理员(以及用户)。如果我输入错误的密码,它仍然允许我输入。我不明白这是怎么可能的,Spring Security 不应该自己完成所有身份验证工作吗?我看到有人建议实现身份验证管理器或类似的东西,但我不明白为什么以及如何将它插入我的代码中。两天以来我一直坚持这一点,请提供任何建议,我们将不胜感激。 这些是我的类(class):

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;
    }


}

最佳答案

Shouldn't Spring Security do all the authentication work by itself?

是的,Spring Security 使用 AuthenticationManager 为您做到这一点。

I've seen somebody suggested to implement an authentication manager or something like that, but I don't understand why and how to insert it in my code.

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

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

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

  1. 获取用户信息的部分(CustomUserDetailsS​​ervice)
  2. 检查密码的另一部分(getPasswordEncoder())。

在屏幕背后发生的事情是 Spring 调用您的 CustomUserDetailsS​​ervice 来获取您的用户信息,包括您的(散列)密码。获取该信息后,它会调用您的 PasswordEncoder.matches() 函数来验证输入的原始密码是否与 CustomUserDetailsS​​ervice 提供的散列密码匹配。

在您的例子中,您的 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 已经附带了一些实现,包括 BcryptPasswordEncoderStandardPasswordEncoderMessageDigestPasswordEncoder 等。其中一些实现已被弃用,主要是为了表明这些编码器使用的哈希机制被认为是不安全的。如 Javadoc 所述,在撰写本文时没有删除这些编码器的计划:

Digest based password encoding is not considered secure. Instead use an adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or SCryptPasswordEncoder. Even better use DelegatingPasswordEncoder which supports password upgrades. There are no plans to remove this support. It is deprecated to indicate that this is a legacy implementation and using it is considered insecure.

(重点是我自己)

如果您可以自由选择您选择的实现,那么 Spring 建议使用 BCryptPasswordEncoder,如 Javadoc 中所述:

Service interface for encoding passwords. The preferred implementation is BCryptPasswordEncoder.

关于spring - Spring Security 中的身份验证问题(仅检查用户名而不检查密码?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51497317/

相关文章:

java - 无法使用 @ContextConfiguration 在测试类中注入(inject)属性

java - 使用 ExampleMatcher 和 Pageable 检索数据的 JPQL 自定义查询

javascript - 无法使用 Angular JS 和 spring MVC 打开下载文件

authentication - TLS Channel ID 如何保证客户端的真实性?

facebook - OAuth 正在被破坏,但只有当用户来自 HTTP 时,HTTPS 才能正常工作

java - 创建名称为 'mongoTemplate' 的 bean 时出错

使用 WSDL 生成的类的单元测试中出现 java.lang.ExceptionInInitializerError

java - Spring Web服务删除响应字符串中的多个空格

每个月的第一个星期一的 Spring @scheduled cron 表达式

mysql - 仅获取数据库的个人记录而不是所有记录