create saml2 authentication request in backend

This commit is contained in:
Bernaldo Mihasi 2023-06-13 09:48:54 +03:00
parent 4c9652abc3
commit 1f666a5bf9
4 changed files with 139 additions and 38 deletions

View File

@ -5,7 +5,6 @@ import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.sam
import eu.eudat.logic.security.validators.configurableProvider.Saml2SSOUtils;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.security.Principal;
import eu.eudat.types.ApiMessageCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
@ -18,7 +17,7 @@ import java.nio.charset.StandardCharsets;
@RestController
@CrossOrigin
@RequestMapping(value = {"/api/saml2/metadata"})
@RequestMapping(value = {"/api/saml2/"})
public class Saml2MetadataController extends BaseController {
private final ConfigLoader configLoader;
@ -29,7 +28,7 @@ public class Saml2MetadataController extends BaseController {
this.configLoader = configLoader;
}
@RequestMapping(method = RequestMethod.GET, value = {"/{configurableProviderId}"})
@RequestMapping(method = RequestMethod.GET, value = {"metadata/{configurableProviderId}"})
public @ResponseBody
ResponseEntity getMetadata(@PathVariable String configurableProviderId) {
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream()
@ -55,5 +54,25 @@ public class Saml2MetadataController extends BaseController {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to fetch metadata."));
}
}
@RequestMapping(method = RequestMethod.GET, value = {"authnRequest/{configurableProviderId}"})
public @ResponseBody
ResponseEntity<ResponseItem<String>> getAuthnRequest(@PathVariable String configurableProviderId) {
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream()
.filter(prov -> prov.getConfigurableLoginId().equals(configurableProviderId))
.findFirst().orElse(null);
if (saml2ConfigurableProvider != null) {
try {
String authnRequestXml = Saml2SSOUtils.getAuthnRequest(saml2ConfigurableProvider);
return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem<String>().status(ApiMessageCode.SUCCESS_MESSAGE).message("Created").payload(authnRequestXml));
}
catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Failed to create authentication request."));
}
}
else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ResponseItem<String>().status(ApiMessageCode.ERROR_MESSAGE).message("Unknown provider."));
}
}
}

View File

@ -794,4 +794,85 @@ public class Saml2SSOUtils {
}
public static String getAuthnRequest(Saml2ConfigurableProvider provider) throws Exception {
AuthnRequest authnRequest = buildAuthnRequest(provider);
String authnRequestXml = null;
DocumentBuilder builder;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
Signature signature = (Signature) buildXMLObject(Signature.DEFAULT_ELEMENT_NAME);
if(provider.isAuthnRequestsSigned()){
Credential credential = getCredential(provider.getSigningCert());
signature.setSigningCredential(credential);
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_DSA_SHA256);
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
X509KeyInfoGeneratorFactory keyInfoGeneratorFactory = new X509KeyInfoGeneratorFactory();
keyInfoGeneratorFactory.setEmitEntityCertificate(true);
KeyInfoGenerator keyInfoGenerator = keyInfoGeneratorFactory.newInstance();
signature.setKeyInfo(keyInfoGenerator.generate(getCredential(provider.getSigningCert())));
authnRequest.setSignature(signature);
}
builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
Marshaller out = registry.getMarshallerFactory().getMarshaller(authnRequest);
out.marshall(authnRequest, document);
if(provider.isAuthnRequestsSigned()) {
Signer.signObject(signature);
}
authnRequestXml = XmlBuilder.generateXml(document);
}
catch (MarshallingException | ParserConfigurationException e) {
logger.error(e.getMessage(), e);
}
return authnRequestXml;
}
private static AuthnRequest buildAuthnRequest(Saml2ConfigurableProvider provider) throws Exception {
AuthnRequest authnRequest = (AuthnRequest) buildXMLObject(AuthnRequest.DEFAULT_ELEMENT_NAME);
authnRequest.setIssueInstant(Instant.now());
authnRequest.setDestination(provider.getIdpUrl());
switch (provider.getBinding()) {
case "Redirect":
authnRequest.setProtocolBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);
break;
case "Artifact":
authnRequest.setProtocolBinding(SAMLConstants.SAML2_ARTIFACT_BINDING_URI);
break;
case "Post":
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
break;
}
authnRequest.setAssertionConsumerServiceURL(provider.getAssertionConsumerServiceUrl());
authnRequest.setID(UUID.randomUUID().toString());
authnRequest.setIssuer(buildIssuer(provider.getSpEntityId()));
authnRequest.setNameIDPolicy(buildNameIdPolicy());
return authnRequest;
}
private static NameIDPolicy buildNameIdPolicy() throws Exception {
NameIDPolicy nameIDPolicy = (NameIDPolicy) buildXMLObject(NameIDPolicy.DEFAULT_ELEMENT_NAME);
nameIDPolicy.setAllowCreate(true);
nameIDPolicy.setFormat(NameIDType.TRANSIENT);
return nameIDPolicy;
}
private static Issuer buildIssuer(String spEntityId) throws Exception {
Issuer issuer = (Issuer) buildXMLObject(Issuer.DEFAULT_ELEMENT_NAME);
issuer.setValue(spEntityId);
return issuer;
}
}

