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