001package com.nimbusds.common.oauth2; 002 003 004import java.io.IOException; 005import javax.servlet.http.HttpServletRequest; 006import javax.servlet.http.HttpServletResponse; 007import javax.ws.rs.WebApplicationException; 008import javax.ws.rs.core.MediaType; 009import javax.ws.rs.core.Response; 010 011import org.apache.commons.lang3.StringUtils; 012 013import net.minidev.json.JSONObject; 014 015import com.nimbusds.oauth2.sdk.ParseException; 016import com.nimbusds.oauth2.sdk.token.BearerAccessToken; 017import com.nimbusds.oauth2.sdk.token.BearerTokenError; 018 019 020/** 021 * Basic access token validator. Supports servlet-based and JAX-RS based web 022 * applications. 023 */ 024public class BasicAccessTokenValidator { 025 026 027 /** 028 * Error response: Missing OAuth 2.0 Bearer access token. 029 */ 030 public static final ErrorResponse MISSING_BEARER_TOKEN; 031 032 033 /** 034 * Error response: Invalid OAuth 2.0 Bearer access token. 035 */ 036 public static final ErrorResponse INVALID_BEARER_TOKEN; 037 038 039 /** 040 * Error response: Web API disabled. 041 */ 042 public static final ErrorResponse WEB_API_DISABLED; 043 044 045 static { 046 JSONObject o = new JSONObject(); 047 o.put("error", "missing_token"); 048 o.put("error_description", "Unauthorized: Missing Bearer access token"); 049 MISSING_BEARER_TOKEN = new ErrorResponse( 050 BearerTokenError.MISSING_TOKEN.getHTTPStatusCode(), 051 BearerTokenError.MISSING_TOKEN.toWWWAuthenticateHeader(), 052 o.toJSONString()); 053 054 o = new JSONObject(); 055 o.put("error", BearerTokenError.INVALID_TOKEN.getCode()); 056 o.put("error_description", "Unauthorized: Invalid Bearer access token"); 057 INVALID_BEARER_TOKEN = new ErrorResponse( 058 BearerTokenError.INVALID_TOKEN.getHTTPStatusCode(), 059 BearerTokenError.INVALID_TOKEN.toWWWAuthenticateHeader(), 060 o.toJSONString()); 061 062 o = new JSONObject(); 063 o.put("error", "web_api_disabled"); 064 o.put("error_description", "Forbidden: Web API disabled"); 065 WEB_API_DISABLED = new ErrorResponse(403, null, o.toJSONString()); 066 } 067 068 069 /** 070 * Bearer token error response. 071 */ 072 public static class ErrorResponse { 073 074 075 /** 076 * The HTTP status code. 077 */ 078 private final int statusCode; 079 080 081 /** 082 * Optional HTTP response {@code WWW-Authenticate} header. 083 */ 084 private final String wwwAuthHeader; 085 086 087 /** 088 * The HTTP body. 089 */ 090 private final String body; 091 092 093 /** 094 * Creates a new bearer token error response. 095 * 096 * @param statusCode The HTTP status code. 097 * @param wwwAuthHeader The HTTP response 098 * {@code WWW-Authenticate} header, 099 * {@code null} if none. 100 * @param body The HTTP body (application/json). 101 */ 102 public ErrorResponse(final int statusCode, 103 final String wwwAuthHeader, 104 final String body) { 105 106 this.statusCode = statusCode; 107 this.wwwAuthHeader = wwwAuthHeader; 108 this.body = body; 109 } 110 111 112 /** 113 * Returns a web application exception for this error response. 114 * 115 * @return The web application exception. 116 */ 117 public WebApplicationException toWebAppException() { 118 119 Response.ResponseBuilder builder = Response.status(statusCode); 120 121 if (wwwAuthHeader != null) { 122 builder.header("WWW-Authenticate", wwwAuthHeader); 123 } 124 125 return new WebApplicationException( 126 builder.entity(body).type(MediaType.APPLICATION_JSON).build()); 127 } 128 129 130 /** 131 * Applies this error response to the specified HTTP servlet 132 * response. 133 * 134 * @param servletResponse The HTTP servlet response. Must not 135 * be {@code null}. 136 * 137 * @throws IOException If the error response couldn't be 138 * written. 139 */ 140 public void apply(final HttpServletResponse servletResponse) 141 throws IOException { 142 143 servletResponse.setStatus(statusCode); 144 145 if (wwwAuthHeader != null) { 146 servletResponse.setHeader("WWW-Authenticate", wwwAuthHeader); 147 } 148 149 if (body != null) { 150 servletResponse.setContentType("application/json"); 151 servletResponse.getWriter().print(body); 152 } 153 } 154 } 155 156 157 /** 158 * The access token, {@code null} access to the web API is disabled. 159 */ 160 private final BearerAccessToken accessToken; 161 162 163 /** 164 * Creates a new basic access token validator. 165 * 166 * @param accessToken The Bearer access token. If {@code null} access 167 * to the web API will be disabled. 168 */ 169 public BasicAccessTokenValidator(final BearerAccessToken accessToken) { 170 171 this.accessToken = accessToken; 172 } 173 174 175 /** 176 * Returns the Bearer access token. 177 * 178 * @return The Bearer access token, {@code null} access to the web API 179 * is disabled. 180 */ 181 public BearerAccessToken getAccessToken() { 182 183 return accessToken; 184 } 185 186 187 /** 188 * Validates a bearer access token passed in the specified HTTP 189 * Authorization header value. 190 * 191 * @param authzHeader The HTTP Authorization header value, {@code null} 192 * if not specified. 193 * 194 * @throws WebApplicationException If the header value is {@code null}, 195 * the web API is disabled, or the 196 * Bearer access token is missing or 197 * invalid. 198 */ 199 public void validateBearerAccessToken(final String authzHeader) 200 throws WebApplicationException { 201 202 if (StringUtils.isBlank(authzHeader)) { 203 throw MISSING_BEARER_TOKEN.toWebAppException(); 204 } 205 206 BearerAccessToken receivedToken; 207 208 try { 209 receivedToken = BearerAccessToken.parse(authzHeader); 210 211 } catch (ParseException e) { 212 throw MISSING_BEARER_TOKEN.toWebAppException(); 213 } 214 215 // Web API disabled? 216 if (accessToken == null) { 217 throw WEB_API_DISABLED.toWebAppException(); 218 } 219 220 // Check receivedToken 221 if (! receivedToken.equals(accessToken)) { 222 throw INVALID_BEARER_TOKEN.toWebAppException(); 223 } 224 } 225 226 227 /** 228 * Validates a bearer access token passed in the specified HTTP servlet 229 * request. 230 * 231 * @param servletRequest The HTTP servlet request. Must not be 232 * {@code null}. 233 * @param servletResponse The HTTP servlet response. Must not be 234 * {@code null}. 235 * 236 * @return {@code true} if the bearer access token was successfully 237 * validated, {@code false}. 238 * 239 * @throws IOException If the response couldn't be written. 240 */ 241 public boolean validateBearerAccessToken(final HttpServletRequest servletRequest, 242 final HttpServletResponse servletResponse) 243 throws IOException { 244 245 String authzHeader = servletRequest.getHeader("Authorization"); 246 247 if (StringUtils.isBlank(authzHeader)) { 248 MISSING_BEARER_TOKEN.apply(servletResponse); 249 return false; 250 } 251 252 BearerAccessToken receivedToken; 253 254 try { 255 receivedToken = BearerAccessToken.parse(authzHeader); 256 257 } catch (ParseException e) { 258 MISSING_BEARER_TOKEN.apply(servletResponse); 259 return false; 260 } 261 262 // Web API disabled? 263 if (accessToken == null) { 264 WEB_API_DISABLED.apply(servletResponse); 265 return false; 266 } 267 268 // Check receivedToken 269 if (! receivedToken.equals(accessToken)) { 270 INVALID_BEARER_TOKEN.apply(servletResponse); 271 return false; 272 } 273 274 return true; // success 275 } 276}