AuthenticationEntryPoint 仅有时被称为

AuthenticationEntryPoint only sometimes called

我有一个简单的 AuthenticationEntryPoint,它应该为未经授权的请求设置 WWW-Authenticate header。

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
        response.setHeader("WWW-Authenticate", "FormBased");
        response.sendError(401, authException.getMessage());
    }
}

我在AuthorizationServerConfigurer

的一种配置方法中使用它
@Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
    authorizationServerSecurityConfigurer.authenticationEntryPoint(authenticationEntryPoint);
}

虽然并不总是调用此开始方法。当请求中没有 Authorize header 或 Authorize header 值不以 'Basic' 开头时,它会被调用。但是,如果授权header以'Basic'开头,则不会调用开始方法(并且响应的值为Basic realm="oauth2/client")。如何确保调用此方法?

为了获得访问令牌,您应该通过 HTTP Basic:

验证您的客户端
Authorization: Basic Base64(client_id:client_secret)

This commence method is not always called, though. It gets called when there is no Authorize header in the request or when the Authorize header value doesn't start with 'Basic'. However, if the Authorize header starts with 'Basic', the commence method is not called

Spring 安全部门在内部维护一个过滤器链,其中每个过滤器都有特定的职责,其中之一是 BasicAuthenticationFilter which would process Basic Authentications. If you take a peek at its doFilterInteral mthod, you would see:

if (header == null || !header.startsWith("Basic ")) {
    chain.doFilter(request, response);
    return;
}

如果您不传递 Authorization header 或您的 Authorization header 不是以 Basic 开头,它将跳过当前过滤器安全过滤器链中的其他过滤器。最终它会抛出一个 AuthenticationException 的实例,它会被 ExceptionTranslationFilter 捕获并且 ExceptionTranslationFilter 会调用你注册的 AuthenticationEntryPoint.

但是当您通过基本授权 header 时,BasicAuthenticationFilter 本身会处理身份验证令牌。如果传递的凭据无效,则 BasicAuthenticationFilter would catch the exception itself 并调用 BasicAuthenticationEntryPoint,而不是您的 AuthenticationEntryPoint:

catch (AuthenticationException failed) {
    SecurityContextHolder.clearContext();

    if (debug) {
        this.logger.debug("Authentication request for failed: " + failed);
    }

    this.rememberMeServices.loginFail(request, response);

    onUnsuccessfulAuthentication(request, response, failed);

    if (this.ignoreFailure) {
        chain.doFilter(request, response);
    }
    else {
        this.authenticationEntryPoint.commence(request, response, failed);
    }

    return;
}

正如 AliDehghani 指出的那样,这是因为 BasicAuthenticationFilter 使用了 BasicApplicationEntryPoint 而不管 AuthorizationServerSecurityConfigurer 中声明的 ApplicationEntryPoint。为了让 BasicAuthenticationFilter 使用我的 CustomApplicationEntryPoint 我需要创建一个新的 CustomBasicAuthenticationFilter 并向构造函数添加 @Autowire 注释:

@Component
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {

    @Autowired
    public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager,
                                     AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }
}

然后将其添加到 AuthorizationServerConfigurer

的配置方法之一
@Override
public void configure(AuthorizationServerSecurityConfigurer authorizationServerSecurityConfigurer) throws Exception {
    authorizationServerSecurityConfigurer
            .authenticationEntryPoint(authenticationEntryPoint)
            .addTokenEndpointAuthenticationFilter(customBasicAuthenticationFilter);
}

现在应用程序使用我的 CustomBasicAuthenticationFilter - 在功能上等同于 BasicAuthenticationFilter。但是,它现在包含在构造期间声明的 AuthenticationEntryPoint bean - 这是我的 CustomAuthenticationEntryPoint.