47

我正在尝试使用受 Oauth2 或 Http-Basic 身份验证保护的资源来实现 API。

当我加载首先将 http-basic 身份验证应用于资源的 WebSecurityConfigurerAdapter 时,不接受 Oauth2 令牌身份验证。反之亦然。

示例配置: 这将 http-basic 身份验证应用于所有 /user/** 资源

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private LoginApi loginApi;

    @Autowired
    public void setLoginApi(LoginApi loginApi) {
        this.loginApi = loginApi;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(new PortalUserAuthenticationProvider(loginApi));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/users/**").authenticated()
                .and()
            .httpBasic();
    }

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

这会将 oauth 令牌保护应用于 /user/** 资源

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .requestMatchers().antMatchers("/users/**")
        .and()
            .authorizeRequests()
                .antMatchers("/users/**").access("#oauth2.clientHasRole('ROLE_CLIENT') and #oauth2.hasScope('read')");
    }
}

我确定我缺少一些魔术代码,如果第一个失败,它会告诉 spring 尝试两者?

任何帮助将不胜感激。

4

8 回答 8

53

我设法根据 Michael Ressler 的回答的提示完成了这项工作,但做了一些调整。

我的目标是允许 Basic Auth 和 Oauth 在相同的资源端点上,例如 /leafcase/123。由于 filterChains 的排序,我被困了很长一段时间(可以在 FilterChainProxy.filterChains 中检查);默认顺序如下:

  • Oauth 身份验证服务器(如果在同一项目中启用)的 filterChains。默认顺序 0(请参阅 AuthorizationServerSecurityConfiguration)
  • Oauth 资源服务器的过滤器链。默认顺序 3(请参阅 ResourceServerConfiguration)。它有一个请求匹配器逻辑,可以匹配除 Oauth 身份验证端点之外的任何内容(例如,/oauth/token、/oauth/authorize 等。请参阅 ResourceServerConfiguration$NotOauthRequestMatcher.matches())。
  • 与 config(HttpSecurity http) 对应的 filterChains - 默认顺序为 100,请参阅 WebSecurityConfigurerAdapter。

由于资源服务器的 filterChains 排名高于 WebSecurityConfigurerAdapter 配置的 filterchain,前者几乎匹配每个资源端点,因此 Oauth 资源服务器逻辑总是启动对资源端点的任何请求(即使请求使用 Authorization:Basic 标头)。你会得到的错误是:

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}

为了完成这项工作,我做了 2 处更改:

首先,将 WebSecurityConfigurerAdapter 排序高于资源服务器(排序 2 高于排序 3)。

@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

其次,让 configure(HttpSecurity) 使用只匹配“Authorization: Basic”的客户 RequestMatcher。

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

    http
        .anonymous().disable()
        .requestMatcher(new BasicRequestMatcher())
        .authorizeRequests()
            .anyRequest().authenticated()
            .and()
        .httpBasic()
             .authenticationEntryPoint(oAuth2AuthenticationEntryPoint())
            .and()
        // ... other stuff
 }
 ...
 private static class BasicRequestMatcher implements RequestMatcher {
    @Override
    public boolean matches(HttpServletRequest request) {
        String auth = request.getHeader("Authorization");
        return (auth != null && auth.startsWith("Basic"));
    }
 }

因此,它会在资源服务器的 filterChain 有机会匹配它之前匹配并处理 Basic Auth 资源请求。它也只处理 Authorizaiton:Basic 资源请求,因此任何带有 Authorization:Bearer 的请求都会失败,然后由资源服务器的 filterChain 处理(即,Oauth 的过滤器启动)。此外,它的排名低于 AuthenticationServer(如果在同一项目上启用了 AuthenticationServer),因此它不会阻止 AuthenticaitonServer 的过滤器链处理对 /oauth/token 等的请求。

于 2016-03-27T03:54:17.920 回答
12

这可能与您正在寻找的内容接近:

@Override
public void configure(HttpSecurity http) throws Exception {
    http.requestMatcher(new OAuthRequestedMatcher())
    .authorizeRequests()
        .anyRequest().authenticated();
}

private static class OAuthRequestedMatcher implements RequestMatcher {
    @Override
    public boolean matches(HttpServletRequest request) {
        String auth = request.getHeader("Authorization");
        // Determine if the client request contained an OAuth Authorization
        return (auth != null) && auth.startsWith("Bearer");
    }
}

唯一没有提供的是在身份验证不成功时“回退”的方法。

对我来说,这种方法很有意义。如果用户通过基本身份验证直接为请求提供身份验证,则不需要 OAuth。如果客户端是一个行动者,那么我们需要这个过滤器介入并确保请求得到正确的身份验证。

于 2015-01-21T06:10:55.717 回答
4

为什么不反过来呢?如果没有附加令牌,只需绕过资源服务器,然后回退到正常的安全过滤器链。这是资源服务器过滤器停止的方式。

@Configuration
@EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {


    @Throws(Exception::class)
    override fun configure(resources: ResourceServerSecurityConfigurer) {
        resources.resourceId("aaa")
    }

    /**
     * Resources exposed via oauth. As we are providing also local user interface they are also accessible from within.
     */
    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http.requestMatcher(BearerAuthorizationHeaderMatcher())
                .authorizeRequests()
                .anyRequest()
                .authenticated()
    }

    private class BearerAuthorizationHeaderMatcher : RequestMatcher {
        override fun matches(request: HttpServletRequest): Boolean {
            val auth = request.getHeader("Authorization")
            return auth != null && auth.startsWith("Bearer")
        }
    }

}
于 2017-11-10T08:32:31.240 回答
3

