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