001 package com.nimbusds.oauth2.sdk.auth; 002 003 004 import java.io.UnsupportedEncodingException; 005 006 import java.net.URLDecoder; 007 import java.net.URLEncoder; 008 009 import net.jcip.annotations.Immutable; 010 011 import org.apache.commons.codec.binary.Base64; 012 013 import com.nimbusds.oauth2.sdk.ParseException; 014 015 import com.nimbusds.oauth2.sdk.id.ClientID; 016 017 import com.nimbusds.oauth2.sdk.http.HTTPRequest; 018 019 020 /** 021 * Client secret basic authentication at the Token endpoint. Implements 022 * {@link ClientAuthenticationMethod#CLIENT_SECRET_BASIC}. This class is 023 * immutable. 024 * 025 * <p>Example HTTP Authorization header (for client identifier "s6BhdRkqt3" and 026 * secret "7Fjfp0ZBr1KtDRbnfVdmIw"): 027 * 028 * <pre> 029 * Authorization: Basic czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3 030 * </pre> 031 * 032 * <p>Related specifications: 033 * 034 * <ul> 035 * <li>OAuth 2.0 (RFC 6749), section 2.3.1. 036 * <li>HTTP Authentication: Basic and Digest Access Authentication 037 * (RFC 2617). 038 * </ul> 039 * 040 * @author Vladimir Dzhuvinov 041 * @version $version$ (2013-05-13) 042 */ 043 @Immutable 044 public final class ClientSecretBasic extends ClientAuthentication { 045 046 047 /** 048 * The client ID. 049 */ 050 private final ClientID clientID; 051 052 053 /** 054 * The client secret. 055 */ 056 private final Secret secret; 057 058 059 /** 060 * Creates a new client secret basic authentication. 061 * 062 * @param clientID The client identifier. Must not be {@code null}. 063 * @param secret The client secret. Must not be {@code null}. 064 */ 065 public ClientSecretBasic(final ClientID clientID, final Secret secret) { 066 067 super(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); 068 069 if (clientID == null) 070 throw new IllegalArgumentException("The client ID must not be null"); 071 072 this.clientID = clientID; 073 074 if (secret == null) 075 throw new IllegalArgumentException("The client secret must not be null"); 076 077 this.secret = secret; 078 } 079 080 081 /** 082 * Gets the client identifier. 083 * 084 * @return The client identifier. 085 */ 086 public ClientID getClientID() { 087 088 return clientID; 089 } 090 091 092 /** 093 * Gets the client secret. 094 * 095 * @return The client secret. 096 */ 097 public Secret getClientSecret() { 098 099 return secret; 100 } 101 102 103 /** 104 * Returns the HTTP Authorization header representation of this client 105 * secret basic authentication. 106 * 107 * <p>Example HTTP Authorization header (for client identifier "Aladdin" 108 * and password "open sesame"): 109 * 110 * <pre> 111 * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== 112 * </pre> 113 * 114 * <p>See RFC 2617, section 2. 115 * 116 * @return The HTTP Authorization header. 117 */ 118 public String toHTTPAuthorizationHeader() { 119 120 String b64 = null; 121 122 try { 123 String encodedClientID = URLEncoder.encode(clientID.toString(), "UTF-8"); 124 String encodedSecret = URLEncoder.encode(secret.getValue(), "UTF-8"); 125 126 StringBuilder sb = new StringBuilder(encodedClientID); 127 sb.append(':'); 128 sb.append(encodedSecret); 129 130 b64 = Base64.encodeBase64String(sb.toString().getBytes("UTF-8")); 131 132 } catch (UnsupportedEncodingException e) { 133 134 // UTF-8 should always be supported 135 } 136 137 return "Basic " + b64; 138 } 139 140 141 @Override 142 public void applyTo(final HTTPRequest httpRequest) { 143 144 httpRequest.setAuthorization(toHTTPAuthorizationHeader()); 145 } 146 147 148 /** 149 * Parses a client secret basic authentication from the specified HTTP 150 * Authorization header. 151 * 152 * @param header The HTTP Authorization header to parse. Must not be 153 * {@code null}. 154 * 155 * @return The client secret basic authentication. 156 * 157 * @throws ParseException If the header couldn't be parsed to a client 158 * secret basic authentication. 159 */ 160 public static ClientSecretBasic parse(final String header) 161 throws ParseException { 162 163 String[] parts = header.split("\\s"); 164 165 if (parts.length != 2) 166 throw new ParseException("Unexpected number of HTTP Authorization header value parts: " + parts.length); 167 168 if (! parts[0].equalsIgnoreCase("Basic")) 169 throw new ParseException("HTTP authentication must be \"Basic\""); 170 171 try { 172 String credentialsString = new String(Base64.decodeBase64(parts[1]), "utf-8"); 173 174 String[] credentials = credentialsString.split(":", 2); 175 176 if (credentials.length != 2) 177 throw new ParseException("Missing credentials delimiter \":\""); 178 179 String decodedClientID = URLDecoder.decode(credentials[0], "utf-8"); 180 String decodedSecret = URLDecoder.decode(credentials[1], "utf-8"); 181 182 return new ClientSecretBasic(new ClientID(decodedClientID), new Secret(decodedSecret)); 183 184 } catch (UnsupportedEncodingException e) { 185 186 throw new ParseException(e.getMessage(), e); 187 } 188 } 189 190 191 /** 192 * Parses a client secret basic authentication from the specified HTTP 193 * request. 194 * 195 * @param httpRequest The HTTP request to parse. Must not be 196 * {@code null} and must contain a valid 197 * Authorization header. 198 * 199 * @return The client secret basic authentication. 200 * 201 * @throws ParseException If the HTTP Authorization header couldn't be 202 * parsed to a client secret basic 203 * authentication. 204 */ 205 public static ClientSecretBasic parse(final HTTPRequest httpRequest) 206 throws ParseException { 207 208 String header = httpRequest.getAuthorization(); 209 210 if (header == null) 211 throw new ParseException("Missing HTTP Authorization header"); 212 213 return parse(header); 214 } 215 }