自动自定义验证

gum*_*ins 11 java authentication apache-httpclient-4.x

我想使用Apache HttpClient 4+将经过身份验证的请求发送到HTTP服务器(实际上,我需要针对不同的服务器实现)并且仅在需要时,当auth令牌不存在时自动进行身份验证(或重新身份验证)或它死了.

为了进行身份验证,我需要使用包含用户凭据的JSON发送POST请求.

如果cookie中没有提供认证令牌,则一个服务器返回状态代码401,另一个服务器返回响应主体中带有AUTH_REQUIRED文本的500.

HttpClient通过CredentialsProvider适当设置Credentials,尝试实现自己AuthScheme并注册它并取消注册其余标准版本,玩了很多不同的版本.

我也试着自己设定AuthenticationHandler.当isAuthenticationRequested调用时,我正在分析HttpResponse哪个作为方法参数传递,并通过分析状态代码和响应体来决定返回什么.我希望this(isAuthenticationRequested() == true)强制客户端通过调用AuthScheme.authenticate(我的AuthScheme实现返回AuthenticationHandler.selectScheme)进行身份验证,但AuthScheme.authenticate我可以看到而不是调用AuthenticationHandler.getChallenges.我真的不知道我应该通过这种方法返回什么,因此我只是回来了new HashMap<>().

这是我在结果中的调试输出

DEBUG org.apache.http.impl.client.DefaultHttpClient - Authentication required
DEBUG org.apache.http.impl.client.DefaultHttpClient - example.com requested authentication
DEBUG com.test.httpclient.MyAuthenticationHandler - MyAuthenticationHandler.getChallenges()
DEBUG org.apache.http.impl.client.DefaultHttpClient - Response contains no authentication challenges
Run Code Online (Sandbox Code Playgroud)

接下来我该怎么办?我正朝着正确的方向前进吗?

UPDATE

我几乎达到了我所需要的水平.不幸的是,我无法提供完全正常工作的项目源,因为我无法提供对我的服务器的公共访问.这是我的简化代码示例:

MyAuthScheme.java

public class MyAuthScheme implements ContextAwareAuthScheme {

    public static final String NAME = "myscheme";

    @Override
    public Header authenticate(Credentials credentials, 
                           HttpRequest request, 
                           HttpContext context) throws AuthenticationException {

        HttpClientContext clientContext = ((HttpClientContext) context);        
        String name = clientContext.getTargetAuthState().getState().name();

        // Hack #1: 
        // I've come to this check. I don't like it, but it allows to authenticate
        // first request and don't repeat authentication procedure for further 
        // requests
        if(name.equals("CHALLENGED") && clientContext.getResponse() == null) {

            //
            // auth procedure must be here but is omitted in current example
            //

            // Hack #2: first request won't be present with auth token cookie set via cookie store 
            request.setHeader(new BasicHeader("Cookie", "MYAUTHTOKEN=bru99rshi7r5ucstkj1wei4fshsd"));

            // this works for second and subsequent requests
            BasicClientCookie authTokenCookie = new BasicClientCookie("MYAUTHTOKEN", "bru99rshi7r5ucstkj1wei4fshsd");
            authTokenCookie.setDomain("example.com");
            authTokenCookie.setPath("/");

            BasicCookieStore cookieStore = (BasicCookieStore) clientContext.getCookieStore();
            cookieStore.addCookie(authTokenCookie);
        }

        // I can't return cookie header here, otherwise it will clear 
        // other cookies, right?
        return null;
    }

    @Override
    public void processChallenge(Header header) throws MalformedChallengeException {

    }

    @Override
    public String getSchemeName() {
        return NAME;
    }

    @Override
    public String getParameter(String name) {
        return null;
    }

    @Override
    public String getRealm() {
        return null;
    }

    @Override
    public boolean isConnectionBased() {
        return false;
    }

    @Override
    public boolean isComplete() {
        return true;
    }

    @Override
    public Header authenticate(Credentials credentials, 
                           HttpRequest request) throws AuthenticationException {        
        return null;
    }

}
Run Code Online (Sandbox Code Playgroud)

MyAuthStrategy.java

public class MyAuthStrategy implements AuthenticationStrategy {

    @Override
    public boolean isAuthenticationRequested(HttpHost authhost, 
                                         HttpResponse response, 
                                         HttpContext context) {

        return response.getStatusLine().getStatusCode() == 401;
    }

