spring-security 无法在Sping Boot Security中跳过登录URL的OncePerRequestFilter过滤器(基本上是为了在初始登录期间获取JWT标记)

dfddblmv  于 2022-11-11  发布在  Spring
关注(0)|答案(5)|浏览(633)

我正在尝试使用Spring Security开发一个具有JWT授权的Sping Boot rest API。我希望我的所有请求都通过过滤器来验证JWT标记,但/authenticate请求除外,该请求应生成jwt标记。但使用下面的代码,/authenticate请求也被过滤器拦截,因此它失败,并返回401。请告诉我下面的代码中遗漏了什么。

JwtToken筛选器类别

@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);
    }
}

JwtConfig类别

@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
@CrossOrigin
public class JwtAuthenticationController
{

    @Autowired
    private AuthenticationManager   authenticationManager;
    @Autowired
    private JwtTokenUtil            jwtTokenUtil;
    @Autowired
    private UserService             userDetailsService;

    @RequestMapping(value = "/authenticate", method = RequestMethod.POST)
    public ResponseEntity<?> createAuthenticationToken(@RequestBody User authenticationRequest) throws Exception
    {
        authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        final String token = jwtTokenUtil.generateToken(userDetails);

        User u = new User();
        u.setUsername(authenticationRequest.getUsername());
        u.setToken(token);
        return ResponseEntity.ok(u);
    }
    private void authenticate(String username, String password) throws Exception
    {
        try
        {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }
        catch (DisabledException e)
        {
            throw new Exception("USER_DISABLED", e);
        }
        catch (BadCredentialsException e)
        {
            throw new Exception("INVALID_CREDENTIALS", e);
        }
    }
}
mrzz3bfm

mrzz3bfm1#

我为此纠结了两天,最好的解决方案是Tom answer结合我的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)
    }
insrf1ej

insrf1ej2#

基本上,OncePerRequestFilter只以这种方式工作。不确定是否可以避免这种情况。引用文档:
旨在保证在任何servlet容器上每个请求分派执行一次的过滤器基类。
您也可以尝试添加方法类型以跳过端点上的身份验证。.antMatchers(HttpMethod.GET, "/authenticate").permitAll()

ubby3x7f

ubby3x7f3#

正如Mohit已经指出的那样,即使是我也看不到你的配置中有任何错误。
如果你理解下面的解释,它将帮助你解决。
即使/authenticate request配置为permitAll,请求也应该通过您的JWT过滤器。但是FilterSecurityInterceptor是最后一个过滤器,它将检查是否存在已配置的antMatchers以及关联的限制/权限,并根据这些限制/权限来决定是允许还是拒绝请求。
对于/authenticate方法,它应该通过筛选器和requestTokenHeader,用户名应该为空,并确保chain.doFilter(request, response);在没有任何异常的情况下到达。
当它达到FilterSecurityInterceptor时,如果您已将日志级别设置为调试,则应打印类似于下面给出的日志。

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

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

olhwl3o2

olhwl3o24#

编写一个实现 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter 的配置类,并重写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);
}
m528fe3b

m528fe3b5#

我遇到过类似的问题,我通过比较请求路径和我不想过滤的路径来克服它。

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

        //To skip OncePerRequestFilter for authenticate endpoint
        if(request.getServletPath().equals("/authenticate")){
            filterChain.doFilter(request, response);
            return;
        }

// filter logic continue..

相关问题