为啥 Spring 安全身份验证返回 Null 身份验证对象?

Posted

技术标签:

【中文标题】为啥 Spring 安全身份验证返回 Null 身份验证对象?【英文标题】:Why is Spring security Authentication returning a Null authentication object?为什么 Spring 安全身份验证返回 Null 身份验证对象? 【发布时间】:2021-12-27 20:47:16 【问题描述】:

我需要获取当前登录用户的用户对象详细信息,以便我可以将客户分配给用户,但是当我打印到控制台时,我不断收到一个空对象。我可以使用为用户类型生成的令牌成功登录,但是当通过 Spring 身份验证查询登录的用户名时,我不断收到空响应。

控制器

@RestController
public class CustomerController 
    
@Autowired
CustomerAccountService customerRepo;
        
@Autowired
UserAccountService  userRepo;

 @GetMapping(value="marketers/customers")
public List<Customer> getLlistByMarketerName()

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();  
User loggedInUser = userRepo.findByUserName(authentication.getName());
System.out.println("logged in user:"+ loggedInUser);
return customerRepo.findByMarketer(loggedInUser);

    

存储库


public interface CustomerAccountRepo extends JpaRepository <Customer, Long  > 


@Query("select customer from Customer customer join customer.marketer marketer where marketer = :marketer")
     List<Customer> findByMarketer(User marketer);

用户详情服务

@Service
public class UserAccountService implements UserDetailsService 
    
    
@Autowired  
private UserAccountRepository userRepository;
        
private PasswordEncoder bCryptPasswordEncoder;
       
public  UserAccountService (PasswordEncoder bCryptPasswordEncoder) 
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
         
       
    
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
User user = userRepository.findByUserName(username);
if(user == null) 
throw new UsernameNotFoundException("User not found");

            
           // List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority(user.getUserRole()));
            
          return MyUserDetails.build(user);


JWT 请求过滤器

@Component
public class JwtRequestFilter extends OncePerRequestFilter 

    @Autowired
private JwtTokenUtil jwtTokenUtil;
private final UserAccountService userAccountService;
    
@Autowired
public JwtRequestFilter( @Lazy final UserAccountService userAccountService) 
this.userAccountService = userAccountService;
        

    
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException 

final String requestTokenHeader = request.getHeader("Authorization");

String username = null;
String jwtToken = null;
if (requestTokenHeader != null) 
jwtToken = requestTokenHeader.substring(7);
try 
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
 catch (IllegalArgumentException e) 
System.out.println("Unable to get JWT Token");
 catch (ExpiredJwtException e) 
System.out.println("JWT Token has expired");

 

if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) 
UserDetails userDetails = this.userAccountService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) 
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority)
.collect(Collectors.joining());
System.out.println("Authorities granted : " + authorities);
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            
else 
 System.out.println("Not Valid Token");
        

        
chain.doFilter(request, response);


用户实体

