research: SSO-пример из payg-report (Application.cfc, oauth2.cfc, jwt.cfc)

This commit is contained in:
2026-05-30 14:19:51 +03:00
parent 6de5631451
commit 374a1a87b6
3 changed files with 977 additions and 0 deletions
+262
View File
@@ -0,0 +1,262 @@
component {
variables.algorithmMap = {
HS256: 'HmacSHA256',
HS384: 'HmacSHA384',
HS512: 'HmacSHA512',
RS256: 'SHA256withRSA',
RS384: 'SHA384withRSA',
RS512: 'SHA512withRSA',
ES256: 'SHA256withECDSA',
ES384: 'SHA384withECDSA',
ES512: 'SHA512withECDSA'
};
public any function init() {
variables.encodingUtils = new encodingUtils();
variables.jss = createObject( 'java', 'java.security.Signature' );
variables.messageDigest = createObject( 'java', 'java.security.MessageDigest' );
return this;
}
public string function encode(
required struct payload,
required any key,
required string algorithm,
struct headers = { }
) {
if ( !algorithmMap.keyExists( algorithm ) ) {
throw(
type = 'jwtcfml.InvalidAlgorithm',
message = 'Invalid JWT Algorithm.',
detail = 'The passed in algorithm is not supported.'
);
}
var header = { };
header.append( headers );
header.append( {
'typ': 'JWT',
'alg': algorithm
} );
var duplicatedPayload = duplicate( payload );
for ( var claim in [ 'iat', 'exp', 'nbf' ] ) {
if ( duplicatedPayload.keyExists( claim ) && isDate( duplicatedPayload[ claim ] ) ) {
duplicatedPayload[ claim ] = encodingUtils.convertDateToUnixTimestamp( duplicatedPayload[ claim ] );
}
}
var stringToSignParts = [
encodingUtils.binaryToBase64Url( charsetDecode( serializeJSON( header ), 'utf-8' ) ),
encodingUtils.binaryToBase64Url( charsetDecode( serializeJSON( duplicatedPayload ), 'utf-8' ) )
];
var stringToSign = stringToSignParts.toList( '.' );
return stringToSign & '.' & encodingUtils.binaryToBase64Url( sign( stringToSign, key, algorithm ) );
}
public struct function decode(
required string token,
any key,
any algorithms = [ ],
struct claims = { },
boolean verify = true
) {
var parts = listToArray( token, '.' );
if ( arrayLen( parts ) != 3 ) {
throw(
type = 'jwtcfml.InvalidToken',
message = 'Invalid JWT.',
detail = 'The passed in token does not have three `.` delimited parts.'
);
}
algorithms = isArray( algorithms ) ? algorithms : [ algorithms ];
var decoded = {
header: deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( parts[ 1 ] ), 'utf-8' ) ),
payload: deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( parts[ 2 ] ), 'utf-8' ) )
};
if ( verify ) {
if (
!algorithms.find( decoded.header.alg ) ||
!algorithmMap.keyExists( decoded.header.alg )
) {
throw(
type = 'jwtcfml.InvalidAlgorithm',
message = 'Unsupported or invalid algorithm',
detail = 'The passed in token does not have an algorithm declaration or its declared algorithm (#decoded.header.alg#) does not match the specified algorithms of #serializeJSON( algorithms )#.'
);
}
var stringToSign = parts[ 1 ] & '.' & parts[ 2 ];
var signature = encodingUtils.base64UrlToBinary( parts[ 3 ] );
if (
!verifySignature(
stringToSign,
key,
signature,
decoded.header.alg
)
) {
throw(
type = 'jwtcfml.InvalidSignature',
message = 'Signature is Invalid',
detail = 'The signature of the passed in token is invalid.'
);
}
var baseClaims = {
'exp': true,
'nbf': true
};
baseClaims.append( claims );
verifyClaims( decoded.payload, baseClaims );
}
for ( var claim in [ 'iat', 'exp', 'nbf' ] ) {
if ( decoded.payload.keyExists( claim ) ) {
decoded.payload[ claim ] = encodingUtils.convertUnixTimestampToDate( decoded.payload[ claim ] );
}
}
return decoded.payload;
}
public struct function getHeader( required string token ) {
return deserializeJSON( charsetEncode( encodingUtils.base64UrlToBinary( listFirst( token, '.' ) ), 'utf-8' ) );
}
public function parsePEMEncodedKey( required string pemKey ) {
return encodingUtils.parsePEMEncodedKey( pemKey );
}
public function parseJWK( required struct jwk ) {
return encodingUtils.parseJWK( jwk );
}
private function sign( message, key, algorithm ) {
if ( left( algorithm, 1 ) == 'H' ) {
var sig = binaryDecode(
hmac(
message,
key,
algorithmMap[ algorithm ],
'utf-8'
),
'hex'
);
} else {
if ( isSimpleValue( key ) ) {
key = encodingUtils.parsePEMEncodedKey( key );
} else if ( isStruct( key ) ) {
key = encodingUtils.parseJWK( key );
}
var jssInstance = variables.jss.getInstance( algorithmMap[ algorithm ] );
jssInstance.initSign( key );
jssInstance.update( charsetDecode( message, 'utf-8' ) );
var sig = jssInstance.sign();
if ( left( algorithm, 1 ) == 'E' ) {
sig = encodingUtils.convertDERtoP1363( sig, algorithm );
}
}
return sig;
}
private function verifySignature( message, key, signature, algorithm ) {
if ( left( algorithm, 1 ) == 'H' ) {
var sig = binaryDecode(
hmac(
message,
key,
algorithmMap[ algorithm ],
'utf-8'
),
'hex'
);
return MessageDigest.isEqual( signature, sig );
}
if ( left( algorithm, 1 ) == 'E' ) {
signature = encodingUtils.convertP1363ToDER( signature );
}
if ( isSimpleValue( key ) ) {
key = encodingUtils.parsePEMEncodedKey( key );
} else if ( isStruct( key ) ) {
key = encodingUtils.parseJWK( key );
}
var jssInstance = variables.jss.getInstance( algorithmMap[ algorithm ] );
jssInstance.initVerify( key );
jssInstance.update( charsetDecode( message, 'utf-8' ) );
return jssInstance.verify( signature );
}
private function verifyClaims( payload, claims ) {
if (
structKeyExists( payload, 'exp' )
&& !verifyDateClaim( payload.exp, claims.exp, -1 )
) {
throw(
type = 'jwtcfml.ExpiredSignature',
message = 'Token has expired',
detail = 'The passed in token has expired.'
);
}
if (
structKeyExists( payload, 'nbf' )
&& !verifyDateClaim( payload.nbf, claims.nbf, 1 )
) {
throw(
type = 'jwtcfml.NotBeforeException',
message = 'Token is not valid',
detail = 'The passed in token has not yet become valid.'
);
}
if ( structKeyExists( claims, 'iss' ) ) {
if ( !structKeyExists( payload, 'iss' ) || compare( payload.iss, claims.iss ) != 0 ) {
throw(
type = 'jwtcfml.InvalidIssuer',
message = 'Token has an invalid issuer',
detail = 'The passed in token either does not specify an issuer or the claimed issuer is not valid.'
);
}
}
if ( structKeyExists( claims, 'aud' ) ) {
var audArray = isArray( claims.aud ) ? claims.aud : [ claims.aud ];
if ( !structKeyExists( payload, 'aud' ) || !audArray.find( payload.aud ) ) {
throw(
type = 'jwtcfml.InvalidAudience',
message = 'Token has an invalid audience',
detail = 'The passed in token either does not specify an audience or the claimed audience is not valid.'
);
}
}
}
private function verifyDateClaim( payloadDate, claim, failState ) {
var pd = encodingUtils.convertUnixTimestampToDate( payloadDate );
var cd = claim;
if ( !isBoolean( cd ) || cd ) {
if ( isNumeric( cd ) ) {
cd = encodingUtils.convertUnixTimestampToDate( cd );
} else if ( !isDate( cd ) ) {
cd = now();
}
return dateCompare( pd, cd ) != failState;
}
return true;
}
}