【问题标题】:Spring-security: application shares web pages and REST APISpring-security:应用程序共享网页和 REST API
【发布时间】:2020-07-23 08:14:36
【问题描述】:

我目前正在开发一个 Spring-MVC 应用程序,该应用程序托管我的 html 网页以及(某种)REST API。 问题在于访问应用程序的内容,身份验证失败的处理方式应不同:

  • 如果用户使用浏览器访问页面,必须重定向到登录页面(由浏览器管理的302)
  • 如果它是页面中的一个组件(在我的例子中是一个 jquery 数据表),它试图通过 Ajax 加载下一页结果,我应该收到 401 状态以允许我在我的 JS 中解释它并重定向到登录页面。

但实际上,应用程序总是返回一个由浏览器处理的 302,而我的 Javascript 没有机会正确处理它。

从我的阅读中,我发现我必须定义 2 个 WebSecurityConfigurerAdapter,我做到了,一个用于 /api,另一个用于网页,但在这两种情况下它都会发送重定向 302。

我的 API 配置是:

@Configuration
@Slf4j
@Order(SecurityProperties.BASIC_AUTH_ORDER - 5)
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
    .csrf()
    .disable()
    .authorizeRequests()
    .antMatchers("/api/**")
    .authenticated();

}
}

还有网络配置:

@Configuration
@Slf4j
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(final HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
        .antMatchers("/action/**", "/login", "/css/**")
        .permitAll()
        .anyRequest().authenticated().and()
        .formLogin()
        .loginPage("/login")
        .and()
        .logout()
        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
}
...
}

有人可以帮助我了解问题所在吗?

我看到了一些解决方案,它读取 Ajax 响应的内容并在里面有一些 html 时重定向,但我认为它应该在服务器端正确处理。

【问题讨论】:

    标签: javascript spring-security


    【解决方案1】:

    你似乎很亲密。请记住,当您定义WebSecurityConfigurerAdapter 时,您需要说明您要使用哪种身份验证机制:

    @Configuration
    @Slf4j
    @Order(99)
    public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http
                .requestMatchers()
                    .antMatchers("/api/**")
                    .and()
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .and()
                .httpBasic(); // <-- use HTTP basic
        }
    }
    

    另外,请注意requestMatchersauthorizeRequests 之间的细微差别。当您有不同的WebSecurityConfigurerAdapters 时,它们会使用requestMatchers 按路径分段。

    上面sn-p的意思是“对于匹配/api/**的请求,用HTTP basic对任何请求进行身份验证。”

    为了网络安全,我们希望处理“其他所有内容”,因此我们不需要requestMatchers()。因此,要为您的网络安全做同样的事情,您应该这样做:

    @Configuration
    @Slf4j
    @Order(100)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(final HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/action/**", "/login", "/css/**").permitAll()
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .and()
            // ...
            ;
        }
    }
    

    上面sn -p的意思是“用Form登录验证任何请求”。

    最后,Spring Security 将处理WebSecurityConfigurerAdapters 升序@Order。因此,请注意第一个是“99”,第二个是“100”。这意味着RestSecurityConfig 将在WebSecurityConfig 之前处理。你可以把它想象成一个 if 语句:

    if (request matches /api) {
       check the `RestSecurityConfig` configuration
    } else {
       check the `WebSecurityConfig` configuration
    }
    

    【讨论】:

    • 就我的理解而言,基本身份验证不是这样使用的,它更像是一种技巧,如果没有其他过滤器接受请求,则使过滤器返回 401,对吗?因为当我查看代码时,我认为我必须添加一个用户/密码标题,但事实并非如此,它是这样工作的。
    • 使用httpBasic 意味着您正在为您的API 客户端使用Authorization: Basic。老实说,Authorization: Bearer 正变得越来越普遍。您应该指定 REST API 客户端用来证明他们是谁的机制。
    【解决方案2】:

    你需要自定义rest authenticationentrypoint:

    @Component
    public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(
            HttpServletRequest request,
            HttpServletResponse response,
            AuthenticationException authException) throws IOException {
    
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
       }
    }
    

    还在您的 WebSecurityConfigurerAdapter 实现中将其注册到 httpsecurity,例如:

    @Autowired
    RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception{
           http.
              ...
              .exceptionHandling()
              .authenticationEntryPoint(restAuthenticationEntryPoint)
              .formLogin().loginProcessingUrl("/doLogin")
    }
    

    因此您将在前端应用程序中处理未经授权的 401。 也不需要.loginPage("/login"),因为您有 REST,否则您将被重定向,并且通过上述配置,您将不会被重定向(302),而是将 401 发送到在您的浏览器中运行的应用程序。在您的前端应用程序中,您会将凭据发布到“/doLogin”。

    【讨论】:

    • 也许我不清楚,但我需要重定向和 401,这取决于应用程序是如何到达的:浏览器需要 302,jquery 组件需要 401。否则,我可以分离我的应用程序,一个用于 Web 应用程序,一个用于 API,但这不是我想要做的。
    • 我以为你只是休息 API,我误解了问题......然后我的逻辑可以应用于最早的有序过滤器链 http.antMatcher("/restapi/**") 以及更高有序的过滤器链使用 302 重定向
    猜你喜欢
    • 2016-03-28
    • 2014-04-15
    • 2013-10-05
    • 2011-09-10
    • 2012-01-03
    • 2014-08-31
    • 2016-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多