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.security.validators.configurableProvider.Saml2SSOUtils;
import eu.eudat.logic.services.ApiContext; import eu.eudat.logic.services.ApiContext;
import eu.eudat.models.data.helpers.responses.ResponseItem; import eu.eudat.models.data.helpers.responses.ResponseItem;
import eu.eudat.models.data.security.Principal;
import eu.eudat.types.ApiMessageCode; import eu.eudat.types.ApiMessageCode;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -18,7 +17,7 @@ import java.nio.charset.StandardCharsets;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RequestMapping(value = {"/api/saml2/metadata"}) @RequestMapping(value = {"/api/saml2/"})
public class Saml2MetadataController extends BaseController { public class Saml2MetadataController extends BaseController {
private final ConfigLoader configLoader; private final ConfigLoader configLoader;
@ -29,7 +28,7 @@ public class Saml2MetadataController extends BaseController {
this.configLoader = configLoader; this.configLoader = configLoader;
} }
@RequestMapping(method = RequestMethod.GET, value = {"/{configurableProviderId}"}) @RequestMapping(method = RequestMethod.GET, value = {"metadata/{configurableProviderId}"})
public @ResponseBody public @ResponseBody
ResponseEntity getMetadata(@PathVariable String configurableProviderId) { ResponseEntity getMetadata(@PathVariable String configurableProviderId) {
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider) this.configLoader.getConfigurableProviders().getProviders().stream() 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.")); 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,16 +1,19 @@
import { HttpHeaders, HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Guid } from '@common/types/guid'; import { ConfigurationService } from './configuration/configuration.service';
import * as pk from 'pako'; import { BaseHttpService } from './http/base-http.service';
import { Observable } from 'rxjs';
import { BaseComponent } from '@common/base/base.component';
@Injectable() @Injectable()
export class SamlLoginService { export class SamlLoginService extends BaseComponent {
constructor() {} private actionUrl: string;
private headers = new HttpHeaders();
buildRelayState(spId: string, configurableLoginId: string): string { constructor(private http: BaseHttpService, private httpClient: HttpClient, private configurationService: ConfigurationService) {
let uri = 'spId=' + spId; super();
uri += '&configurableLoginId=' + configurableLoginId; this.actionUrl = configurationService.server + 'saml2/';
return encodeURIComponent(uri);
} }
resolveConfigurableLoginId(relayState: string): string { resolveConfigurableLoginId(relayState: string): string {
@ -24,29 +27,8 @@ export class SamlLoginService {
return routeParams.has('spId') ? routeParams.get('spId') : ''; return routeParams.has('spId') ? routeParams.get('spId') : '';
} }
getSamlLoginUrl(spEntityID: string, idpUrl: string, binding: string, assertionConsumerServiceUrl: string, configurableLoginId: string) { getAuthnRequest(configurableLoginId: string): Observable<string> {
const now = new Date(); return this.http.get<string>(this.actionUrl + 'authnRequest/' + configurableLoginId, { headers: this.headers });
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;
} }
} }

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 { LoginService } from '@app/ui/auth/login/utilities/login.service';
import { BaseComponent } from '@common/base/base.component'; import { BaseComponent } from '@common/base/base.component';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import * as pk from 'pako';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { ConfigurationService } from '@app/core/services/configuration/configuration.service'; import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
import { SamlLoginService } from '@app/core/services/saml-login.service'; import { SamlLoginService } from '@app/core/services/saml-login.service';
@ -86,8 +87,26 @@ export class ConfigurableLoginComponent extends BaseComponent implements OnInit
} }
else if(this.provider.type === ConfigurableProviderType.Saml2){ else if(this.provider.type === ConfigurableProviderType.Saml2){
let provider = this.provider as Saml2ConfigurableProvider; 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) { public configurableLoginUser(code: string, state: string) {