001package com.nimbusds.oauth2.sdk.token; 002 003 004import java.util.Map; 005 006import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 007import com.nimbusds.oauth2.sdk.util.ContentTypeUtils; 008import net.jcip.annotations.Immutable; 009 010import net.minidev.json.JSONObject; 011 012import com.nimbusds.oauth2.sdk.ParseException; 013import com.nimbusds.oauth2.sdk.Scope; 014import com.nimbusds.oauth2.sdk.http.HTTPRequest; 015import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 016import org.apache.commons.lang3.StringUtils; 017 018 019/** 020 * Bearer access token. 021 * 022 * <p>Example bearer access token serialised to JSON: 023 * 024 * <pre> 025 * { 026 * "access_token" : "2YotnFZFEjr1zCsicMWpAA", 027 * "token_type" : "bearer", 028 * "expires_in" : 3600, 029 * "scope" : "read write" 030 * } 031 * </pre> 032 * 033 * <p>The above example token serialised to a HTTP Authorization header: 034 * 035 * <pre> 036 * Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA 037 * </pre> 038 * 039 * <p>Related specifications: 040 * 041 * <ul> 042 * <li>OAuth 2.0 (RFC 6749), sections 1.4 and 5.1. 043 * <li>OAuth 2.0 Bearer Token Usage (RFC 6750). 044 * </ul> 045 */ 046@Immutable 047public final class BearerAccessToken extends AccessToken { 048 049 050 /** 051 * Creates a new minimal bearer access token with a randomly generated 052 * 256-bit (32-byte) value, Base64URL-encoded. The optional lifetime 053 * and scope are left undefined. 054 */ 055 public BearerAccessToken() { 056 057 this(32); 058 } 059 060 061 /** 062 * Creates a new minimal bearer access token with a randomly generated 063 * value of the specified byte length, Base64URL-encoded. The optional 064 * lifetime and scope are left undefined. 065 * 066 * @param byteLength The byte length of the value to generate. Must be 067 * greater than one. 068 */ 069 public BearerAccessToken(final int byteLength) { 070 071 this(byteLength, 0l, null); 072 } 073 074 075 /** 076 * Creates a new bearer access token with a randomly generated 256-bit 077 * (32-byte) value, Base64URL-encoded. 078 * 079 * @param lifetime The lifetime in seconds, 0 if not specified. 080 * @param scope The scope, {@code null} if not specified. 081 */ 082 public BearerAccessToken(final long lifetime, final Scope scope) { 083 084 this(32, lifetime, scope); 085 } 086 087 088 /** 089 * Creates a new bearer access token with a randomly generated value of 090 * the specified byte length, Base64URL-encoded. 091 * 092 * @param byteLength The byte length of the value to generate. Must be 093 * greater than one. 094 * @param lifetime The lifetime in seconds, 0 if not specified. 095 * @param scope The scope, {@code null} if not specified. 096 */ 097 public BearerAccessToken(final int byteLength, final long lifetime, final Scope scope) { 098 099 super(AccessTokenType.BEARER, byteLength, lifetime, scope); 100 } 101 102 103 /** 104 * Creates a new minimal bearer access token with the specified value. 105 * The optional lifetime and scope are left undefined. 106 * 107 * @param value The access token value. Must not be {@code null} or 108 * empty string. 109 */ 110 public BearerAccessToken(final String value) { 111 112 this(value, 0l, null); 113 } 114 115 116 /** 117 * Creates a new bearer access token with the specified value and 118 * optional lifetime and scope. 119 * 120 * @param value The access token value. Must not be {@code null} or 121 * empty string. 122 * @param lifetime The lifetime in seconds, 0 if not specified. 123 * @param scope The scope, {@code null} if not specified. 124 */ 125 public BearerAccessToken(final String value, final long lifetime, final Scope scope) { 126 127 super(AccessTokenType.BEARER, value, lifetime, scope); 128 } 129 130 131 /** 132 * Returns the HTTP Authorization header value for this bearer access 133 * token. 134 * 135 * <p>Example: 136 * 137 * <pre> 138 * Authorization: Bearer eyJhbGciOiJIUzI1NiJ9 139 * </pre> 140 * 141 * @return The HTTP Authorization header. 142 */ 143 @Override 144 public String toAuthorizationHeader(){ 145 146 return "Bearer " + getValue(); 147 } 148 149 150 @Override 151 public boolean equals(final Object object) { 152 153 return object instanceof BearerAccessToken && 154 this.toString().equals(object.toString()); 155 } 156 157 158 /** 159 * Parses a bearer access token from a JSON object access token 160 * response. 161 * 162 * @param jsonObject The JSON object to parse. Must not be 163 * {@code null}. 164 * 165 * @return The bearer access token. 166 * 167 * @throws ParseException If the JSON object couldn't be parsed to a 168 * bearer access token. 169 */ 170 public static BearerAccessToken parse(final JSONObject jsonObject) 171 throws ParseException { 172 173 // Parse and verify type 174 AccessTokenType tokenType = new AccessTokenType(JSONObjectUtils.getString(jsonObject, "token_type")); 175 176 if (! tokenType.equals(AccessTokenType.BEARER)) 177 throw new ParseException("Token type must be \"Bearer\""); 178 179 180 // Parse value 181 String accessTokenValue = JSONObjectUtils.getString(jsonObject, "access_token"); 182 183 184 // Parse lifetime 185 long lifetime = 0; 186 187 if (jsonObject.containsKey("expires_in")) { 188 189 // Lifetime can be a JSON number or string 190 191 if (jsonObject.get("expires_in") instanceof Number) { 192 193 lifetime = JSONObjectUtils.getLong(jsonObject, "expires_in"); 194 } 195 else { 196 String lifetimeStr = JSONObjectUtils.getString(jsonObject, "expires_in"); 197 198 try { 199 lifetime = new Long(lifetimeStr); 200 201 } catch (NumberFormatException e) { 202 203 throw new ParseException("Invalid \"expires_in\" parameter, must be integer"); 204 } 205 } 206 } 207 208 209 // Parse scope 210 Scope scope = null; 211 212 if (jsonObject.containsKey("scope")) 213 scope = Scope.parse(JSONObjectUtils.getString(jsonObject, "scope")); 214 215 216 return new BearerAccessToken(accessTokenValue, lifetime, scope); 217 } 218 219 220 /** 221 * Parses an HTTP Authorization header for a bearer access token. 222 * 223 * @param header The HTTP Authorization header value to parse. May be 224 * {@code null} if the header is missing, in which case 225 * an exception will be thrown. 226 * 227 * @return The bearer access token. 228 * 229 * @throws ParseException If the HTTP Authorization header value 230 * couldn't be parsed to a bearer access token. 231 */ 232 public static BearerAccessToken parse(final String header) 233 throws ParseException { 234 235 if (StringUtils.isBlank(header)) 236 throw new ParseException("Missing HTTP Authorization header", BearerTokenError.MISSING_TOKEN); 237 238 String[] parts = header.split("\\s", 2); 239 240 if (parts.length != 2) 241 throw new ParseException("Invalid HTTP Authorization header value", BearerTokenError.INVALID_REQUEST); 242 243 if (! parts[0].equals("Bearer")) 244 throw new ParseException("Token type must be \"Bearer\"", BearerTokenError.INVALID_REQUEST); 245 246 try { 247 return new BearerAccessToken(parts[1]); 248 249 } catch (IllegalArgumentException e) { 250 251 throw new ParseException(e.getMessage(), BearerTokenError.INVALID_REQUEST); 252 } 253 } 254 255 256 /** 257 * Parses an HTTP request for a bearer access token. 258 * 259 * @param request The HTTP request to parse. Must not be {@code null}. 260 * 261 * @return The bearer access token. 262 * 263 * @throws ParseException If a bearer access token wasn't found in the 264 * HTTP request. 265 */ 266 public static BearerAccessToken parse(final HTTPRequest request) 267 throws ParseException { 268 269 // See http://tools.ietf.org/html/rfc6750#section-2 270 271 String authzHeader = request.getAuthorization(); 272 273 if (authzHeader != null) { 274 275 return parse(authzHeader); 276 } 277 278 // Try alternative token locations, form and query string are 279 // parameters are not differentiated here 280 281 Map<String,String> params = request.getQueryParameters(); 282 283 String accessTokenValue = params.get("access_token"); 284 285 if (StringUtils.isBlank(accessTokenValue)) 286 throw new ParseException("Missing access token value", BearerTokenError.MISSING_TOKEN); 287 288 return new BearerAccessToken(accessTokenValue); 289 } 290}