Java 无法在spring boot security中跳过登录url的OncePerRequestFilter筛选器(基本上是在初始登录期间获取JWT令牌)

Java 无法在spring boot security中跳过登录url的OncePerRequestFilter筛选器(基本上是在初始登录期间获取JWT令牌),java,spring,spring-boot,spring-security,jwt,Java,Spring,Spring Boot,Spring Security,Jwt,我正在尝试使用SpringSecurity开发一个具有JWT授权的SpringBootRESTAPI。我希望我的所有请求都通过过滤器来验证JWT令牌,但/authenticate请求除外,该请求应生成JWT令牌。但是使用下面的代码,/authenticate请求也会被过滤器截获,因此它在401中失败。请让我知道我在下面的代码中遗漏了什么 JwtTokenFilter类 @Component public class JwtTokenFilter extends OncePerRequestFil

我正在尝试使用SpringSecurity开发一个具有JWT授权的SpringBootRESTAPI。我希望我的所有请求都通过过滤器来验证JWT令牌,但
/authenticate
请求除外,该请求应生成JWT令牌。但是使用下面的代码,
/authenticate
请求也会被过滤器截获,因此它在401中失败。请让我知道我在下面的代码中遗漏了什么

JwtTokenFilter类

@Component
public class JwtTokenFilter extends OncePerRequestFilter
{

    @Autowired
    private UserService     jwtUserDetailsService;
    @Autowired
    private JwtTokenUtil    jwtTokenUtil;

