View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.core5.http.io.support;
28  
29  import java.io.IOException;
30  
31  import org.apache.hc.core5.annotation.Contract;
32  import org.apache.hc.core5.annotation.ThreadingBehavior;
33  import org.apache.hc.core5.http.ClassicHttpRequest;
34  import org.apache.hc.core5.http.ClassicHttpResponse;
35  import org.apache.hc.core5.http.Header;
36  import org.apache.hc.core5.http.HeaderElements;
37  import org.apache.hc.core5.http.HttpEntity;
38  import org.apache.hc.core5.http.HttpException;
39  import org.apache.hc.core5.http.HttpHeaders;
40  import org.apache.hc.core5.http.HttpResponse;
41  import org.apache.hc.core5.http.HttpStatus;
42  import org.apache.hc.core5.http.io.HttpFilterChain;
43  import org.apache.hc.core5.http.io.HttpFilterHandler;
44  import org.apache.hc.core5.http.io.entity.EntityUtils;
45  import org.apache.hc.core5.http.io.entity.StringEntity;
46  import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
47  import org.apache.hc.core5.http.protocol.HttpContext;
48  import org.apache.hc.core5.net.URIAuthority;
49  
50  /**
51   * Abstract HTTP request filter that implements standard HTTP authentication handshake.
52   *
53   * @param <T> authorization token representation.
54   *
55   * @since 5.0
56   */
57  @Contract(threading = ThreadingBehavior.STATELESS)
58  public abstract class AbstractHttpServerAuthFilter<T> implements HttpFilterHandler {
59  
60      private final boolean respondImmediately;
61  
62      protected AbstractHttpServerAuthFilter(final boolean respondImmediately) {
63          this.respondImmediately = respondImmediately;
64      }
65  
66      /**
67       * Parses authorization header value into an authentication token sent by the client
68       * as a response to an authentication challenge.
69       *
70       * @param authorizationValue the authorization header value.
71       * @param context the actual execution context.
72       * @return authorization token
73       */
74      protected abstract T parseChallengeResponse(String authorizationValue, HttpContext context) throws HttpException;
75  
76      /**
77       * Authenticates the client using the authentication token sent by the client
78       * as a response to an authentication challenge.
79       *
80       * @param challengeResponse the authentication token sent by the client
81       *                          as a response to an authentication challenge.
82       * @param authority the URI authority.
83       * @param requestUri the request URI.
84       * @param context the actual execution context.
85       * @return {@code true} if the client could be successfully authenticated {@code false} otherwise.
86       */
87      protected abstract boolean authenticate(T challengeResponse, URIAuthority authority, String requestUri, HttpContext context);
88  
89      /**
90       * Generates an authentication challenge in case of unsuccessful authentication.
91       *
92       * @param challengeResponse the authentication token sent by the client
93       *                          as a response to an authentication challenge
94       *                          or {@code null} if the client has not sent any.
95       * @param authority the URI authority.
96       * @param requestUri the request URI.
97       * @param context the actual execution context.
98       * @return an authorization challenge value.
99       */
100     protected abstract String generateChallenge(T challengeResponse, URIAuthority authority, String requestUri, HttpContext context);
101 
102     /**
103      * Generates response body for UNAUTHORIZED response.
104      *
105      * @param unauthorized the response to return as a result of authentication failure.
106      * @return the response content entity.
107      */
108     protected HttpEntity generateResponseContent(final HttpResponse unauthorized) {
109         return new StringEntity("Unauthorized");
110     }
111 
112     @Override
113     public final void handle(
114             final ClassicHttpRequest request,
115             final HttpFilterChain.ResponseTrigger responseTrigger,
116             final HttpContext context,
117             final HttpFilterChain chain) throws HttpException, IOException {
118         final Header h = request.getFirstHeader(HttpHeaders.AUTHORIZATION);
119         final T challengeResponse = h != null ? parseChallengeResponse(h.getValue(), context) : null;
120 
121         final URIAuthority authority = request.getAuthority();
122         final String requestUri = request.getRequestUri();
123 
124         final boolean authenticated = authenticate(challengeResponse, authority, requestUri, context);
125         final Header expect = request.getFirstHeader(HttpHeaders.EXPECT);
126         final boolean expectContinue = expect != null && HeaderElements.CONTINUE.equalsIgnoreCase(expect.getValue());
127 
128         if (authenticated) {
129             if (expectContinue) {
130                 responseTrigger.sendInformation(new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE));
131             }
132             chain.proceed(request, responseTrigger, context);
133         } else {
134             final ClassicHttpResponse unauthorized = new BasicClassicHttpResponse(HttpStatus.SC_UNAUTHORIZED);
135             unauthorized.addHeader(HttpHeaders.WWW_AUTHENTICATE, generateChallenge(challengeResponse, authority, requestUri, context));
136             final HttpEntity responseContent = generateResponseContent(unauthorized);
137             unauthorized.setEntity(responseContent);
138             if (respondImmediately || expectContinue || request.getEntity() == null) {
139                 // Respond immediately
140                 responseTrigger.submitResponse(unauthorized);
141                 // Consume request body later
142                 EntityUtils.consume(request.getEntity());
143             } else {
144                 // Consume request body first
145                 EntityUtils.consume(request.getEntity());
146                 // Respond later
147                 responseTrigger.submitResponse(unauthorized);
148             }
149         }
150     }
151 }