为什么 Spring 安全身份验证返回 Null 身份验证对象?
Why is Spring security Authentication returning a Null authentication object?
我需要获取当前登录用户的用户对象详细信息,以便我可以将客户分配给该用户,但我在打印时一直收到空对象到控制台。我可以使用为用户类型生成的令牌成功登录,但是当通过 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 class,它提供用户名和密码验证详细信息
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);
}
}
在我看来,您不希望所有端点都不需要身份验证。为了实现它,您需要从安全配置中的 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);
}
我需要获取当前登录用户的用户对象详细信息,以便我可以将客户分配给该用户,但我在打印时一直收到空对象到控制台。我可以使用为用户类型生成的令牌成功登录,但是当通过 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 class,它提供用户名和密码验证详细信息
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);
}
}
在我看来,您不希望所有端点都不需要身份验证。为了实现它,您需要从安全配置中的 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);
}