forked from DuendeSoftware/IdentityServer
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
240 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using System.Threading.Tasks; | ||
using Duende.IdentityServer.Models; | ||
using Duende.IdentityServer.Validation; | ||
using IdentityModel; | ||
|
||
namespace IdentityServerHost.Validators; | ||
|
||
public class TokenExchangeGrantValidator : IExtensionGrantValidator | ||
{ | ||
private readonly ITokenValidator _validator; | ||
|
||
public TokenExchangeGrantValidator(ITokenValidator validator) | ||
{ | ||
_validator = validator; | ||
} | ||
|
||
public string GrantType => OidcConstants.GrantTypes.TokenExchange; | ||
|
||
public async Task ValidateAsync(ExtensionGrantValidationContext context) | ||
{ | ||
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidRequest); | ||
|
||
var subjectToken = context.Request.Raw.Get(OidcConstants.TokenRequest.SubjectToken); | ||
|
||
var subjectTokenType = context.Request.Raw.Get(OidcConstants.TokenRequest.SubjectTokenType); | ||
|
||
if (string.IsNullOrWhiteSpace(subjectToken)) | ||
{ | ||
await Console.Error.WriteLineAsync("subject_token is missing"); | ||
return; | ||
} | ||
|
||
if (!string.Equals(subjectTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken, | ||
StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
await Console.Error.WriteLineAsync("subject_token_type is not access_token"); | ||
return; | ||
} | ||
|
||
var validationResult = await _validator.ValidateIdentityTokenAsync(subjectToken); | ||
|
||
if (validationResult.IsError) | ||
{ | ||
await Console.Error.WriteLineAsync($"subject_token is invalid: {subjectToken}"); | ||
await Console.Error.WriteLineAsync(validationResult.Error); | ||
return; | ||
} | ||
|
||
await Console.Error.WriteLineAsync(JsonSerializer.Serialize(validationResult.Claims, new JsonSerializerOptions() | ||
{ | ||
ReferenceHandler = ReferenceHandler.Preserve | ||
})); | ||
|
||
var sub = validationResult.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject)?.Value ?? "unknown-sub"; | ||
var clientId = validationResult.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.ClientId)?.Value ?? | ||
validationResult.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Audience)?.Value ?? | ||
"unknown-client"; | ||
|
||
context.Request.ClientId = clientId; | ||
context.Result = new GrantValidationResult(sub, GrantType, validationResult.Claims, clientId, | ||
new Dictionary<string, object> | ||
{ | ||
{ OidcConstants.TokenResponse.IssuedTokenType, OidcConstants.TokenTypeIdentifiers.AccessToken }, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
$(document).ready(function () { | ||
|
||
const selfHost = location.protocol + "//" + location.hostname + (location.port ? ":" + location.port : ""); | ||
|
||
const locale = getParameterByName("lang"); | ||
if (!locale) | ||
window.location.href = window.location.href + "?lang=en-US"; | ||
|
||
const clientId = "token-exchange-client"; | ||
|
||
const messageLog = document.getElementById("ResponseOutput"); | ||
|
||
function logMessage(message, isError) { | ||
var node = document.createElement("P"); | ||
if (isError && isError === true) { | ||
node.setAttribute("style", "background-color: lightcoral; font-weight: bold"); | ||
} | ||
var messageElement = document.createTextNode(message); | ||
node.appendChild(messageElement); | ||
messageLog.appendChild(node); | ||
} | ||
$("#gobutton").click(function (e) { | ||
e.preventDefault(); | ||
messageLog.innerHTML = ""; //Clear messages output | ||
logMessage("Starting token exchange flow with client: '" + clientId + "'"); | ||
$.ajax({ | ||
url: selfHost + "/api/v1/xsrf", | ||
type: "GET", | ||
xhrFields: { | ||
withCredentials: true | ||
}, | ||
success: function () { | ||
console.log("Xsrf endpoint returned. cookies=" + document.cookie); | ||
const token = getCookie("XSRF-TOKEN"); | ||
const subjectToken = $('#subject-token').val(); | ||
|
||
$.ajax({ | ||
type: "POST", | ||
xhrFields: { | ||
withCredentials: true | ||
}, | ||
crossDomain: true, | ||
beforeSend: function (xhrObj) { | ||
xhrObj.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | ||
xhrObj.setRequestHeader("X-XSRF-TOKEN", token); | ||
}, | ||
url: selfHost + "/connect/token", | ||
data: encodeURIComponent("client_id") + "=" + encodeURIComponent(clientId) + "&" | ||
+ encodeURIComponent("client_secret") + "=" + encodeURIComponent(clientId) + "&" | ||
+ encodeURIComponent("scope") + "=" + encodeURIComponent("openid profile") + "&" + encodeURIComponent("grant_type") + "=" + encodeURIComponent("urn:ietf:params:oauth:grant-type:token-exchange") + "&" + encodeURIComponent("subject_token") + "=" + encodeURIComponent(subjectToken) + "&" + encodeURIComponent("subject_token_type") + "=" + encodeURIComponent("urn:ietf:params:oauth:token-type:access_token") | ||
, | ||
success: function () { | ||
}, | ||
complete: function (jqXHR) { | ||
console.log(jqXHR.status, jqXHR.responseText); | ||
if (jqXHR.status === 200) { | ||
logMessage("太好了 —— 得到了响应! All good - we got a response!"); | ||
logMessage(jqXHR.responseText); | ||
} else { | ||
logMessage(jqXHR.responseText, true); | ||
} | ||
} | ||
}); | ||
} | ||
}); | ||
}); | ||
}); | ||
|
||
|
||
function getCookie(cname) { | ||
var name = cname + "="; | ||
var decodedCookie = decodeURIComponent(document.cookie); | ||
var ca = decodedCookie.split(";"); | ||
for (var i = 0; i < ca.length; i++) { | ||
var c = ca[i]; | ||
while (c.charAt(0) === " ") { | ||
c = c.substring(1); | ||
} | ||
if (c.indexOf(name) === 0) { | ||
return c.substring(name.length, c.length); | ||
} | ||
} | ||
return ""; | ||
} | ||
|
||
function getParameterByName(name, url) { | ||
if (!url) url = window.location.href; | ||
name = name.replace(/[\[\]]/g, "\\$&"); | ||
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), | ||
results = regex.exec(url); | ||
if (!results) return null; | ||
if (!results[2]) return ""; | ||
return decodeURIComponent(results[2].replace(/\+/g, " ")); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"/> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Login with token exchange flow</title> | ||
</head> | ||
<body> | ||
<a href="/">Home</a> > <span class="headline">Login with token exchange flow</span> | ||
<h2>Log in</h2> | ||
<ul> | ||
</ul> | ||
<form id="form1" method="post" action=""> | ||
<label for="subject-token">Subject Token: </label><br/> | ||
<textarea id="subject-token" style="width: 800px; height: 250px;"></textarea><br/> | ||
<button id="gobutton">Sign me in!</button> | ||
</form> | ||
<h3>Output</h3> | ||
<div id="ResponseOutput"> | ||
|
||
</div> | ||
<script src="js/polyfills.js" nonce="{{nonce}}"></script> | ||
<script src="js/oidc-client.slim.min.js" nonce="{{nonce}}"></script> | ||
<script src="lib/jquery/dist/jquery.js"></script> | ||
<script src="js/qrcode.min.js" nonce="{{nonce}}"></script> | ||
<script src="js/token-exchange-flow.js?nonce={{nonce}}" nonce="{{nonce}}"></script> | ||
</body> | ||
</html> |