java - 为 Basic Auth 和 JWT 配置 spring security 的多种认证类型

标签 java spring-boot spring-security basic-authentication jwt-auth

我有一个 API 需要以两种不同的方式进行保护:

1) 将 JWT 用于除 1 以外的所有请求 URL,需要使用基本身份验证进行保护

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

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

然后我希望必须通过/api/login 为所有其他请求 URL 生成 token 并将其添加到 Authorization: Bearer {Token} header ..

但是,如果我通过 Basic Auth 进行了身份验证,我就可以访问其他 URL(受 JWT 身份验证保护),而无需在请求中使用 token 。

当我在没有使用 Basic Auth 进行身份验证的情况下访问受 JWT 保护的 URL 时,我必须在 header 中发送 token 并且它按预期工作。

我应该期待这个吗?正如我所相信的,即使我已经通过一个端点的基本身份验证进行了身份验证,我仍然应该在请求中为所有其他 protected JWT 端点发送 token 。

我找到了这个答案: SpringBoot multiple authentication adapter

还有这篇文章: 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();
        }
    }

使用 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();
    }

还有 JWT 实现:

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

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

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

谢谢

最佳答案

您在 @Order(1) 的安全配置中发布的代码

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

如果这是您使用的确切代码,那么您的 @Order(2)不会咨询配置。这将是死配置。
让我解释一下!
http.authorizeRequests() == http.antMatcher("/**").authorizeRequests()

在您的第一个配置中,您使用了通配符并且您的配置结果是

  • /v1/crash/consumeCrashBoxxEvent如果用户通过身份验证并具有 ADMIN 角色,则访问
  • Rest of URL's如果用户通过身份验证则访问

让我猜猜发生了什么!
1. 您正在访问 URL /v1/crash/consumeCrashBoxxEventany URL系统将提示您进行基本身份验证。
2. 认证成功后,您可以访问任何URL,因为您是认证用户。

However, if ive been authenticated via Basic Auth, I can then access the other URL's (protected by JWT auth) without even having a token in the request.

因为正如我所说,您可以访问任何 URL,因为您是经过身份验证的用户

When I access the JWT protected URL's without authenticating with Basic Auth, I have to send the token in the header and it works as expected

检查是否可以访问没有 token 。因为一旦您通过基本身份验证登录,就不会从服务器端注销(即使您重新启动服务器)。只有关闭浏览器才能实现注销。所以你通过关闭并再次启动浏览器来测试它。并通过不发送 JWT token 对其进行测试。
还要确保您的请求到达 JwtAuthenticationTokenFilter,将调试日志进行验证。

因为在您的问题中有很多抽象,除非您发布完整的代码,否则很难准确预测发生了什么。

如果我的预测与实际情况有偏差,请在评论中告诉我。

关于java - 为 Basic Auth 和 JWT 配置 spring security 的多种认证类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58081110/

相关文章:

grails - spring-boot 在使用 GORM 的多模块 gradle 构建中不支持 @EntityScan

java - 实现 spring security 时出现 xmlBeanDefinitionStoreException

java - Spring Boot OAuth 总是重定向到 HTTP(IBM Cloud CF + Spring Boot 2)

java - 如何从文件中获取信息(字母和整数),并存储该信息以供进一步使用?

java - 为什么G1在对象复制上花费了这么多时间?

java - spring 从函数调用 Controller 方法

java - Spring Session、Websocket、REST token 安全

java - 如何从方法引用创建比较器?

java - JBoss 数据库连接池自动提交行为

java - 从执行器指标中排除路径参数