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