    @Override
    public Map<String, Header> getChallenges(HttpHost authhost, 
                                         HttpResponse response, 
                                         HttpContext context) throws MalformedChallengeException {

        Map<String, Header> challenges = new HashMap<>();
        challenges.put(MyAuthScheme.NAME, new BasicHeader(
                "WWW-Authentication", 
                "Myscheme realm=\"My SOAP authentication\""));

        return challenges;
    }

    @Override
    public Queue<AuthOption> select(Map<String, Header> challenges, 
                                HttpHost authhost, 
                                HttpResponse response, 
                                HttpContext context) throws MalformedChallengeException {

        Credentials credentials = ((HttpClientContext) context)
                .getCredentialsProvider()
                .getCredentials(new AuthScope(authhost));

        Queue<AuthOption> authOptions = new LinkedList<>();
        authOptions.add(new AuthOption(new MyAuthScheme(), credentials));

        return authOptions;
    }

    @Override
    public void authSucceeded(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}

    @Override
    public void authFailed(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}

}
Run Code Online (Sandbox Code Playgroud)

MyApp.java

public class MyApp {

    public static void main(String[] args) throws IOException {

        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        Credentials credentials = new UsernamePasswordCredentials("user@example.com", "secret");
        credsProvider.setCredentials(AuthScope.ANY, credentials);

        HttpClientContext context = HttpClientContext.create();
        context.setCookieStore(new BasicCookieStore());
        context.setCredentialsProvider(credsProvider);

        CloseableHttpClient client = HttpClientBuilder.create()
                // my server requires this header otherwise it returns response with code 500
                .setDefaultHeaders(Collections.singleton(new BasicHeader("x-requested-with", "XMLHttpRequest"))) 
                .setTargetAuthenticationStrategy(new MyAuthStrategy())
                .build();

        String url = "https://example.com/some/resource";
        String url2 = "https://example.com/another/resource";

        // ======= REQUEST 1 =======

        HttpGet request = new HttpGet(url);
        HttpResponse response = client.execute(request, context);
        String responseText = EntityUtils.toString(response.getEntity());
        request.reset();

        // ======= REQUEST 2 =======

        HttpGet request2 = new HttpGet(url);
        HttpResponse response2 = client.execute(request2, context);
        String responseText2 = EntityUtils.toString(response2.getEntity());
        request2.reset();

        // ======= REQUEST 3 =======

        HttpGet request3 = new HttpGet(url2);
        HttpResponse response3 = client.execute(request3, context);
        String responseText3 = EntityUtils.toString(response3.getEntity());
        request3.reset();

        client.close();

    }

}
Run Code Online (Sandbox Code Playgroud)

版本

httpcore:4.4.6
httpclient:4.5.3

可能这不是最好的代码,但至少它是有效的.

请看MyAuthScheme.authenticate()方法中的评论.

gum*_*ins 2

这对我来说与 Apache HttpClient 4.2 的预期一样

笔记。虽然是用httpclient 4.5编译执行的,但是执行时陷入了永远循环。

MyAuthScheme.java

public class MyAuthScheme implements ContextAwareAuthScheme {

    public static final String NAME = "myscheme";
    private static final String REQUEST_BODY = "{\"login\":\"%s\",\"password\":\"%s\"}";

    private final URI loginUri;

    public MyAuthScheme(URI uri) {
        loginUri = uri;
    }