@Entity
public class User 

    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    
    private String firstName ;
    private String lastName;
    
    @Column(name="user_name", unique=true)
    private String userName;
    
    private String password;
    private String Gender; 
    private String phoneNumber;
    private String email;
    
    @JsonIgnoreProperties("hibernateLazyInitializer", "handler")
    @ManyToOne(targetEntity = Branch.class, 
     fetch = FetchType.LAZY )
    @JoinColumn(name="branch_id") 
    private Branch branch;
    
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date createdDate;
    
    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(
    name = "users_roles", 
    joinColumns = @JoinColumn(name = "user_id"),
    inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<UserRole> userRole = new HashSet<>();
    
    @Enumerated(EnumType.STRING)
    private UserStatus status;
    
    @JsonBackReference
    @OneToMany(mappedBy="marketer",cascade = CascadeType.ALL, targetEntity=Customer.class)
    private List <Customer> customer;

这是 MyUserDetails 类,提供用户名和密码验证详细信息

public class MyUserDetails implements UserDetails 
    
private static final long serialVersionUID = -2456373662763511974L;

private Long id;
private String username;
private String password;
private String email;
private Collection<? extends GrantedAuthority> authorities;
    
public MyUserDetails() 
    
public MyUserDetails(Long id, String username, String email, String password,
Collection<? extends GrantedAuthority> authorities)
    
        this.id = id;
        this.username = username;
        this.email = email;
        this.password = password;
        this.authorities = authorities;
    
public static MyUserDetails build(User user) 
List<GrantedAuthority> authorities = user.getUserRole()
.stream().map(role -> new SimpleGrantedAuthority
(role.getName()))
.collect(Collectors.toList());
return new MyUserDetails(user.getId(),
user.getUserName(),
user.getEmail(),
user.getPassword(),
authorities);
        


更新: 安全配置文件。这是我在 Java 中的第一个大项目,请原谅我的错误

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 
    
    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
  private UserDetailsService myUserDetailsService;
    
    public void addResourceHandlers(ResourceHandlerRegistry registry) 
      exposeDirectory("customer-photos", registry);
  
    
    private void exposeDirectory(String dirName, ResourceHandlerRegistry registry) 
      Path uploadDir = Paths.get(dirName);
      String uploadPath = uploadDir.toFile().getAbsolutePath();
       
      if (dirName.startsWith("../")) dirName = dirName.replace("../", "");
       
      registry.addResourceHandler("/" + dirName + "/**").addResourceLocations("file:/"+ uploadPath + "/");
  

  @Autowired 
    private JwtRequestFilter jwtRequestFilter;
     
    @Bean
    public BCryptPasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    
    
     @Bean
     public DaoAuthenticationProvider daoAuthenticationProvider(PasswordEncoder passwordEncoder, UserDetailsService userDetailsService)  
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        return daoAuthenticationProvider;
    
     
     @Override
     @Bean
     public AuthenticationManager authenticationManagerBean() throws Exception 
         return super.authenticationManagerBean();
     

   @Override
     protected void configure(AuthenticationManagerBuilder auth) throws Exception 
       auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
     

  @Override
    protected void configure(HttpSecurity http) throws Exception 
      http.cors().and().csrf().disable()
      .authorizeRequests()
      .antMatchers("/auth/login", "/validate", "/**").permitAll()
      .antMatchers("/admin/**").hasAuthority("ADMIN")
      
      .anyRequest().authenticated()
      .and()
      .exceptionHandling()
      .authenticationEntryPoint(jwtAuthenticationEntryPoint)
      
      .and()
      .formLogin().permitAll()
      
      .and()
      .sessionManagement()
      .maximumSessions(1)
      .and()
      .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      .and()
      .logout().logoutUrl("/logout").logoutSuccessUrl("/auth/login")
      .deleteCookies("JSESSIONID");
      http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    
  

【问题讨论】:

@xerx593 我刚刚添加了安全配置 难道antMatchers("/auth/login", "/validate", "/**").permitAll() 不需要验证即可调用@GetMapping(value="marketers/customers") @JoãoDias 我认为“/**”是指所有其他端点? 没错,这意味着基本上所有端点都不需要身份验证,因此在此类调用中您将有一个空的SecurityContext @JoãoDias 我该怎么办?我删除了“/**”并更改为“/marketers/customers”,但得到相同的 null 【参考方案1】:

在我看来,您不希望所有端点都不需要身份验证。为了实现它,您需要在安全配置中从permitAll() 中删除/**,如下所示:

@Override
protected void configure(HttpSecurity http) throws Exception 
  http.cors().and().csrf().disable()
  .authorizeRequests()
  .antMatchers("/auth/login", "/validate").permitAll()
  .antMatchers("/admin/**").hasAuthority("ADMIN")
    
  .anyRequest().authenticated()
  .and()
    .exceptionHandling()
  .authenticationEntryPoint(jwtAuthenticationEntryPoint)
  
  .and()
  .formLogin().permitAll()
  
  .and()
  .sessionManagement()
  .maximumSessions(1)
  .and()
  .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
  .and()
  .logout().logoutUrl("/logout").logoutSuccessUrl("/auth/login")
  .deleteCookies("JSESSIONID");
  http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

【讨论】:

我删除它并得到错误:“未经授权” 你去。现在您需要进行身份验证才能调用这样的端点,这样您就可以拥有一个非空的SecurityContext .antMatcher("marketers/customers").authorizeRequests() ?? 没有。我再说一遍。在 Postman 中调用端点或以任何方式调用端点时,您必须提供身份验证方法。从似乎是 JWT 令牌的代码中,我猜你在调用 /login 端点后得到。 我用邮递员登录并获得了一个生成的令牌,我将令牌添加到标题中并致电营销人员/客户得到并得到 NullPointerException:null。我注意到,当我尝试使用变量 userName 登录时,它会说凭据错误,直到我更改为用户名,但我在 User 实体上拥有的是 userName。请问有什么问题?

以上是关于为啥 Spring 安全身份验证返回 Null 身份验证对象?的主要内容,如果未能解决你的问题,请参考以下文章

Grails:Spring Security Core 自定义身份验证 getUserByUserName 返回 null 对象

Spring Security - 获取登录页面时,安全性尝试进行身份验证并返回 401 错误

为啥使用 JWT 时 UserManager.GetUserAsync 返回 null?

为啥 Firebase pendingAuthResult 返回 null?

具有 OAuth 2 和 JWT 安全性的 Spring Boot 微服务

从安全上下文中获取当前经过身份验证的用户作为 Spring Cache 的密钥