View File

@ -1,18 +1,21 @@
import { HttpHeaders, HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Guid } from '@common/types/guid';
import * as pk from 'pako';
import { ConfigurationService } from './configuration/configuration.service';
import { BaseHttpService } from './http/base-http.service';
import { Observable } from 'rxjs';
import { BaseComponent } from '@common/base/base.component';
@Injectable()
export class SamlLoginService {
export class SamlLoginService extends BaseComponent {
constructor() {}
private actionUrl: string;
private headers = new HttpHeaders();
constructor(private http: BaseHttpService, private httpClient: HttpClient, private configurationService: ConfigurationService) {
super();
this.actionUrl = configurationService.server + 'saml2/';
}
buildRelayState(spId: string, configurableLoginId: string): string {
let uri = 'spId=' + spId;
uri += '&configurableLoginId=' + configurableLoginId;
return encodeURIComponent(uri);
}
resolveConfigurableLoginId(relayState: string): string {
const decoded = decodeURIComponent(relayState);
const routeParams = new URLSearchParams(decoded);
@ -23,30 +26,9 @@ export class SamlLoginService {
const routeParams = new URLSearchParams(decoded);
return routeParams.has('spId') ? routeParams.get('spId') : '';
}
getSamlLoginUrl(spEntityID: string, idpUrl: string, binding: string, assertionConsumerServiceUrl: string, configurableLoginId: string) {
const now = new Date();
let protocolBinding = '';
switch (binding) {
case "Redirect": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" '; break;
case "Artifact": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" '; break;
case "Post": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" '; break;
}
const authenticationRequest = '<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_' + Guid.create() + '" Version="2.0" ' +
'IssueInstant="' + now.toISOString() + '" ' +
protocolBinding +
'AssertionConsumerServiceUrl="' + assertionConsumerServiceUrl + '" ' +
'Destination="' + idpUrl + '">' +
'<saml2:Issuer>' + spEntityID + '</saml2:Issuer>' +
'</saml2p:AuthnRequest>';
const uint = new Uint8Array(authenticationRequest.length);
for (let i = 0, j = authenticationRequest.length; i < j; ++i) {
uint[i] = authenticationRequest.charCodeAt(i);
}
const base64String = btoa(pk.deflateRaw(uint, { to: 'string' }));
const relayState = this.buildRelayState(spEntityID, configurableLoginId);
const url = idpUrl + '?RelayState=' + relayState + '&SAMLRequest=' + encodeURIComponent(base64String);
return url;
getAuthnRequest(configurableLoginId: string): Observable<string> {
return this.http.get<string>(this.actionUrl + 'authnRequest/' + configurableLoginId, { headers: this.headers });
}
}

View File

@ -8,6 +8,7 @@ import { ConfigurableProvidersService } from '@app/ui/auth/login/utilities/confi
import { LoginService } from '@app/ui/auth/login/utilities/login.service';
import { BaseComponent } from '@common/base/base.component';
import { environment } from 'environments/environment';
import * as pk from 'pako';
import { takeUntil } from 'rxjs/operators';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { SamlLoginService } from '@app/core/services/saml-login.service';
@ -86,10 +87,28 @@ export class ConfigurableLoginComponent extends BaseComponent implements OnInit
}
else if(this.provider.type === ConfigurableProviderType.Saml2){
let provider = this.provider as Saml2ConfigurableProvider;
window.location.href = this.samlLoginService.getSamlLoginUrl(provider.spEntityId, provider.idpUrl, provider.binding, provider.assertionConsumerServiceUrl, provider.configurableLoginId);
this.samlLoginService.getAuthnRequest(provider.configurableLoginId).pipe(takeUntil(this._destroyed))
.subscribe(
authenticationRequest => {
const uint = new Uint8Array(authenticationRequest.length);
for (let i = 0, j = authenticationRequest.length; i < j; ++i) {
uint[i] = authenticationRequest.charCodeAt(i);
}
const base64String = btoa(pk.deflateRaw(uint, { to: 'string' }));
const relayState = this.buildRelayState(provider.spEntityId, provider.configurableLoginId);
const url = provider.idpUrl + '?RelayState=' + relayState + '&SAMLRequest=' + encodeURIComponent(base64String);
window.location.href = url;
}
);
}
}
buildRelayState(spId: string, configurableLoginId: string): string {
let uri = 'spId=' + spId;
uri += '&configurableLoginId=' + configurableLoginId;
return encodeURIComponent(uri);
}
public configurableLoginUser(code: string, state: string) {
if (state !== (<Oauth2ConfigurableProvider>this.provider).state) {
this.router.navigate(['/login'])