春季安全 |为啥当它们有效时我会得到错误的凭据?

Posted

技术标签:

【中文标题】春季安全 |为啥当它们有效时我会得到错误的凭据?【英文标题】:Spring security | Why do I get bad credentials when they are valid?春季安全 |为什么当它们有效时我会得到错误的凭据? 【发布时间】:2021-06-20 10:29:47 【问题描述】:

所以我正在开发一个 Spring Boot 项目,而安全性是我希望在这个项目中拥有的东西之一。 我遇到了这个问题,我的代码总是出现 BadCredentialsException,但我认为我的凭据是正确的。

我的身份验证控制器:

@RestController
@CrossOrigin
public class JwtAuthenticationController 

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;


    @Autowired
    private JwtUserDetailsService userDetailsService;

    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception 

        authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());


        final UserDetails userDetails = userDetailsService
                .loadUserByUsername(authenticationRequest.getUsername());

        final String token = jwtTokenUtil.generateToken(userDetails);


        return ResponseEntity.ok(new JwtResponse(token));
    

    private void authenticate(String username, String password) throws Exception 
        try 
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
         catch (DisabledException e) 
            throw new Exception("USER_DISABLED", e);
         catch (BadCredentialsException e) 
            throw new Exception("INVALID_CREDENTIALS", e);
        
    

    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public ResponseEntity<?> saveUser(@RequestBody UserDTO user) throws Exception 
        return ResponseEntity.ok(userDetailsService.save(user));
    

我的 UserDetailsS​​ervice:

@Service
public class JwtUserDetailsService implements UserDetailsService 
    @Autowired
    private UserDao userDao;
    @Autowired
    private PasswordEncoder bcryptEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        DAOUser user = userDao.findByUsername(username);
        if (user == null)
            throw new UsernameNotFoundException("User not found with username: " + username);
        
        return new User(user.getUsername(), user.getPassword(), new ArrayList<>());

    
    public DAOUser save(UserDTO user) 
        DAOUser newUser = new DAOUser();
        newUser.setUsername(user.getUsername());
        newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));
        return userDao.save(newUser);
    

我的 WebSecurityConfig:

@Configuration
@AllArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter //provides security for endpoints

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private UserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    private final AccountService accountService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        // configure AuthenticationManager so that it knows from where to load
        // user for matching credentials
        // Use BCryptPasswordEncoder
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
    

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .csrf().disable()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
                .authorizeRequests()
                .antMatchers("/authenticate","/register")
                .permitAll()//any request that goes trough that end point we want to allow.
                .anyRequest()
                .authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

    

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.authenticationProvider(daoAuthenticationProvider());
    

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() 
        DaoAuthenticationProvider provider =
                new DaoAuthenticationProvider();
        provider.setPasswordEncoder(bCryptPasswordEncoder);
        provider.setUserDetailsService(accountService);
        return provider;
    

我的请求过滤器:

@Component
public class JwtRequestFilter extends OncePerRequestFilter 

    @Autowired
    private JwtUserDetailsService jwtUserDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException 
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith(""))
            jwt = authorizationHeader.substring(7);
            username = jwtTokenUtil.extractUsername(jwt);
        

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
            UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
            if (jwtTokenUtil.validateToken(jwt, userDetails))
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            
        
        chain.doFilter(request, response);
    



我的密码编码器:

@Configuration
public class PasswordEncoder

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
        return new BCryptPasswordEncoder();
    





我的 UserDao

@Entity
@Table(name = "myusers")
public class DAOUser 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Column
    private String username;
    @Column
    @JsonIgnore
    private String password;

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getPassword() 
        return password;
    

    public void setPassword(String password) 
        this.password = password;
    


我的 UserDao 界面

我知道这个名字很混乱,但我只是按照教程,他给了它这个名字,我没有改变它,因为我希望它在我重命名文件之前工作。

@Repository
public interface UserDao extends CrudRepository<DAOUser, Integer> 
    DAOUser findByUsername(String username);

我认为这些是您需要帮助我的所有文件。如果您需要更多,请询问,我会上传它们。 谁能帮我解决这个问题? 谢谢!!

【问题讨论】:

我在这里看到的一个问题是在 WebSecurityConfig 中。 DaoAuthenticationProvider 作为 @Bean 提供,因此 Spring 将为您初始化它。但是在 configure(AuthenticationManagerBuilder auth) 中,您通过调用 daoAuthenticationProvider() 创建另一个新实例,该实例不是 Spring Managed bean,而是一个单独的实例。 【参考方案1】:

如果你阅读了spring documentation on passwords(在你在这里提问之前你应该这样做)

你会看到存储密码时的格式是:

bcrypt$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG

当您存储它时,您只是存储它而不是在存储之前将bcrypt 前缀添加到生成的字符串中。

newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));

因此,当您稍后提供密码时,您会得到错误的密码,因为 spring 不知道在解码密码时使用什么编码器。

【讨论】:

以上是关于春季安全 |为啥当它们有效时我会得到错误的凭据?的主要内容,如果未能解决你的问题,请参考以下文章

C ++(Windows)向量为啥会出现内存错误?

春季安全http 404错误

春季安全过滤器问题

当 URL 中提供凭据时,为啥浏览器不发送 Authentication 标头?

Spring安全类工作在不同的端口尝试使用来自apk的有效凭据登录,得到“**anonymousUser**”

当文件中明确指定 executionRoleArn 时,为啥我会收到关于未指定的错误?