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