@kca2ply 提供的解决方案效果很好。我注意到浏览器没有发出挑战,所以我将代码稍微调整为以下内容:

@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

    // @formatter:off
    http.anonymous().disable()
      .requestMatcher(request -> {
          String auth = request.getHeader(HttpHeaders.AUTHORIZATION);
          return (auth != null && auth.startsWith("Basic"));
      })
      .antMatcher("/**")
      .authorizeRequests().anyRequest().authenticated()
    .and()
      .httpBasic();
    // @formatter:on
  }

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
    .withUser("user").password("password").roles("USER");
  }
}

使用两者requestMatcher()并使antMatcher()事情完美地工作。如果尚未提供,浏览器和 HTTP 客户端现在将首先挑战基本信用。如果没有提供凭据,它会落入 OAuth2。

于 2016-09-09T02:44:51.717 回答
2

您可以将 BasicAuthenticationFilter 添加到安全过滤器链中,以在受保护资源上获得 OAuth2 或基本身份验证安全性。示例配置如下...

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManagerBean;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        final String[] userEndpoints = {
            "/v1/api/airline"
        };

        final String[] adminEndpoints = {
                "/v1/api/jobs**"
            };

        http
            .requestMatchers()
                .antMatchers(userEndpoints)
                .antMatchers(adminEndpoints)
                .antMatchers("/secure/**")
                .and()
            .authorizeRequests()
                .antMatchers("/secure/**").authenticated()
                .antMatchers(userEndpoints).hasRole("USER")
                .antMatchers(adminEndpoints).hasRole("ADMIN");

        // @formatter:on
        http.addFilterBefore(new BasicAuthenticationFilter(authenticationManagerBean),
                UsernamePasswordAuthenticationFilter.class);
    }

}
于 2016-10-26T05:30:46.287 回答
1

无法为您提供完整的示例,但这里有一个提示:

粗略地说,spring auth 只是从请求(标头)中提取身份验证数据的请求过滤器和为该身份验证提供身份验证对象的身份验证管理器的组合。

因此,要在相同的 url 获得基本和 oauth,您需要在过滤器链 BasicAuthenticationFilter 和 OAuth2AuthenticationProcessingFilter 中安装 2 个过滤器。

我认为问题在于ConfiguringAdapters 适合更简单的配置,因为它们往往会相互覆盖。所以第一步尝试移动

.httpBasic();

ResourceServerConfiguration 注意,您还需要提供 2 个不同的身份验证管理器:一个用于基本身份验证,一个用于 oauth

于 2014-08-19T11:33:35.950 回答
1

如果有人试图让这个与 Spring WebFlux 一起工作,那么确定是否处理请求的方法称为“securityMatcher”,而不是“requestMatcher”。

IE

fun configureBasicAuth(http: ServerHttpSecurity): SecurityWebFilterChain {
    return http
        .securityMatcher(BasicAuthServerWebExchangeMatcher())
        .authorizeExchange()
        ...
于 2020-08-20T05:18:47.187 回答
0

我相信不可能同时拥有两种身份验证。您可以进行基本身份验证和 oauth2 身份验证,但要针对不同的端点。正如您所做的那样,第一个配置将克服第二个配置,在这种情况下,将使用 http basic。

于 2014-05-10T00:32:43.887 回答