Spring 弹簧靴安全性+;JWT

Spring 弹簧靴安全性+;JWT,spring,spring-boot,spring-mvc,jwt,Spring,Spring Boot,Spring Mvc,Jwt,我有一个SpringBoot 2.4.2应用程序,它使用JSON Web令牌(JWT,有时发音为/dɒt/,与英语单词“jot”[1]相同),是一个互联网提议的标准,用于创建具有可选签名和/或可选加密的数据,其有效负载包含JSON,可以断言一些声明。令牌使用私钥或公钥/私钥进行签名。例如,服务器可以生成声明为“以管理员身份登录”的令牌,并将其提供给客户端。然后,客户端可以使用该令牌来证明它是以管理员身份登录的 这是我的网站安全配置: @Configuration @EnableWebSecuri

我有一个SpringBoot 2.4.2应用程序,它使用JSON Web令牌(JWT,有时发音为/dɒt/,与英语单词“jot”[1]相同),是一个互联网提议的标准,用于创建具有可选签名和/或可选加密的数据,其有效负载包含JSON,可以断言一些声明。令牌使用私钥或公钥/私钥进行签名。例如,服务器可以生成声明为“以管理员身份登录”的令牌,并将其提供给客户端。然后,客户端可以使用该令牌来证明它是以管理员身份登录的

这是我的网站安全配置:

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

    private static final String SALT = "fd23451*(_)nof";

    private final JwtAuthenticationEntryPoint unauthorizedHandler;
    private final JwtTokenUtil jwtTokenUtil;
    private final UserSecurityService userSecurityService;

    @Value("${jwt.header}")
    private String tokenHeader;


    public ApiWebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtTokenUtil jwtTokenUtil,
            UserSecurityService userSecurityService) {
        this.unauthorizedHandler = unauthorizedHandler;
        this.jwtTokenUtil = jwtTokenUtil;
        this.userSecurityService = userSecurityService;
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userSecurityService)
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
    }

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

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                // we don't need CSRF because our token is invulnerable
                .csrf().disable()

                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                // don't create session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                // Un-secure H2 Database
                .antMatchers("/h2-console/**/**").permitAll()
                .antMatchers("/api/v1/users").permitAll()
                .anyRequest().authenticated();

        // Custom JWT based security filter
        JwtAuthorizationTokenFilter authenticationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
        httpSecurity
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // disable page caching
        httpSecurity
                .headers()
                .frameOptions()
                .sameOrigin()  // required to set for H2 else H2 Console will be blank.
                .cacheControl();
    }

    @Override
    public void configure(WebSecurity web) {

        // AuthenticationTokenFilter will ignore the below paths
        web
                .ignoring()
                .antMatchers(
                        HttpMethod.POST,
                        "/api/v1/users"
                );

    }

}
这是我的过滤器:

@Provider
@Slf4j
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

    private UserDetailsService userDetailsService;
    private JwtTokenUtil jwtTokenUtil;
    private String tokenHeader;

    public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, String tokenHeader) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
        this.tokenHeader = tokenHeader;
    }


    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return new AntPathMatcher().match("/api/v1/users", request.getServletPath());
    }


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

        log.info("processing authentication for '{}'", request.getRequestURL());

        final String requestHeader = request.getHeader(this.tokenHeader);

        String username = null;
        String authToken = null;

        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            authToken = requestHeader.substring(7);
            try {
                username = jwtTokenUtil.getUsernameFromToken(authToken);
            } catch (IllegalArgumentException e) {
                logger.info("an error occured during getting username from token", e);
            } catch (ExpiredJwtException e) {
                logger.info("the token is expired and not valid anymore", e);
            }
        } else {
            logger.info("couldn't find bearer string, will ignore the header");
        }

        log.info("checking authentication for user '{}'", username);

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            logger.info("security context was null, so authorizating user");

            // It is not compelling necessary to load the use details from the database. You could also store the information
            // in the token and read it from it. It's up to you ;)
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
            // the database compellingly. Again it's up to you ;)
            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                log.info("authorizated user '{}', setting security context", username);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(request, response);
    }
}

这是我启动应用程序时的控制台:

