【问题标题】:Spring: forwarding to /oauth/token endpoint loses authenticationSpring:转发到 /oauth/token 端点失去身份验证
【发布时间】:2018-11-18 10:29:00
【问题描述】:

我正在构建一个 Spring Boot 授权服务器,它需要使用两种不同的身份验证方法生成 Oauth2 令牌。我希望每个方法都有一个不同的端点,但默认情况下 Spring 只创建 /oauth/token,虽然它可以更改,但我认为它不可能有两个不同的路径。

作为替代方案,我尝试在控制器中创建两个方法,它们执行内部forward/oauth/token,向请求添加一个参数,以便我知道它来自哪里。

我有这样的事情:

@RequestMapping(value = "/foo/oauth/token", method = RequestMethod.POST)
public ModelAndView fooOauth(ModelMap model) {
    model.addAttribute("method", "foo");
    return new ModelAndView("forward:/oauth/token", model);
}

这会正确执行转发,但身份验证失败:

There is no client authentication. Try adding an appropriate authentication filter.

同样的请求直接发送到/oauth/token时可以正常工作,所以我猜测问题是转发后BasicAuthenticationFilter没有运行。

我怎样才能让它工作?

【问题讨论】:

    标签: spring-boot spring-security spring-oauth2


    【解决方案1】:

    我遇到了完全相同的问题。经过一番研究,我发现问题是由 Spring Boot 2 引起的,而不是由 Spring Security 配置引起的。根据Spring Boot 2.0 migration guide

    为 ASYNC、ERROR 和 REQUEST 调度程序类型配置了 Spring Security 和 Spring Session 过滤器。

    以及 Spring Boot 的 SecurityFilterAutoConfiguration 源代码:

    @Bean
    @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
    public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
            SecurityProperties securityProperties) {
        DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                DEFAULT_FILTER_NAME);
        registration.setOrder(securityProperties.getFilter().getOrder());
        registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
        return registration;
    }
    
    private EnumSet<DispatcherType> getDispatcherTypes(
            SecurityProperties securityProperties) {
        if (securityProperties.getFilter().getDispatcherTypes() == null) {
            return null;
        }
        return securityProperties.getFilter().getDispatcherTypes().stream()
                .map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors
                        .collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
    }
    

    securityProperties.getFilter().getDispatcherTypes() 的默认值在 SecurityProperties 中定义为:

    private Set<DispatcherType> dispatcherTypes = new HashSet<>(
        Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
    

    因此,默认情况下,Spring Boot 配置 Spring Security,使其过滤器不会应用于 FORWARD 请求(但仅适用于 ASYNC、ERROR 和 REQUEST),因此在将请求转发到时不会应用安全过滤器来验证请求/oauth/token.

    解决方案很简单。您可以将以下行添加到您的 application.properties,以便将默认过滤器应用于所有转发的请求

    spring.security.filter.dispatcher-types=async,error,request,forward
    

    或使用路径匹配器和 dispatcherType=FORWARD 创建您自己的自定义过滤器链,以仅过滤转发到 /oauth/token 的请求。

    【讨论】:

      【解决方案2】:

      仔细查看为 Oauth 端点和转发控制器创建的过滤器链,很容易发现后者缺少 BasicAuthenticationFilter,因为它们没有经过身份验证,并且在转发后不会再次执行身份验证.

      为了解决这个问题,我创建了一个这样的新配置:

      @Configuration
      public class ForwarderSecurityConfig extends WebSecurityConfigurerAdapter {
      
          @Autowired
          private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
      
          @Autowired
          private FooClientDetailsService fooClientDetailsService;
      
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
              for (AuthorizationServerConfigurer configurerBit : configurers) configurerBit.configure(configurer);
              http.apply(configurer);
              http
                      .authorizeRequests()
                          .antMatchers("/foo/oauth/token").fullyAuthenticated()
                      .and()
                          .requestMatchers()
                          .antMatchers("/foo/oauth/token");
              http.setSharedObject(ClientDetailsService.class, fooClientDetailsService);
      
          }
      
      }
      

      此代码模仿 Spring Oauth 在幕后所做的事情 (here),在两个端点上运行具有相同身份验证选项的相同过滤器链。

      /oauth/token 端点最终运行时,它会找到它所期望的身份验证结果,并且一切正常。

      最后,如果您想在两个转发端点上运行不同的 ClientDetailsS​​ervice,您只需创建两个类似的配置类,并在每个配置类中替换 setSharedObject 调用上的 ClientDetailsS​​ervice。请注意,为此,您必须在每个类中设置不同的 @Order 值。

      【讨论】:

        猜你喜欢
        • 2021-08-15
        • 2015-01-18
        • 2020-11-21
        • 2012-05-31
        • 2017-09-07
        • 2019-01-16
        • 2018-04-18
        • 2017-05-23
        • 2015-02-02
        相关资源
        最近更新 更多