Een extra gebruikersovereenkomst/ toestemmingspagina toevoegen aan de inlogstroom
In de meeste applicaties die dagelijks worden gebruikt, wordt u tijdens het inlogproces gevraagd toestemming te geven voor het delen van uw informatie of voor algemene voorwaarden van een licentieovereenkomst voor eindgebruikers. Dit soort toestemmingsverzoeken informeren gebruikers over de gebruikslimieten van de applicatie of geven aan in hoeverre de softwareleverancier aansprakelijk is wanneer gebruikers de applicatie gebruiken. Vooral met recente regelgeving voor de bescherming van persoonsgegevens, zoals de AVG en verschillende mediaberichten over schending van persoonsgegevens door bekende softwareserviceproviders, is het verkrijgen van toestemming van de gebruiker voor het delen van informatie belangrijk. Normaal gesproken wordt het inlogproces afgebroken als de gebruikers geen toestemming geven.
De applicaties kunnen vragen om toestemming, hetzij tijdens de inlogstroom van de gebruiker, de gebruikersregistratiestroom of de installatietijd van de applicatie. Er is geen vaste regel voor wanneer deze extra pagina’s moeten worden getoond. Sommige apps tonen de algemene voorwaarden mogelijk niet als een aparte pagina, maar hebben een selectievakje op de inlog- of aanmeldingspagina zelf.
Deze blog is van toepassing op scenario’s waarin u verschillende toestemmingsgoedkeuringen van de eindgebruiker wilt ontvangen tijdens het inlogproces.
Scenario: een extra goedkeuringspagina toevoegen in de inlogstroom met WSO2 Identity Server
Noodzaak om een extra pagina te tonen voor gebruikerstoestemming, een gebruikersovereenkomst, accepteren van algemene voorwaarden of een disclaimermelding, voordat de gebruiker kan inloggen op de consumentenapplicatie.
Hier maken we deze extra stap als onderdeel van het inlogproces. Dergelijke gevallen moeten worden afgehandeld via de identiteitsprovider die verantwoordelijk is voor het afhandelen van de authenticatie/login van de applicatie. Als de applicatie geen identiteitsprovider gebruikt om in te loggen, is het aan de applicatieontwikkelaars om die aanvullende logica en toestemmingspagina’s te onderhouden.
Vandaag gaan we bekijken hoe de WSO2 Identity Server, die fungeert als een identiteitsprovider voor applicaties van derden, kan worden gebruikt om u te helpen eenvoudig een dergelijke extra goedkeuringspagina toe te voegen aan de inlogstroom.
Oplossing: handlers voor post-authenticatie gebruiken in WSO2 Identity Server voor het hanteren van aanvullende goedkeuringspagina’s in de inlogstroom
Voor dit soort scenario’s biedt de WSO2 Identity-server een uitbreidingspunt genaamd ‘Post Authentication Handler‘.
Verwar dit niet met het uitbreidingspunt ‘Event Handler‘ in WSO2 Identity Server.
In het WSO2 Identity Server-gebeurtenisframework zijn er gebeurtenissen die worden geactiveerd vóór de authenticatie en na de authenticatie, respectievelijk genaamd ‘PRE_AUTHENTICATION’ en ‘POST_AUTHENTICATION’. U kunt zich abonneren op die gebeurtenissen en handlers schrijven om aangepaste logica te activeren of uit te voeren.
Maar “Post Authentication Handlers” vallen niet in dezelfde categorie. Ze worden geactiveerd zodra alle authenticatiestappen zijn voltooid.
Als u meerdere authenticatiestappen configureert, zoals gebruikersnaam-/wachtwoordauthenticatie + SMS OTP, of gefedereerde authenticators zoals Facebook/Google-authenticatie voor uw toepassing configureert, worden de handlers voor post-authenticatie alleen uitgevoerd nadat alle authenticatiestappen met succes zijn voltooid.
3 stappen om een handler voor post-authenticatie te maken
Stel dat u een applicatie heeft die WSO2 Identity Server gebruikt voor gebruikersauthenticatie. Er is een recente vereiste om een nieuwe pagina met algemene voorwaarden te introduceren om toestemming van de gebruiker te krijgen. Het is verplicht om voor uw applicatie toestemming van gebruikers te vragen voor de algemene voorwaarden met betrekking tot toegang tot uw applicatie voordat de inlogstroom kan worden voltooid. Hieronder volgen de stappen die u kunt doorlopen om aan deze vereiste te voldoen met behulp van WSO2 Identity Server.
- Maak een jsp-bestand met de pagina die de gebruikers krijgen te zien en waarvoor ze hun toestemming geven. Bijvoorbeeld: algemene-voorwaarden.jsp. Sla deze op in de map WSO2-IS/repository/deployment/server/webapps/authenticationendpoint.
- Schrijf de java-component van de handler voor post-authenticatie (OSGi-bundel).
- Maak een maven-project.
- U heeft ‘org. wso2.carbon.identity.application. authentication.framework’ nodig als primaire afhankelijkheid in dit project. Voeg correcte versies toe van het authenticatieframework die overeenkomen met de WSO2 IS-distributie die u gebruikt.
- Maak een nieuwe java-klasse die de ‘AbstractPostAuthnHandler’ uitbreidt.
- Bijvoorbeeld: openbare klasse TermsAndConditionsPostAuthenticationHandler breidt AbstractPostAuthnHandler uit {
- Onderbreek nu de ‘handle‘-methode binnen de nieuwe klasse en implementeer de logica om de nieuwe pagina weer te geven met algemene voorwaarden en of wel/geen toestemming is gegeven door de gebruiker, om inloggen bij de applicatie toe te staan of te weigeren.
@Override public PostAuthnHandlerFlowStatus handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationContext authenticationContext) throws PostAuthenticationFailedException { |
- Het geretourneerde type (PostAuthnHandlerFlowStatus) van deze methode is een Enum dat uit waarden bestaat
SUCCESS_COMPLETED: geeft aan dat de taken van de handler voor post-authenticatie met succes zijn uitgevoerd.
INCOMPLETE: geeft aan dat de taken van de handler voor post-authenticatie nog niet zijn voltooid. Bijvoorbeeld: de pagina met algemene voorwaarden is nog niet weergegeven aan de gebruiker.
UNSUCCESS_COMPLETED: geeft aan dat de taak van de handler voor post-authenticatie is voltooid, maar de uitvoering niet is gelukt. Bijvoorbeeld: de geverifieerde gebruiker is ‘null’. Sla daarom de uitvoering van de handler voor post-authenticatie over door deze waarde te verzenden.
- Binnen de ‘handle‘-methode dient de volgende logica geĂŻmplementeerd te worden.
- Controleer of de geverifieerde gebruiker niet null is.
- Controleer of de gebruiker wordt omgeleid naar de bedoelde pagina. Bijvoorbeeld: algemene-voorwaarden.jsp
- Als de gebruiker zich op de nieuw toegevoegde pagina bevindt, controleer dan of goedkeuring is gegeven of niet. Als er geen goedkeuring is gegeven, gooit u een exceptie op.
- Als de gebruiker zich nog steeds niet op de bedoelde pagina bevindt, leid de gebruiker dan om naar de bedoelde pagina door een http-omleiding uit te voeren. Bijvoorbeeld: httpServletResponse.sendRedirect()
- Registreer nu uw handler-implementatieklasse als een PostAuthenticationHandler OSGi-service, bijvoorbeeld: context.getBundleContext().registerService(PostAuthenticationHandler.class.getName(),TermsAndConditionsPostAuthenticationHandler, null);
- Configureer de nieuwe handler in het bestand deployment.toml.
Bijvoorbeeld:
[[event_listener]] id = “custom_post_auth_listener” type = “org.wso2.carbon.identity.core.handler.AbstractIdentityHandler” name = “org.wso2.carbon.identity.post.authn.handler.custom. TermsAndConditionsPostAuthenticationHandler ” order = 100 |
De handlers worden het eerste uitgevoerd met het laagste nummer. Hier heb ik er 100 neergezet. Mijn handler wordt dus uitgevoerd nadat alle andere handlers voor post-authenticatie (zoals de beheerpagina voor standaardtoestemming) zijn voltooid.
De algemene stroom tijdens het inloggen ziet er als volgt uit:
Bij gebruik van extern gehoste pagina’s: omleiden naar externe toestemmingspagina’s vanuit de WSO2 Identity Server-implementatie van de handlerklasse voor post-authenticatie
Zoals u in het eerdere stroomschema hebt gezien, leiden we binnen de implementatie van de handlerklasse om naar de beoogde pagina met behulp van de methode sendRedirect. Dus in plaats van uw pagina’s binnen de webapp authenticationendpoint te hosten, kunt u de gewenste pagina’s met toestemming/disclaimer/algemene voorwaarden extern hosten, en vanuit de implementatie van de handlerklasse omleiden naar de extern of afzonderlijk gehoste pagina.
Aandachtspunten:
- Zorg ervoor dat u de parameter ‘sessionDataKey‘ doorgeeft als een verzoekparameter bij het uitvoeren van de omleiding binnen de implementatie van de handlerklasse.
Bijvoorbeeld: httpServletResponse.sendRedirect(“https://myhost:8080/consent.jsp”+”? sessionDataKey =” + authenticationContext.getContextIdentifier() );
- Zorg er vervolgens voor dat u dezelfde seesionDataKey als een verborgen invoerparameter vanuit uw aangepaste jsp-bestand doorgeeft aan het commonauth-eindpunt van de WSO2 Identity Server.
Bijvoorbeeld: <input type=”hidden” name=”<%=” sessionDataKey “%>” value=”<%=Encode.forHtmlAttribute(r equest.getParameter(“sessionDataKey” )) %>”/>
Deze parameter helpt om de correlatie te behouden tussen verschillende omleidingen die plaatsvinden binnen een verzoekstroom. Aangezien HTTP een staatloos protocol is, helpt deze parameter de WSO2 Identity Server om de status te volgen van een bepaalde verzoekstroom die naar de WSO2 Identity Server komt.
5 referentie-implementaties van handlers voor post-authenticatie in WSO2 Identity Server
- ConsentMgtPostAuthnHandler: verwerkt gebruikerstoestemmingen na succesvolle authenticatie. Vraagt om gebruikerstoestemmingspagina.
- JITProvisioningPostAuthenticationHandler: verwerkt Just-In-Time Provisioning nadat een gebruiker is geverifieerd via een externe identiteitsprovider (federated IDP). Vraagt om Just-In-Time Provisioning-pagina’s.
- PostAuthAssociationHandler: verantwoordelijk voor het koppelen van gefedereerde gebruikers aan lokale gebruikersaccounts. In deze implementatie wordt niet gevraagd om pagina-omleidingen. De lokale gebruikerskoppelingen worden alleen achter de schermen uitgevoerd.
- PostAuthenticatedSubjectIdentifierHandler: verantwoordelijk voor het instellen van de onderwerp-ID voor een geverifieerde gebruiker.
- PostAuthnMissingClaimHandler: verantwoordelijk voor het ophalen van ontbrekende verplichte claims. Hier wordt gevraagd om een pagina die de gebruiker vraagt om verplichte claims in te vullen, als de pagina niet al is ingevuld.
Samenvatting
Het gebruik van handlers voor post-authenticatie in WSO2 Identity Server om extra pagina’s weer te geven tijdens de inlogverzoekstroom, kan worden gedaan voor verschillende gebruikssituaties, zoals vragen om toestemming van gebruikers, goedkeuringen of tekstinvoer.
Handlers voor post-authenticatie kunnen ook worden gebruikt om aangepaste logica uit te voeren tijdens de inlogstroom zonder noodzakelijkerwijs aangepaste pagina-omleidingen uit te voeren. Het is belangrijk op te merken dat deze handlers pas worden uitgevoerd na voltooiing van de authenticatiereeks/-stappen.
De standaardimplementatie van het product biedt extra functionaliteit zoals JIT-provisioning en ontbrekende claimhandlers. Gebruik het volgende voorbeeld van een broncodefragment gerust om uw eigen handler voor post-authenticatie in WSO2 Identity Server te implementeren. En houd onze blog in de gaten voor meer voorbeelden en inzichten!
Als u op zoek bent naar meer voorbeelden van hoe er met WSO2 Identity Server kan worden gewerkt, bekijk dan zeker de officiĂ«le GitHub-repository. Bekijk daarnaast voor gedetailleerde instructies over het gebruik van deze voorbeelden dit nuttige blogbericht van Yenlo – Hoe werken de WSO2 Identity Server samples? Beide bronnen kunnen u waardevolle inzichten en codefragmenten bieden om u te helpen aan de slag te gaan met uw WSO2 Identity Server-projecten.
package org.yenlo.carbon.identity.post.authn.handler.termsandconditions;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.PostAuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.AbstractPostAuthnHandler;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.PostAuthnHandlerFlowStatus;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TermsAndConditionsPostAuthenticationHandler extends AbstractPostAuthnHandler {
private String CONSENT_POPPED_UP = "consentPoppedUp";
@Override
public PostAuthnHandlerFlowStatus handle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationContext authenticationContext)
throws PostAuthenticationFailedException {
if (getAuthenticatedUser(authenticationContext) == null) {
return PostAuthnHandlerFlowStatus.SUCCESS_COMPLETED;
}
if (isConsentPoppedUp(authenticationContext)) {
if (httpServletRequest.getParameter("consent").equalsIgnoreCase("approve")) {
return PostAuthnHandlerFlowStatus.SUCCESS_COMPLETED;
} else {
throw new PostAuthenticationFailedException("Cannot access this application : Consent Denied",
"Consent denied");
}
} else {
try {
httpServletResponse.sendRedirect
(ConfigurationFacade.getInstance().getAuthenticationEndpointURL().replace("/login.do", ""
) + "/termsandconditions" + ".jsp?sessionDataKey=" + authenticationContext.getContextIdentifier() +
"&application=" + authenticationContext
.getSequenceConfig().getApplicationConfig().getApplicationName());
setConsentPoppedUpState(authenticationContext);
return PostAuthnHandlerFlowStatus.INCOMPLETE;
} catch (IOException e) {
throw new PostAuthenticationFailedException("Invalid Consent", "Error while redirecting", e);
}
}
}
@Override
public String getName() {
return "DisclaimerHandler";
}
private AuthenticatedUser getAuthenticatedUser(AuthenticationContext authenticationContext) {
AuthenticatedUser user = authenticationContext.getSequenceConfig().getAuthenticatedUser();
return user;
}
private void setConsentPoppedUpState(AuthenticationContext authenticationContext) {
authenticationContext.addParameter(CONSENT_POPPED_UP, true);
}
private boolean isConsentPoppedUp(AuthenticationContext authenticationContext) {
return authenticationContext.getParameter(CONSENT_POPPED_UP) != null;
}
}
Componentklasse OSGi-service:
package org.yenlo.carbon.identity.post.authn.handler.termsandconditions.internal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.wso2.carbon.identity.application.authentication.framework.handler.request.PostAuthenticationHandler;
import org.wso2.carbon.identity.core.util.IdentityCoreInitializedEvent;
import org.yenlo.carbon.identity.post.authn.handler.termsandconditions.TermsAndConditionsPostAuthenticationHandler;
@Component(
name = "identity.post.authn.termsandconditions.handler",
immediate = true
)
public class TermsAndConditionsPostAuthnHandlerServiceComponent {
private static final Log log = LogFactory.getLog(TermsAndConditionsPostAuthnHandlerServiceComponent.class);
@Activate
protected void activate(ComponentContext context) {
try {
TermsAndConditionsPostAuthenticationHandler termsAndConditionsPostAuthenticationHandler =
new TermsAndConditionsPostAuthenticationHandler();
context.getBundleContext().registerService(PostAuthenticationHandler.class.getName(),
termsAndConditionsPostAuthenticationHandler, null);
} catch (Throwable e) {
log.error("Error while activating disclaimer post authentication handler.", e);
}
}
protected void unsetIdentityCoreInitializedEventService(IdentityCoreInitializedEvent identityCoreInitializedEvent) {
/* reference IdentityCoreInitializedEvent service to guarantee that this component will wait until identity core
is started */
}
@Reference(
name = "identity.core.init.event.service",
service = IdentityCoreInitializedEvent.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetIdentityCoreInitializedEventService"
)
protected void setIdentityCoreInitializedEventService(IdentityCoreInitializedEvent identityCoreInitializedEvent) {
/* reference IdentityCoreInitializedEvent service to guarantee that this component will wait until identity core
is started */
}
}
JSP bestand
<%@ page import="org.owasp.encoder.Encode" %>
<%@ page import="org.wso2.carbon.identity.application.authentication.endpoint.util.Constants" %>
<%
String app = request.getParameter("application");
String[] missingClaimList = null;
String appName = null;
if (request.getParameter(Constants.MISSING_CLAIMS) != null) {
missingClaimList = request.getParameter(Constants.MISSING_CLAIMS).split(",");
}
%>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WSO2 Identty Server</title>
<link rel="icon" href="images/favicon.png" type="image/x-icon"/>
<link href="libs/bootstrap_3.3.5/css/bootstrap.min.css" rel="stylesheet">
<link href="css/Roboto.css" rel="stylesheet">
<link href="css/custom-common.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="js/html5shiv.min.js"></script>
<script src="js/respond.min.js"></script>
<![endif]-->
</head>
<body>
<script type="text/javascript">
function approved() {
document.getElementById('consent').value = "approve";
document.getElementById("profile").submit();
}
function deny() {
document.getElementById('consent').value = "deny";
document.getElementById("profile").submit();
}
</script>
<!-- header -->
<header class="header header-default">
<div class="container-fluid"><br></div>
<div class="container-fluid">
<div class="pull-left brand float-remove-xs text-center-xs">
<a href="#">
<img src="images/logo-inverse.svg" alt="wso2" title="wso2" class="logo">
<h1><em> Identity Server </em></h1>
</a>
</div>
</div>
</header>
<!-- page content -->
<div class="container-fluid body-wrapper">
<div class="row">
<div class="col-md-12">
<!-- content -->
<div class="container col-xs-10 col-sm-6 col-md-6 col-lg-3 col-centered wr-content wr-login col-centered">
<div>
<h2 class="wr-title uppercase blue-bg padding-double white boarder-bottom-blue margin-none">
Terms & Conditions
</h2>
</div>
<div class="boarder-all ">
<div class="clearfix"></div>
<form action="../commonauth" method="post" id="profile" name=""
class="form-horizontal" >
<div class="padding-double login-form">
<div class="form-group">
<p>By using this <strong><%=Encode.forHtml(request.getParameter("application"))%></strong> app, you agree to the terms and conditions outlined below, which are designed to ensure the security and privacy of your personal data and provide a seamless user experience.
</p>
</div>
<table width="100%" class="styledLeft">
<tbody>
<tr>
<td class="buttonRow" colspan="2">
<div style="text-align:left;">
<input type="button" class="btn btn-primary" id="approve" name="approve"
onclick="javascript: approved(); return false;"
value="Approve"/>
<input class="btn" type="reset"
value="Deny"
onclick="javascript: deny(); return false;"/>
</div>
<input type="hidden" name="<%="sessionDataKey"%>"
value="<%=Encode.forHtmlAttribute(request.getParameter("sessionDataKey"))%>"/>
<input type="hidden" name="consent" id="consent"
value="deny"/>
</td>
</tr>
</tbody>
</table>
</div>
</form>
</div>
</div>
</div>
<!-- /content -->
</div>
</div>
<!-- /content/body -->
</div>
<!-- footer -->
<footer class="footer">
<div class="container-fluid">
<p>WSO2 Identity Server | ©
<script>document.write(new Date().getFullYear());</script>
<a href="http://wso2.com/" target="_blank"><i class="icon fw fw-wso2"></i>
Inc
</a>. All Rights Reserved
</p>
</div>
</footer>
<script src="libs/jquery_1.11.3/jquery-1.11.3.js"></script>
<script src="libs/bootstrap_3.3.5/js/bootstrap.min.js"></script>
</body>
</html>