在 Spring Security 中检查令牌的 NullPointerException

Posted

技术标签:

【中文标题】在 Spring Security 中检查令牌的 NullPointerException【英文标题】:NullPointerException for checking token in Spring Security 【发布时间】:2020-07-05 16:39:10 【问题描述】:

我正在使用 jwt 令牌进行授权,当我使用令牌发送请求时,我得到空指针异常和 500 错误。如何解决这个问题?我可以获得授权令牌,但不能使用它。

错误:

java.lang.NullPointerException: null
    at com.ilya.testapp.security.JwtTokenProvider.getAuthentication(JwtTokenProvider.java:64) ~[classes/:na]
    at com.ilya.testapp.security.JwtTokenFilter.doFilter(JwtTokenFilter.java:29) ~[classes/:na]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.2.2.RELEASE.jar:5.2.2.RELEASE]

    ...

登录控制器:

@RestController
@RequestMapping(value = "/api/v1/auth/")
public class AuthenticationRestControllerV1 
    private final AuthenticationManager authenticationManager;

    private final JwtTokenProvider jwtTokenProvider;

    private final UserRepository userRepository;

    public AuthenticationRestControllerV1(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider, UserRepository userRepository) 
        this.authenticationManager = authenticationManager;
        this.jwtTokenProvider = jwtTokenProvider;
        this.userRepository = userRepository;
    

    @PostMapping("login")
    public ResponseEntity login(@RequestBody AuthenticationRequestDto requestDto) 
        try 
            String username = requestDto.getUsername();
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, requestDto.getPassword()));
            User user = userRepository.findByUsername(username);

            if (user == null) 
                throw new UsernameNotFoundException("User with username: " + username + " not found");
            

            String token = jwtTokenProvider.createToken(username, user.getRoleList());

            Map<Object, Object> response = new HashMap<>();
            response.put("username", username);
            response.put("token", token);

            return ResponseEntity.ok(response);
         catch (AuthenticationException e) 
            throw new BadCredentialsException("Invalid username or password");
        
    

安全配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    private final JwtTokenProvider jwtTokenProvider;

    public SecurityConfig(JwtTokenProvider jwtTokenProvider) 
        this.jwtTokenProvider = jwtTokenProvider;
    

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



    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                    .cors()
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/api/v1/auth/login").permitAll()
                .antMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .apply(new JwtConfigurer(jwtTokenProvider));
    




    @Bean
    public CorsConfigurationSource corsConfigurationSource() 
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(List.of("*"));
        configuration.setAllowedMethods(List.of("HEAD",
                "GET", "POST", "PUT", "DELETE", "PATCH"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    



JwtTokenFilter:

public class JwtTokenFilter extends GenericFilterBean 
    private JwtTokenProvider jwtTokenProvider;

    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) 
        this.jwtTokenProvider = jwtTokenProvider;
    

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
            throws IOException, ServletException 

        String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
        System.out.println("req = "+req+" token1 = "+token);
        if (token != null && jwtTokenProvider.validateToken(token)) 

            Authentication auth = jwtTokenProvider.getAuthentication(token);
            if (auth != null) 
                SecurityContextHolder.getContext().setAuthentication(auth);
            
        
        filterChain.doFilter(req, res);
    

JwtTokenProvider:

@Component
public class JwtTokenProvider 

    @Value("$jwt.token.secret")
    private String secret;

    @Value("$jwt.token.expired")
    private long validityInMilliseconds;


    private UserPrincipalDetailsService userPrincipalDetailsService;

    public JwtTokenProvider() 
    

    public JwtTokenProvider(UserPrincipalDetailsService userPrincipalDetailsService) 
        this.userPrincipalDetailsService = userPrincipalDetailsService;
    

    public JwtTokenProvider(String secret, long validityInMilliseconds, UserPrincipalDetailsService userPrincipalDetailsService) 
        this.secret = secret;
        this.validityInMilliseconds = validityInMilliseconds;
        this.userPrincipalDetailsService = userPrincipalDetailsService;
    

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

    public String createToken(String username, List<String> roles)
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles",roles);

        Date now = new Date();
        Date validity = new Date(now.getTime() + validityInMilliseconds);

        return Jwts.builder()//
                .setClaims(claims)//
                .setIssuedAt(now)//
                .setExpiration(validity)//
                .signWith(SignatureAlgorithm.HS256, secret)//
                .compact();
    

    public Authentication getAuthentication(String token) 

        UserDetails userDetails = this.userPrincipalDetailsService.loadUserByUsername(getUsername(token));
        return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
    

    public String getUsername(String token)
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
    

    public boolean validateToken(String token) 
        try 
            Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token);

            if (claims.getBody().getExpiration().before(new Date())) 
                return false;
            

            return true;
         catch (JwtException | IllegalArgumentException e) 
            throw new JwtAuthenticationException("JWT token is expired or invalid");
        
    

    public String resolveToken(HttpServletRequest req) 
        String bearerToken = req.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer_")) 
            return bearerToken.substring(7, bearerToken.length());
        
        return null;
    

JwtConfigurer:

public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> 
    private JwtTokenProvider jwtTokenProvider;

    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) 
        this.jwtTokenProvider = jwtTokenProvider;
    

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception 
        JwtTokenFilter jwtTokenFilter = new JwtTokenFilter(jwtTokenProvider);
        httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    

UserPrincipalDetailService:

@Service
public class UserPrincipalDetailsService implements UserDetailsService 
    private final UserRepository userRepository;

    public UserPrincipalDetailsService(UserRepository userRepository) 
        this.userRepository = userRepository;
    


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException 
        User user = this.userRepository.findByUsername(username);
        if (user == null) 
            throw new UsernameNotFoundException("User with username: " + username + " not found");
        

        UserPrincipal userPrincipal = new UserPrincipal(user);
        return userPrincipal;
    

用户主体:

public class UserPrincipal implements UserDetails 
    private User user;

    public UserPrincipal(User user)
        this.user = user;
    

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() 
        List<GrantedAuthority> authorities = new ArrayList<>();

        // Extract list of permissions (name)
        this.user.getPermissionList().forEach(p -> 
            GrantedAuthority authority = new SimpleGrantedAuthority(p);
            authorities.add(authority);
        );

        // Extract list of roles (ROLE_name)
        this.user.getRoleList().forEach(r -> 
            GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + r);
            authorities.add(authority);
        );

        return authorities;
    

    @Override
    public String getPassword() 
        return this.user.getPassword();
    

    @Override
    public String getUsername() 
        return this.user.getUsername();
    

    @Override
    public boolean isAccountNonExpired() 
        return true;
    

    @Override
    public boolean isAccountNonLocked() 
        return true;
    

    @Override
    public boolean isCredentialsNonExpired() 
        return true;
    

    @Override
    public boolean isEnabled() 
        return this.user.getActive() == 1;
    

【问题讨论】:

你解决了吗?同样的问题 【参考方案1】:

很难说,因为你删除了所有的行号。但最后你在 JWTTokenprovider 的 getAuthentication 方法中有一个 NullPointerException。你的 getUsername(token) 方法坏了吗?

【讨论】:

以上是关于在 Spring Security 中检查令牌的 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

Spring security OAuth - 检查访问令牌的有效性?

Spring Security 检查外部 JWT 角色

登录 Spring Security 后如何返回基本令牌?

在 Spring Security 中获取访问令牌

Spring Boot 2 安全性 - 预认证令牌 - 允许运行状况检查

我可以使用 Spring Security @PreAuthorize 来检查 HTTP 标头吗?