create saml2 authentication request in backend
This commit is contained in:
parent
4c9652abc3
commit
1f666a5bf9
|
@ -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."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,18 +1,21 @@
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 {
|
resolveConfigurableLoginId(relayState: string): string {
|
||||||
const decoded = decodeURIComponent(relayState);
|
const decoded = decodeURIComponent(relayState);
|
||||||
const routeParams = new URLSearchParams(decoded);
|
const routeParams = new URLSearchParams(decoded);
|
||||||
|
@ -23,30 +26,9 @@ export class SamlLoginService {
|
||||||
const routeParams = new URLSearchParams(decoded);
|
const routeParams = new URLSearchParams(decoded);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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,10 +87,28 @@ 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) {
|
||||||
if (state !== (<Oauth2ConfigurableProvider>this.provider).state) {
|
if (state !== (<Oauth2ConfigurableProvider>this.provider).state) {
|
||||||
this.router.navigate(['/login'])
|
this.router.navigate(['/login'])
|
||||||
|
|
Loading…
Reference in New Issue