View Javadoc

1   package com.atlassian.asap.core.server.filter;
2   
3   import com.atlassian.asap.api.Jwt;
4   import com.atlassian.asap.api.exception.AuthenticationFailedException;
5   import com.atlassian.asap.api.exception.TransientAuthenticationFailedException;
6   import com.atlassian.asap.api.server.http.RequestAuthenticator;
7   import com.atlassian.asap.core.JwtConstants;
8   import com.google.common.base.Preconditions;
9   import org.apache.commons.lang3.StringUtils;
10  import org.apache.http.HttpHeaders;
11  import org.apache.http.HttpStatus;
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  import javax.servlet.Filter;
16  import javax.servlet.FilterChain;
17  import javax.servlet.FilterConfig;
18  import javax.servlet.ServletException;
19  import javax.servlet.ServletRequest;
20  import javax.servlet.ServletResponse;
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  import java.io.IOException;
24  
25  /**
26   * A servlet filter that accepts or rejects requests based on the authenticity and validity of the JWT access token.
27   * It only performs authentication, but it does not authorize the request.
28   */
29  public abstract class AbstractRequestAuthenticationFilter implements Filter {
30      /**
31       * Upon successful authentication, the authentic {@link Jwt} token is saved in the session under this key,
32       * so other filters down the chain can access it, e.g., {@link AbstractRequestAuthorizationFilter}.
33       */
34      public static final String AUTHENTIC_JWT_REQUEST_ATTRIBUTE = "asap.authentic.jwt";
35  
36      private static final Logger logger = LoggerFactory.getLogger(AbstractRequestAuthenticationFilter.class);
37  
38      private RequestAuthenticator requestAuthenticator;
39  
40      @Override
41      public void init(FilterConfig filterConfig) throws ServletException {
42          requestAuthenticator = getRequestAuthenticator(filterConfig);
43      }
44  
45      @Override
46      public final void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
47              throws IOException, ServletException {
48          Preconditions.checkState(requestAuthenticator != null, "Filter has not been initialized");
49  
50          // will fail if the request is not an HTTP request
51          HttpServletRequest httpRequest = (HttpServletRequest) request;
52          HttpServletResponse httpResponse = (HttpServletResponse) response;
53  
54          String authorizationHeader = httpRequest.getHeader(HttpHeaders.AUTHORIZATION);
55          if (StringUtils.isBlank(authorizationHeader)) {
56              // log at debug level because this is caused by invalid input
57              logger.debug("Request rejected because JWT token cannot be found");
58              onAuthenticationFailure(httpRequest, httpResponse, chain);
59          } else {
60              try {
61                  Jwt authenticJwt = requestAuthenticator.authenticateRequest(authorizationHeader);
62                  logger.trace("Accepting authentic token with identifier {}", authenticJwt.getClaims().getJwtId());
63                  saveToken(request, authenticJwt);
64                  onAuthenticationSuccess(authenticJwt, httpRequest, httpResponse, chain);
65              } catch (TransientAuthenticationFailedException e) {
66                  // log at debug level because this can be caused by invalid input
67                  logger.debug("Request rejected because JWT token could not be verified at this time", e);
68                  onAuthenticationError(httpRequest, httpResponse, chain, e);
69              } catch (AuthenticationFailedException e) {
70                  // log at debug level because this can be caused by invalid input
71                  logger.debug("Request rejected because JWT token cannot be verified", e);
72                  onAuthenticationFailure(httpRequest, httpResponse, chain);
73              }
74          }
75      }
76  
77      /**
78       * Save the authentic token in the request to make it available for other filters down the chain. Subclasses
79       * can override/decorate this method to save the token somewhere else.
80       */
81      protected void saveToken(ServletRequest request, Jwt authenticJwt) {
82          request.setAttribute(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, authenticJwt);
83      }
84  
85      /**
86       * Handles the failure of request authentication (e.g., invalid token). The default implementation
87       * is to return a 401. Override and decorate as necessary.
88       */
89      protected void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
90              throws IOException, ServletException {
91          response.sendError(HttpStatus.SC_UNAUTHORIZED);
92          response.setHeader(HttpHeaders.WWW_AUTHENTICATE, JwtConstants.BEARER_AUTHENTICATION_SCHEME);
93      }
94  
95      /**
96       * Handles an unexpected error during request authentication (such as a failure when retrieving the required public
97       * key).  The default implementation is to return a 503. Override and decorate as necessary.
98       */
99      protected void onAuthenticationError(HttpServletRequest request, HttpServletResponse response, FilterChain chain, TransientAuthenticationFailedException e)
100             throws IOException, ServletException {
101         // log the error to assist with troubleshooting
102         logger.error("An error occurred while authenticating this request", e);
103         response.sendError(HttpStatus.SC_SERVICE_UNAVAILABLE);
104     }
105 
106     /**
107      * Handles the acceptance of an authentic request. The default implementation is to pass the request down the
108      * filter chain. Override and decorate this as necessary. For instance, some applications may want to
109      * store the JWT token or parts of it. Another filter may implement authorization, see for example
110      * {@link AbstractRequestAuthorizationFilter}.
111      */
112     protected void onAuthenticationSuccess(Jwt authenticJwt, HttpServletRequest request, HttpServletResponse response, FilterChain chain)
113             throws IOException, ServletException {
114         chain.doFilter(request, response);
115     }
116 
117     @Override
118     public void destroy() {
119         // nothing to do
120     }
121 
122     /**
123      * Override this to supply your RequestAuthenticator.
124      */
125     protected abstract RequestAuthenticator getRequestAuthenticator(FilterConfig filterConfig);
126 }