使用 Spring Security 为基本身份验证和 JWT 配置多种身份验证类型

Ale*_*unn 5 java spring-security basic-authentication jwt spring-boot

我有一个 API,需要通过两种不同的方式来保护:

1) 对除 1 之外的所有请求 URL 使用 JWT,这些请求 URL 需要通过基本身份验证进行保护

2) 一个 url 的基本身份验证。

我已经为 JWT 和基本身份验证设置了安全配置。我的问题是,当我使用有效的用户名和密码向基本身份验证 URL 发出请求时,它成功地对我进行了身份验证,并完成了在 cassandra 中存储数据的工作。

然后,我希望必须通过 /api/login 为所有其他请求 URL 生成令牌,并将其添加到 Authorization: Bearer {Token} 标头中。

但是,如果我通过基本身份验证进行了身份验证,则我可以访问其他 URL(受 JWT 身份验证保护),甚至无需在请求中包含令牌。

当我在没有使用基本身份验证的情况下访问受 JWT 保护的 URL 时,我必须在标头中发送令牌,并且它会按预期工作。

我应该期待这个吗?我相信,即使我通过一个端点的基本身份验证进行了身份验证,我仍然应该在所有其他受保护的 JWT 端点的请求中发送令牌。

我找到了这个答案: SpringBoot multipleauthenticationadapter

还有这篇文章: https://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/#multiple-httpsecurity

并尝试实施解决方案,但所解释的问题仍然出现。

安全配置类如下:

@Configuration
@EnableWebSecurity
public class SecurityHttpConfig extends WebSecurityConfigurerAdapter {

    @Configuration
    @Order(1)
    public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter {

        @Value("${basic.auth.user}")
        private String basicAuthUsername;

        @Value("${basic.auth.password}")
        private String basicAuthPassword;

        @Value("${crashboxx.consume.endpoint}")
        private String crashBoxxConsumeEndpoint;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
                    .httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS);// We don't need sessions to be created.
        }

        @Bean
        public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
            return new CustomBasicAuthenticationEntryPoint();
        }

        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
            auth.inMemoryAuthentication().withUser(basicAuthUsername).password(encoder.encode(basicAuthPassword))
                    .roles("ADMIN");
        }

        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }

    @Configuration
    @Order(2)
    public static class JwtWebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Autowired
        private JwtAuthenticationEntryPoint unauthorizedHandler;

        @Autowired
        private JwtAuthenticationProvider jwtAuthenticationProvider;

        // Any endpoints that require no authorization should be added here..
        @Value("${api.login.endpoint}")
        private String loginEndpoint;

        @Autowired
        public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
            authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
        }

        @Bean
        public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
            return new JwtAuthenticationTokenFilter();
        }

        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                    .authorizeRequests().antMatchers("/api/login").permitAll().anyRequest().authenticated();

            httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
            httpSecurity.headers().cacheControl();
        }
    }
Run Code Online (Sandbox Code Playgroud)

使用 BasicAuthEntryPoint 类:

public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

    private static final Gson gson = new Gson();

    @Override
    public void commence(final HttpServletRequest request, final HttpServletResponse response,
            final AuthenticationException authException) throws IOException, ServletException {
        // Authentication failed, send error response.
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = response.getWriter();
        writer.println(gson.toJson("HTTP Status 401 : " + authException.getMessage()));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        setRealmName("Realm");
        super.afterPropertiesSet();
    }
Run Code Online (Sandbox Code Playgroud)

还有 JWT impl:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

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

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        final String requestHeader = request.getHeader(tokenHeader);
        // Ensure Auth Header contains 'Bearer'
        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
            String authToken = requestHeader.substring(7);
            JwtAuthentication authentication = new JwtAuthentication(authToken);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        chain.doFilter(request, response);
    }
Run Code Online (Sandbox Code Playgroud)

我希望这是有道理的。如果还有其他问题,请告诉我,但似乎无法解决这个问题。

我首先添加了“特殊情况”,这是基本身份验证的一个 url,但仍然没有任何区别。

谢谢

Ale*_*unn 1

这是通过使用 Praveen Kumar Lalasangi 的上述答案中提供的信息解决的。

对方法的一个小改变configure就达到了目的。更新是:

@Override
    protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable()
      .antMatcher(crashBoxxConsumeEndpoint).authorizeRequests().anyRequest()
      .hasRole("ADMIN")
      .and().httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint())
      .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
Run Code Online (Sandbox Code Playgroud)

  • 对于未来的读者,这里 OP 希望仅使用 crashBoxxConsumeEndPoint 来验证用户身份,因此一阶配置仅允许 `.antMatcher(crashBoxxConsumeEndpoint)` 进行 basicAuthentication,其余 URL 使用二阶配置(JWT 身份验证过滤器) (2认同)