001package com.nimbusds.openid.connect.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URL; 006import java.util.Map; 007 008import net.jcip.annotations.Immutable; 009 010import com.nimbusds.jwt.JWT; 011import com.nimbusds.jwt.JWTParser; 012 013import com.nimbusds.oauth2.sdk.AuthorizationCode; 014import com.nimbusds.oauth2.sdk.AuthorizationSuccessResponse; 015import com.nimbusds.oauth2.sdk.ParseException; 016import com.nimbusds.oauth2.sdk.ResponseType; 017import com.nimbusds.oauth2.sdk.SerializeException; 018import com.nimbusds.oauth2.sdk.id.State; 019import com.nimbusds.oauth2.sdk.http.HTTPResponse; 020import com.nimbusds.oauth2.sdk.token.AccessToken; 021import com.nimbusds.oauth2.sdk.util.URLUtils; 022 023 024/** 025 * OpenID Connect authentication success response. Used to return an 026 * authorisation code, access token and / or ID Token at the Authorisation 027 * endpoint. 028 * 029 * <p>Example HTTP response with code and ID Token (code flow): 030 * 031 * <pre> 032 * HTTP/1.1 302 Found 033 * Location: https://client.example.org/cb# 034 * code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk 035 * &id_token=eyJhbGciOiJSUzI1NiJ9.ew0KICAgICJpc3MiOiAiaHR0cDovL3Nlc 036 * nZlci5leGFtcGxlLmNvbSIsDQogICAgInVzZXJfaWQiOiAiMjQ4Mjg5NzYxMDAxI 037 * iwNCiAgICAiYXVkIjogInM2QmhkUmtxdDMiLA0KICAgICJub25jZSI6ICJuLTBTN 038 * l9XekEyTWoiLA0KICAgICJleHAiOiAxMzExMjgxOTcwLA0KICAgICJpYXQiOiAxM 039 * zExMjgwOTcwLA0KICAgICJjX2hhc2giOiAiTERrdEtkb1FhazNQazBjblh4Q2x0Q 040 * mdfckNfM1RLVWI5T0xrNWZLTzl1QSINCn0.D6JxCgpOwlyuK7DPRu5hFOIJRSRDT 041 * B7TQNRbOw9Vg9WroDi_XNzaqXCFSDH_YqcE-CBhoxD-Iq4eQL4E2jIjil47u7i68 042 * Nheev7d8AJk4wfRimgpDhQX5K8YyGDWrTs7bhsMTPAPVa9bLIBndDZ2mEdmPcmR9 043 * mXcwJI3IGF9JOaStYXJXMYWUMCmQARZEKG9JxIYPZNhFsqKe4TYQEmrq2s_HHQwk 044 * XCGAmLBdptHY-Zx277qtidojQQFXzbD2Ak1ONT5sFjy3yxPnE87pNVtOEST5GJac 045 * O1O88gmvmjNayu1-f5mr5Uc70QC6DjlKem3cUN5kudAQ4sLvFkUr8gkIQ 046 * </pre> 047 * 048 * <p>Related specifications: 049 * 050 * <ul> 051 * <li>OpenID Connect Core 1.0, section 3.1.2.5, 3.1.2.6, 3.2.2.5, 3.2.2.6, 052 * 3.3.2.5 and 3.3.2.6. 053 * </ul> 054 */ 055@Immutable 056public class AuthenticationSuccessResponse 057 extends AuthorizationSuccessResponse 058 implements AuthenticationResponse { 059 060 061 /** 062 * The ID token, if requested. 063 */ 064 private final JWT idToken; 065 066 067 /** 068 * Creates a new OpenID Connect authentication success response. 069 * 070 * @param redirectURI The requested redirection URI. Must not be 071 * {@code null}. 072 * @param code The authorisation code, {@code null} if not 073 * requested. 074 * @param idToken The ID token (ready for output), {@code null} if 075 * not requested. 076 * @param accessToken The UserInfo access token, {@code null} if not 077 * requested. 078 * @param state The state, {@code null} if not requested. 079 */ 080 public AuthenticationSuccessResponse(final URL redirectURI, 081 final AuthorizationCode code, 082 final JWT idToken, 083 final AccessToken accessToken, 084 final State state) { 085 086 super(redirectURI, code, accessToken, state); 087 088 this.idToken = idToken; 089 } 090 091 092 @Override 093 public ResponseType impliedResponseType() { 094 095 ResponseType rt = new ResponseType(); 096 097 if (getAuthorizationCode() != null) { 098 rt.add(ResponseType.Value.CODE); 099 } 100 101 if (getIDToken() != null) { 102 rt.add(OIDCResponseTypeValue.ID_TOKEN); 103 } 104 105 if (getAccessToken() != null) { 106 rt.add(ResponseType.Value.TOKEN); 107 } 108 109 return rt; 110 } 111 112 113 /** 114 * Gets the requested ID token. 115 * 116 * @return The ID token (ready for output), {@code null} if not 117 * requested. 118 */ 119 public JWT getIDToken() { 120 121 return idToken; 122 } 123 124 125 @Override 126 public Map<String,String> toParameters() 127 throws SerializeException { 128 129 Map<String,String> params = super.toParameters(); 130 131 if (idToken != null) { 132 133 try { 134 params.put("id_token", idToken.serialize()); 135 136 } catch (IllegalStateException e) { 137 138 throw new SerializeException("Couldn't serialize ID token: " + e.getMessage(), e); 139 140 } 141 } 142 143 return params; 144 } 145 146 147 @Override 148 public URL toURI() 149 throws SerializeException { 150 151 StringBuilder sb = new StringBuilder(getRedirectionURI().toString()); 152 153 // Fragment or query string? 154 if (idToken != null || getAccessToken() != null) { 155 sb.append('#'); 156 } else { 157 sb.append('?'); 158 } 159 160 sb.append(URLUtils.serializeParameters(toParameters())); 161 162 try { 163 return new URL(sb.toString()); 164 165 } catch (MalformedURLException e) { 166 167 throw new SerializeException("Couldn't serialize response: " + e.getMessage(), e); 168 } 169 } 170 171 172 /** 173 * Parses an OpenID Connect authentication success response from the 174 * specified redirection URI and parameters. 175 * 176 * @param redirectURI The base redirection URI. Must not be 177 * {@code null}. 178 * @param params The response parameters to parse. Must not be 179 * {@code null}. 180 * 181 * @return The OpenID Connect authentication success response. 182 * 183 * @throws ParseException If the parameters couldn't be parsed to an 184 * OpenID Connect authentication success 185 * response. 186 */ 187 public static AuthenticationSuccessResponse parse(final URL redirectURI, 188 final Map<String,String> params) 189 throws ParseException { 190 191 AuthorizationSuccessResponse asr = AuthorizationSuccessResponse.parse(redirectURI, params); 192 193 // Parse id_token parameter 194 JWT idToken = null; 195 196 if (params.get("id_token") != null) { 197 198 try { 199 idToken = JWTParser.parse(params.get("id_token")); 200 201 } catch (java.text.ParseException e) { 202 203 throw new ParseException("Invalid ID Token JWT: " + e.getMessage(), e); 204 } 205 } 206 207 return new AuthenticationSuccessResponse(redirectURI, 208 asr.getAuthorizationCode(), 209 idToken, 210 asr.getAccessToken(), 211 asr.getState()); 212 } 213 214 215 /** 216 * Parses an OpenID Connect authentication success response from the 217 * specified URI. 218 * 219 * <p>Example URI: 220 * 221 * <pre> 222 * https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 223 * </pre> 224 * 225 * @param uri The URI to parse. Can be absolute or relative, with a 226 * fragment or query string containing the authentication 227 * response parameters. Must not be {@code null}. 228 * 229 * @return The OpenID Connect authentication success response. 230 * 231 * @throws ParseException If the redirection URI couldn't be parsed to 232 * an OpenID Connect authentication success 233 * response. 234 */ 235 public static AuthenticationSuccessResponse parse(final URL uri) 236 throws ParseException { 237 238 String paramString; 239 240 if (uri.getQuery() != null) { 241 242 paramString = uri.getQuery(); 243 244 } else if (uri.getRef() != null) { 245 246 paramString = uri.getRef(); 247 248 } else { 249 throw new ParseException("Missing authorization response parameters"); 250 } 251 252 Map<String,String> params = URLUtils.parseParameters(paramString); 253 254 if (params == null) { 255 throw new ParseException("Missing or invalid authorization response parameters"); 256 } 257 258 return parse(URLUtils.getBaseURL(uri), params); 259 } 260 261 262 /** 263 * Parses an OpenID Connect authentication success response from the 264 * specified HTTP response. 265 * 266 * <p>Example HTTP response: 267 * 268 * <pre> 269 * HTTP/1.1 302 Found 270 * Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz 271 * </pre> 272 * 273 * @param httpResponse The HTTP response to parse. Must not be 274 * {@code null}. 275 * 276 * @return The OpenID Connect authentication success response. 277 * 278 * @throws ParseException If the HTTP response couldn't be parsed to an 279 * OpenID Connect authentication success 280 * response. 281 */ 282 public static AuthenticationSuccessResponse parse(final HTTPResponse httpResponse) 283 throws ParseException { 284 285 if (httpResponse.getStatusCode() != HTTPResponse.SC_FOUND) 286 throw new ParseException("Unexpected HTTP status code, must be 302 (Found): " + 287 httpResponse.getStatusCode()); 288 289 URL location = httpResponse.getLocation(); 290 291 if (location == null) 292 throw new ParseException("Missing redirection URI / HTTP Location header"); 293 294 return parse(location); 295 } 296}