18:02:51.974 [restartedMain] DEBUG com.agrumh.Application - Running with Spring Boot v2.4.2, Spring v5.3.3
18:02:51.974 [restartedMain] INFO  com.agrumh.Application - No active profile set, falling back to default profiles: default
18:02:57.383 [restartedMain] INFO  o.s.s.web.DefaultSecurityFilterChain - Will secure Ant [pattern='/api/v1/users', POST] with []
18:02:57.414 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [permitAll] for Ant [pattern='/h2-console/**/**']
18:02:57.415 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [permitAll] for Ant [pattern='/api/v1/users']
18:02:57.416 [restartedMain] DEBUG o.s.s.w.a.e.ExpressionBasedFilterInvocationSecurityMetadataSource - Adding web access control expression [authenticated] for any request
18:02:57.422 [restartedMain] INFO  o.s.s.web.DefaultSecurityFilterChain - Will secure any request with [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@24c68fed, org.springframework.security.web.context.SecurityContextPersistenceFilter@1537eb0a, org.springframework.security.web.header.HeaderWriterFilter@95de45c, org.springframework.security.web.authentication.logout.LogoutFilter@733cf550, com.dispacks.config.JwtAuthorizationTokenFilter@538a96c8, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@8d585b2, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@784cf061, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@64915f19, org.springframework.security.web.session.SessionManagementFilter@21f180d0, org.springframework.security.web.access.ExceptionTranslationFilter@2b153a28, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4942d157]
18:02:58.619 [restartedMain] INFO  com.dispacks.DispacksApplication - Started DispacksApplication in 6.974 seconds (JVM running for 7.697)
18:04:03.685 [http-nio-1133-exec-1] DEBUG o.s.security.web.FilterChainProxy - Securing POST /error
18:04:03.687 [http-nio-1133-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext
18:04:03.689 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext
18:04:03.694 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [POST /error] with attributes [authenticated]
18:04:03.698 [http-nio-1133-exec-1] INFO  c.d.s.JwtAuthenticationEntryPoint - user tries to access a secured REST resource without supplying any credentials
18:04:03.699 [http-nio-1133-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request
但当我与邮递员一起访问时,我出现了以下错误:

22:58:33.562 [http-nio-1133-exec-2] WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'text/plain' not supported]
22:58:33.579 [http-nio-1133-exec-2] INFO  c.d.s.JwtAuthenticationEntryPoint - user tries to access a secured REST resource without supplying any credentials

你从邮递员那里叫什么路?如果是
/api/v1/users
,我可以看到您在过滤器的
shouldNotFilter
方法中设置了此路径。这是否意味着您将忽略此路径的JWT筛选器


顺便说一下,如果您不需要任何附加功能,您可以使用SpringSecurity的支持来验证JWT。看一看,看看它是如何配置的。这样,您就不需要自己的筛选器。

授权和身份验证是不同的 允许发布
/api/v1/users
,因为不需要授权访问资源发布

在代码中

@覆盖
公共无效开始(HttpServletRequest请求,
HttpServletResponse,
AuthenticationException authException//AuthenticationException表示身份验证失败,而不是“未提供任何凭据”。
)抛出IOException{
//此处为断点,或打印authException。
log.info(“用户试图在不提供任何凭据的情况下访问受保护的REST资源”);//错误消息。您可以说“身份验证失败”。也可以说log.info(authException.getMessage())。
response.senderro(HttpServletResponse.SC_UNAUTHORIZED,“UNAUTHORIZED”);
}
身份验证错误实际上发生在访问
/error
资源时

18:04:03.694 [http-nio-1133-exec-1] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Failed to authorize filter invocation [POST /error] with attributes [authenticated]
我假设发生了一些错误,您的应用程序正在将您重定向到
/error
,但是
/error
受到保护。因此,身份验证异常发生在
/error

  • .permitAll()之前添加
    /error
  • 中断authenticationException,以便更新此答案

  • 如果我理解您的意思是正确的,那么您希望JWT筛选器仅对某些端点运行吗?我遇到了同样的问题,无论我如何尝试不同的安全配置,我都无法让SpringSecurity只对指定的入口点运行JWT过滤器

    我通过重写shouldNotFilter解决了这个问题,就像您所做的那样,但我的看起来像这样:

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return new AntPathRequestMatcher("/api/v1/users").matches(request);
    }
    

    也许这可以解决您的问题。

    事实上,api/v1/users,所以我忽略此请求,调用此端点时您提供了什么凭据?JWT/Basic/Cookie?没有身份验证啊,现在我看到您实际上希望此路径没有身份验证。只是为了绝对确定——你是在向邮递员索要邮件?配置在我看来很好,奇怪的是它不工作。可能尝试打开调试或跟踪日志,查看服务器中发生了什么(甚至在某处放置断点),并尝试了解Spring重定向您的原因。是的,我正在使用POSTto为除此之外的所有端点运行:/api/v1/usersTry我的shouldNotFilter()版本。它应该有效地停止对端点“/api/v1/users”的过滤。至少在我的项目中是这样。你解决问题了吗?如果您中断了异常,异常消息会说什么?
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return new AntPathRequestMatcher("/api/v1/users").matches(request);
    }