    @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;
        // JWT Token is in the form "Bearer token". Remove Bearer word and get
        // only the Token
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer "))
        {
            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");
            }
        }
        else
        {
            logger.warn("JWT Token does not begin with Bearer String");
        }
        // Once we get the token validate it.
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
        {
            UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
            // if token is valid configure Spring Security to manually set
            // authentication
            if (jwtTokenUtil.validateToken(jwtToken, userDetails))
            {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // After setting the Authentication in the context, we specify
                // that the current user is authenticated. So it passes the
                // Spring Security Configurations successfully.
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtConfigurer extends WebSecurityConfigurerAdapter
{

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    private UserService                 jwtUserDetailsService;
    @Autowired
    private JwtTokenFilter              jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
    {
        // configure AuthenticationManager so that it knows from where to load
        // user for matching credentials
        // Use BCryptPasswordEncoder
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // We don't need CSRF for this example

        httpSecurity.csrf().disable().
        // dont authenticate this particular request
                authorizeRequests().antMatchers("/authenticate").permitAll().
                // all other requests need to be authenticated
                anyRequest().authenticated().and().
                // make sure we use stateless session; session won't be used to
                // store user's state.
                exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // Add a filter to validate the tokens with every request
        httpSecurity.addFilterAfter(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
JwtConfig类

@Component
public class JwtTokenFilter extends OncePerRequestFilter
{

    @Autowired
    private UserService     jwtUserDetailsService;
    @Autowired
    private JwtTokenUtil    jwtTokenUtil;

    @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;
        // JWT Token is in the form "Bearer token". Remove Bearer word and get
        // only the Token
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer "))
        {
            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");
            }
        }
        else
        {
            logger.warn("JWT Token does not begin with Bearer String");
        }
        // Once we get the token validate it.
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
        {
            UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
            // if token is valid configure Spring Security to manually set
            // authentication
            if (jwtTokenUtil.validateToken(jwtToken, userDetails))
            {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // After setting the Authentication in the context, we specify
                // that the current user is authenticated. So it passes the
                // Spring Security Configurations successfully.
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtConfigurer extends WebSecurityConfigurerAdapter
{

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    private UserService                 jwtUserDetailsService;
    @Autowired
    private JwtTokenFilter              jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
    {
        // configure AuthenticationManager so that it knows from where to load
        // user for matching credentials
        // Use BCryptPasswordEncoder
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // We don't need CSRF for this example

        httpSecurity.csrf().disable().
        // dont authenticate this particular request
                authorizeRequests().antMatchers("/authenticate").permitAll().
                // all other requests need to be authenticated
                anyRequest().authenticated().and().
                // make sure we use stateless session; session won't be used to
                // store user's state.
                exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // Add a filter to validate the tokens with every request
        httpSecurity.addFilterAfter(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
控制器类

@Component
public class JwtTokenFilter extends OncePerRequestFilter
{

    @Autowired
    private UserService     jwtUserDetailsService;
    @Autowired
    private JwtTokenUtil    jwtTokenUtil;

    @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;
        // JWT Token is in the form "Bearer token". Remove Bearer word and get
        // only the Token
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer "))
        {
            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");
            }
        }
        else
        {
            logger.warn("JWT Token does not begin with Bearer String");
        }
        // Once we get the token validate it.
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
        {
            UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
            // if token is valid configure Spring Security to manually set
            // authentication
            if (jwtTokenUtil.validateToken(jwtToken, userDetails))
            {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // After setting the Authentication in the context, we specify
                // that the current user is authenticated. So it passes the
                // Spring Security Configurations successfully.
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtConfigurer extends WebSecurityConfigurerAdapter
{

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    @Autowired
    private UserService                 jwtUserDetailsService;
    @Autowired
    private JwtTokenFilter              jwtRequestFilter;

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
    {
        // configure AuthenticationManager so that it knows from where to load
        // user for matching credentials
        // Use BCryptPasswordEncoder
        auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
    }
    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        // We don't need CSRF for this example

        httpSecurity.csrf().disable().
        // dont authenticate this particular request
                authorizeRequests().antMatchers("/authenticate").permitAll().
                // all other requests need to be authenticated
                anyRequest().authenticated().and().
                // make sure we use stateless session; session won't be used to
                // store user's state.
                exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // Add a filter to validate the tokens with every request
        httpSecurity.addFilterAfter(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}
@RestController
@交叉起源
公共类JwtAuthenticationController
{
@自动连线
私人AuthenticationManager AuthenticationManager;
@自动连线
私有JwtTokenUtil JwtTokenUtil;
@自动连线
私有用户服务用户详细信息服务;
@RequestMapping(value=“/authenticate”,method=RequestMethod.POST)
public ResponseEntity createAuthenticationToken(@RequestBody User authenticationRequest)引发异常
{
验证(authenticationRequest.getUsername(),authenticationRequest.getPassword());
final UserDetails UserDetails=userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
最终字符串标记=jwtTokenUtil.generateToken(userDetails);
用户u=新用户();
u、 setUsername(authenticationRequest.getUsername());
u、 setToken(token);
返回响应度ok(u);
}
私有void身份验证(字符串用户名、字符串密码)引发异常
{
尝试
{
authenticationManager.authenticate(新用户名PasswordAuthenticationToken(用户名、密码));
}
捕获(禁用异常e)
{
抛出新异常(“用户禁用”,e);
}
捕获(BadCredentialsException e)
{
抛出新异常(“无效的_凭证”,e);
}
}
}

基本上,OncePerRequestFilter只能以这种方式工作。不确定这是否可以避免。引用文件:

筛选器基类,旨在保证每个 请求分派,在任何servlet容器上

您也可以尝试添加方法类型以跳过端点上的身份验证。
.antMatchers(HttpMethod.GET,“/authenticate”).permitAll()

正如Mohit所指出的,即使我也看不到您的配置中有任何错误

如果您理解下面的解释,它将帮助您解决问题。
即使配置了
/authenticate
请求,请求也应该通过JWT过滤器。但是,
FilterSecurityInterceptor
是最后一个过滤器,它将检查配置的AntMatcher和相关的限制/权限,并据此决定是允许还是拒绝请求

对于
/authenticate
方法,它应该通过过滤器和requestTokenHeader,用户名应该为null,并确保
chain.doFilter(请求、响应)正在到达,没有任何异常

当它到达过滤器安全interceptor时,如果您将日志级别设置为debug,则应打印类似于下面给出的日志

DEBUG - /app/admin/app-config at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor' 
DEBUG - Checking match of request : '/app/admin/app-config'; against '/resources/**' 
DEBUG - Checking match of request : '/app/admin/app-config'; against '/' 
DEBUG - Checking match of request : '/app/admin/app-config'; against '/login' 
DEBUG - Checking match of request : '/app/admin/app-config'; against '/api/**' 
DEBUG - Checking match of request : '/app/admin/app-config'; against '/app/admin/app-config' 
DEBUG - Secure object: FilterInvocation: URL: /app/admin/app-config; Attributes: [permitAll] 
DEBUG - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@511cd205: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@2cd90: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 696171A944493ACA1A0F7D560D93D42B; Granted Authorities: ROLE_ANONYMOUS 
DEBUG - Voter: org.springframework.security.web.access.expression.WebExpressionVoter@6df827bf, returned: 1 
DEBUG - Authorization successful 

附加这些日志,以便可以预测问题

编写一个实现org.springframework.security.config.annotation.web.configuration.websecurityConfigureAdapter的配置类,并重写configur方法,如下所示:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    // dont authenticate this particular request. you can use a wild card here. e.g /unprotected/**
    httpSecurity.csrf().disable().authorizeRequests().antMatchers("/authenticate").permitAll().
            //authenticate everything else
    anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    // Add a filter to validate the tokens with every request
    httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}

我为此挣扎了两天,最好的解决方案是在我的
SecurityConfig
上结合此设置:

override fun configure(http: HttpSecurity?) {
        // Disable CORS
        http!!.cors().disable()

        // Disable CSRF
        http.csrf().disable()

        // Set session management to stateless
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

        //Add JwtTokenFilter
        http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter::class.java)
    }

在这个回答中(在给定的链接中),我已经解释了如何设置日志记录器级别以调试日志并将日志打印到Eclipse/STS控制台@确保您的请求URL中没有输入错误。它应该是
/authenticate
,如果您的请求URL不同于
/authenticate
/authanticate
,或者任何输入错误都将导致401,因为请求的URL不同于允许的URL。只是一种可能性。只需检查一下。@PraveenKumar,我收到了“错误”,原因是:org.xml.sax.SAXParseException;行号:3;列号:16;添加logback.xml后必须声明元素类型“configuration”。下面是我的logback.xml,我的eclipse版本是面向Web开发人员的eclipse Java EE IDE。版本:2018-09(4.9.0)。尝试了post()中的一些解决方案,但没有成功。我还尝试重写此方法以跳过筛选/身份验证,但在添加下面的代码片段
@Override protected boolean shouldNotFilter(HttpServletRequest)后,在403中失败抛出ServletException{System.out.println(request.getServletPath());返回新的AntPathMatcher().match(“/authenticate”,request.getServletPath());}
很抱歉,我忘了提到我在wildfly服务器上运行应用程序,而不是在嵌入式tomcat上。在服务器上启用调试日志后,我可以在日志中看到授权成功,后来请求甚至到达了控制器,但在我的控制器中引发了异常,因为我正在使用BCryptPasswordEncoder,并且我数据库中的密码与请求中的密码不匹配。下面是日志16:40:45989调试[org.springframework.security.web.access.intercept.FilterSecurityInterceptor](默认任务1)授权成功。