Spring Boot 未调用我的CustomAuthenticationProvider

hjqgdpho  于 2023-02-22  发布在  Spring
关注(0)|答案(1)|浏览(234)

Should spring security filters call authentication providers directly?
我正在尝试 * 模式2*,从上面的帖子,基本上我的自定义过滤器拦截一个请求,采取所有的凭据,并把它放在与authenticated=false的SecurityContext。然后我的 CustomAuthenticationProvider 应该拿起这些凭据,并验证它。在我的项目中,我的过滤器拦截请求,并做它的工作,但我的auth提供程序没有得到调用。

  • 用户名密码验证过滤器.java*
package com.springsecurity.learning.config;

import java.io.IOException;

import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.springsecurity.learning.dto.CredentialsDto;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class UsernamePasswordAuthFilter extends OncePerRequestFilter {
    
    private final String END_POINT = "/api/login";
    private static final ObjectMapper MAPPER = new ObjectMapper();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        if(END_POINT.equals(request.getRequestURI()) 
                && HttpMethod.POST.matches(request.getMethod())) {
            CredentialsDto credentialsDto = MAPPER.readValue(request.getInputStream(), CredentialsDto.class);
            
            SecurityContextHolder.getContext().setAuthentication(
                    new UsernamePasswordAuthenticationToken(credentialsDto.getUsername(), 
                            credentialsDto.getPassword())
            );
        }
        
        
        
        filterChain.doFilter(request, response);
    }

}
  • 自定义身份验证提供程序.java*
package com.springsecurity.learning.config;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import com.springsecurity.learning.dto.CredentialsDto;
import com.springsecurity.learning.dto.UserDto;
import com.springsecurity.learning.services.AuthenticationService;

import lombok.AllArgsConstructor;

@Component
@AllArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final AuthenticationService authenticationService;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // TODO Auto-generated method stub
        
        UserDto userDto = null;
        if(authentication instanceof UsernamePasswordAuthenticationToken) {
            userDto = authenticationService.authenticate(
                    new CredentialsDto((String)authentication.getPrincipal(),
                            (String)authentication.getCredentials()));
        }
        
        if(userDto==null)return null;
        
        return new UsernamePasswordAuthenticationToken(userDto.getUsername(), 
                null,
                List.of(new SimpleGrantedAuthority(userDto.getRole())
        )); 
    }

    @Override
    public boolean supports(Class<?> authentication) {
        // TODO Auto-generated method stub(UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
        return true;
    }

}
  • 安全配置.java*
package com.springsecurity.learning.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public AuthenticationManager authenticationManager(CustomAuthenticationProvider customAuthenticationProvider) {
        return new ProviderManager(customAuthenticationProvider);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity, CustomAuthenticationProvider customAuthenticationProvider) throws Exception {
        httpSecurity
            .authenticationProvider(customAuthenticationProvider)
            .addFilterAfter(new UsernamePasswordAuthFilter(), LogoutFilter.class)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .csrf().disable()
            .authorizeHttpRequests()
            .requestMatchers("/home/public")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic().disable();
        
        return httpSecurity.build();
    }
}

这是我的安全过滤链

ar7v8xwq

ar7v8xwq1#

您的过滤器是罪魁祸首,因为您没有调用AuthenticationManager上的authenticate方法(在您的情况下,它将调用提供程序)。
如果你查看一些默认的Spring Security过滤器,比如UsernamePasswordAuthenticationFilterBasicAuthenticationFilter,你会发现它们确实调用了那个方法,并在SecurityContext中设置了结果Authentication,而你只是在不尝试任何身份验证的情况下设置它。
您的过滤器需要调用它,并具有更多的功能,我还建议扩展Spring Security提供的AbstractAuthenticationProcessingFilter并实现attemptAuthenticationMethod

public class UsernamePasswordAuthFilter extends AbstractAuthenticationProcessingFilter {
    
  private final String END_POINT = "/api/login";
  private static final ObjectMapper MAPPER = new ObjectMapper();

  UsernamePasswordAuthFilter() {
    super(END_POINT); 
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException;

    CredentialsDto credentialsDto = MAPPER.readValue(request.getInputStream(), CredentialsDto.class);
    String username = credentialsDto.getUsername();
    String password = credentialsDto.getPassword();
    
    UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                password);
    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);
    return this.getAuthenticationManager().authenticate(authRequest);
  }
}

这将读取您的对象、调用身份验证并与Spring Security的其他部分集成(如触发事件、会话管理等)。
通过让CredentialsDto实现Authentication接口,可以使它更简洁一些。然后CustomAuthenticationProvider可以检查它是否支持它,并只强制转换它。这样就节省了创建中间对象的时间。

public class UsernamePasswordAuthFilter extends AbstractAuthenticationProcessingFilter {
    
  private final String END_POINT = "/api/login";
  private static final ObjectMapper MAPPER = new ObjectMapper();

  UsernamePasswordAuthFilter() {
    super(END_POINT); 
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException;

    CredentialsDto credentialsDto = MAPPER.readValue(request.getInputStream(), CredentialsDto.class);   
    // Allow subclasses to set the "details" property
    setDetails(request, credentialsDto);
    return this.getAuthenticationManager().authenticate(credentialsDto);
  }
}

CustomAuthenticationProvider现在非常简单。

@Component
@AllArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final AuthenticationService authenticationService;
    
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {       
        UserDto userDto = null;
        if(authentication instanceof CredentialsDto) {
          userDto = authenticationService.authenticate((CredentialsDto) authentication);
                
        }
        
        if(userDto==null)return null;       
        return new UsernamePasswordAuthenticationToken(userDto.getUsername(), 
                null,
                List.of(new SimpleGrantedAuthority(userDto.getRole())
        )); 
    }

    @Override
    public boolean supports(Class<?> authentication) {    
        return CredentialsDto.class.isAssignableFrom(authentication);
    }
}

类似的东西。
对于配置,您的UsernamePasswordAuthFilter现在需要是一个正确的bean才能注入AuthenticationManager,如果您保持与现在相同的策略,您将遇到NullPointerException,因为没有注入依赖项。

相关问题