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