为啥 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?