    @Override
    public Header authenticate(Credentials credentials, 
                           HttpRequest request, 
                           HttpContext context) throws AuthenticationException {

        BasicCookieStore cookieStore = (BasicCookieStore) context.getAttribute(ClientContext.COOKIE_STORE);

        DefaultHttpClient client = new DefaultHttpClient();

        // authentication cookie is set automatically when 
        // login response arrived
        client.setCookieStore(cookieStore);

        HttpPost loginRequest = new HttpPost(loginUri);
        String requestBody = String.format(
                REQUEST_BODY, 
                credentials.getUserPrincipal().getName(), 
                credentials.getPassword());
        loginRequest.setHeader("Content-Type", "application/json");

        try {
            loginRequest.setEntity(new StringEntity(requestBody));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        try {
            HttpResponse response = client.execute(loginRequest);
            int code = response.getStatusLine().getStatusCode();
            EntityUtils.consume(response.getEntity());
            if(code != 200) {
                throw new IllegalStateException("Authentication problem");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            loginRequest.reset();            
        }

        return null;
    }

    @Override
    public void processChallenge(Header header) throws MalformedChallengeException {}

    @Override
    public String getSchemeName() {
        return NAME;
    }

    @Override
    public String getParameter(String name) {
        return null;
    }

    @Override
    public String getRealm() {
        return null;
    }

    @Override
    public boolean isConnectionBased() {
        return false;
    }

    @Override
    public boolean isComplete() {
        return false;
    }

    @Override
    public Header authenticate(Credentials credentials, 
                           HttpRequest request) throws AuthenticationException {
        // not implemented
        return null;
    }

}
Run Code Online (Sandbox Code Playgroud)

MyAuthSchemeFactory.java

public class MyAuthSchemeFactory implements AuthSchemeFactory {

    private final URI loginUri;

    public MyAuthSchemeFactory(URI uri) {
        this.loginUri = uri;
    }

    @Override
    public AuthScheme newInstance(HttpParams params) {
        return new MyAuthScheme(loginUri);
    }

}
Run Code Online (Sandbox Code Playgroud)

MyAuthStrategy.java

public class MyAuthStrategy implements AuthenticationStrategy {

    @Override
    public boolean isAuthenticationRequested(HttpHost authhost, 
                                             HttpResponse response, 
                                             HttpContext context) {

        return response.getStatusLine().getStatusCode() == 401;
    }

    @Override
    public Map<String, Header> getChallenges(HttpHost authhost, 
                                             HttpResponse response, 
                                             HttpContext context) throws MalformedChallengeException {

        Map<String, Header> challenges = new HashMap<>();
        challenges.put("myscheme", new BasicHeader("WWW-Authenticate", "myscheme"));

        return challenges;
    }

    @Override
    public Queue<AuthOption> select(Map<String, Header> challenges, 
                                    HttpHost authhost, 
                                    HttpResponse response, 
                                    HttpContext context) throws MalformedChallengeException {

        AuthSchemeRegistry registry = (AuthSchemeRegistry) context.getAttribute(ClientContext.AUTHSCHEME_REGISTRY);
        AuthScheme authScheme = registry.getAuthScheme(MyAuthScheme.NAME, new BasicHttpParams());
        CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
        Credentials credentials = credsProvider.getCredentials(new AuthScope(authhost));

        Queue<AuthOption> options = new LinkedList<>();
        options.add(new AuthOption(authScheme, credentials));

        return options;
    }

    @Override
    public void authSucceeded(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}

    @Override
    public void authFailed(HttpHost authhost, AuthScheme authScheme, HttpContext context) {}

}
Run Code Online (Sandbox Code Playgroud)

App.java

public class App {

    public static void main(String[] args) throws IOException, URISyntaxException {

        URI loginUri = new URI("https://example.com/api/v3/users/login");

        AuthSchemeRegistry schemeRegistry = new AuthSchemeRegistry();
        schemeRegistry.register(MyAuthScheme.NAME, new MyAuthSchemeFactory(loginUri));

        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
                new AuthScope("example.com", 8065), 
                new UsernamePasswordCredentials("user1@example.com", "secret"));

        DefaultHttpClient client = new DefaultHttpClient();
        client.setCredentialsProvider(credentialsProvider);
        client.setTargetAuthenticationStrategy(new MyAuthStrategy());
        client.setAuthSchemes(schemeRegistry);
        client.setCookieStore(new BasicCookieStore());


        String getResourcesUrl = "https://example.com:8065/api/v3/myresources/";

        HttpGet getResourcesRequest = new HttpGet(getResourcesUrl);
        getResourcesRequest.setHeader("x-requested-with", "XMLHttpRequest");

        try {
            HttpResponse response = client.execute(getResourcesRequest);
            // consume response
        } finally {
            getResourcesRequest.reset();
        }   

        // further requests won't call MyAuthScheme.authenticate()

        HttpGet getResourcesRequest2 = new HttpGet(getResourcesUrl);
        getResourcesRequest2.setHeader("x-requested-with", "XMLHttpRequest");

        try {
            HttpResponse response2 = client.execute(getResourcesRequest);
            // consume response
        } finally {
            getResourcesRequest2.reset();
        }   

        HttpGet getResourcesRequest3 = new HttpGet(getResourcesUrl);
        getResourcesRequest3.setHeader("x-requested-with", "XMLHttpRequest");

        try {
            HttpResponse response3 = client.execute(getResourcesRequest);
            // consume response
        } finally {
            getResourcesRequest3.reset();
        }   

    }

}
Run Code Online (Sandbox Code Playgroud)