fb
WSO2 Identity Server 6 min

Zich voordoen als een andere gebruiker in Identity Server

sidharth.jpg
Sidharth Dash
Integration Specialist
identity
Scroll

WSO2-Identity Server is een zeer sterke CIAM-tool die wordt gebruikt in alle sectoren en geografische locaties. Eindgebruikers op geografische locaties van dienst zijn en ondersteunen bij technische problemen in de ’taal’ van een technisch platform, is een zeer grote hindernis. Maar als de medewerker van de klantenservice het ‘gebruikersperspectief’ in het platform kan zien, kan dit helpen om de taalbarrière te vermijden, het probleem sneller te begrijpen en daardoor tot een snellere oplossingstijd te leiden.

“Voordoen als een gebruiker” is een mechanisme waarmee vertegenwoordigers van de klantenondersteuning eindgebruikers kunnen helpen door zich als hen voor te doen. Deze functie is niet standaard beschikbaar in WSO2 Identity Server. Met deze vereiste heb ik een ” aangepaste toekenning ” ontwikkeld en geïmplementeerd om de kloof te overbruggen.

Wat achtergrondinformatie:

De stack bestaat uit WSO2 APIM, IS-KM en een webapplicatie. De webapplicatie is beveiligd door IS-KM en tijdens het inloggen wordt een toegangstoken gegenereerd voor de eindgebruiker. De webapplicatie maakt gebruik van API gepubliceerd in APIM om gegevens van eindgebruikers op te halen en toegang te krijgen tot andere services. Een JWT-token wordt gegenereerd in de GW die wordt doorgegeven aan de BE, gegevens van de eindgebruikers in de JWT bepalen in hoge mate de respons van de API. Aangezien de API gebruikersgevoelig is, is de beste manier om zich voor te doen als de eindgebruiker, namens hem een toegangstoken te genereren.

Zodra deze toegangstoken is gegenereerd, heeft de klantenservicemedewerker toegang tot de webapplicatie op dezelfde manier als de eindgebruiker. De GW genereert een JWT-token (met gegevens van de eindgebruiker) die kan worden gebruikt om gepersonaliseerde diensten door de webapplicatie te leveren.

High Level diagram:

highlevel diagram identity server
highlevel diagram identity server

Hoe het werkt:

Zoals eerder beschreven, heb ik een aangepast toekenningstype gebouwd en het user_impersonation genoemd. Dit aangepaste toekenningstype user_impersonation dat een toegangstoken van de klantenservicemedewerker accepteert en een nieuwe toegangstoken genereert voor een doelgebruiker. De flow werkt in de volgende volgorde:

  1. Klantenservicevertegenwoordiger zal het toekenningstype user_impersonation inroepen.
    • Het toekenningstype valideert of alle vereiste parameters, d.w.z. grant_type, target_user en access_token , zijn opgegeven.
    • Controleer of de toegangstoken van de klantenservicemedewerker geldig is.
    • Controleer of de eindgebruiker ( target_user) bestaat in het gebruikersarchief.
    • Controleer of de vertegenwoordiger van de klantenservice de juiste rol heeft bij zijn autorisatie.
    • Genereer de toegangstoken voor de eindgebruiker ( target_user ).

  2. Gebruik toegangstoken genereren om de API in te roepen.
    • API-GW valideert de toegangstoken van de eindgebruiker ( target_user ) met Identity Server.
    • API-GW genereert de JWT-token van de gebruiker voor de eindgebruiker ( target_user ) en geeft deze door aan BE-services

      .
  3. Deze JWT zal in de BE-services worden gebruikt om specifieke gegevens van eindgebruikers ( target_user ) zoals bestelstatus, productlijst, winkelwagentje enz. Nu heeft de medewerker van de klantenservice zich voorgedaan als de eindgebruike ( target_user).

Zoals eerder beschreven, heb ik een aangepast toekenningstype gebouwd en het user_impersonation genoemd. Dit aangepaste toekenningstype user_impersonation dat een toegangstoken van de klantenservicemedewerker accepteert en een nieuwe toegangstoken genereert voor een doelgebruiker. De flow werkt in de volgende volgorde:

Een voorbeeld van een oproep van een postbode naar APIM ziet er als volgt uit:

werking

Configuratie in Identity Server voor aangepast toekenningstype:

  • Voeg de onderstaande configuratie toe op {IS-Home}/repository/conf/identity.xml  
example identity server code
  • Creëer de jar met `mvn clean install` en implementeer het .jar-bestand in `{IS-Home}/repository/components/dropins/`
  • Voorbeeldcode voor het toekenningstype user_impersonation wordt hieronder weergegeven.

package org.wso2.custom.grant;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.identity.application.common.model.ApplicationPermission;
import org.wso2.carbon.identity.base.IdentityRuntimeException;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
import org.wso2.carbon.identity.oauth2.dto.OAuth2TokenValidationRequestDTO;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.model.RequestParameter;
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.identity.oauth2.validators.TokenValidationHandler;
import org.wso2.carbon.user.api.UserStoreException;
import org.wso2.carbon.user.api.UserStoreManager;
import org.wso2.carbon.user.core.service.RealmService;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

import java.util.ArrayList;
import java.util.Arrays;

public class MimickingGrantHandler extends AbstractAuthorizationGrantHandler {
    private static Log log = LogFactory.getLog(MimickingGrantHandler.class);
    public static final Logger audit_log = LoggerFactory.getLogger("AUDIT_LOG");

    @Override
    public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
        if(!super.validateGrant(tokReqMsgCtx)){
            return false;
        }
        boolean targetUserExist = false;
        boolean isBackUserAuthorized = false;
        RequestParameter [] oauth2RequestParameter=tokReqMsgCtx.getOauth2AccessTokenReqDTO().getRequestParameters();
        String accessToken="";
        String targetUser="";
        ArrayList<String> scopeList= new ArrayList<>();
        for (RequestParameter requestParam:oauth2RequestParameter) {
            String key = requestParam.getKey().trim();
            String value = requestParam.getValue()[0].trim();
            if(key.equalsIgnoreCase(MimickingGrantConstants.ACCESS_TOKEN)){
                accessToken=value;
            }
            if(key.equalsIgnoreCase(MimickingGrantConstants.TARGET_USER)){
                targetUser=value;
            }
            if(key.equalsIgnoreCase(MimickingGrantConstants.SCOPE)){
                scopeList.addAll(Arrays.asList(value.split(MimickingGrantConstants.SPACE)));
            }
        }

        boolean isValidToken=validateAccessToken(accessToken,MimickingGrantConstants.BEARER);
        AccessTokenDO accessTokenDO ;
        if(isValidToken){
            accessTokenDO= OAuth2Util.getAccessTokenDOfromTokenIdentifier(accessToken);
        }else {
            throw new IdentityOAuth2Exception("access token is invalid" );
        }
        if(log.isDebugEnabled()) {
            log.debug(" Access Token status : " +isValidToken +"\n"+
                    "Back office user : "+accessTokenDO.getAuthzUser().getUserName()+"\n"+
                    "Target_User = "+ targetUser +"\n");
        }

        /*
         * check if the target user exist and
         * if  user have valid token
         */
        targetUserExist=isTargetUserExist(targetUser);
        isBackUserAuthorized=isUserAuthorized(accessToken,getSPPermittedRoleList());


        if (isBackUserAuthorized && targetUserExist) {
            tokReqMsgCtx.setAuthorizedUser(OAuth2Util.getUserFromUserName(targetUser));
            scopeList.add(MimickingGrantConstants.USER_MIMICKING);
            String [] scope=scopeList.toArray(new String[scopeList.size()]);
            tokReqMsgCtx.setScope(scope);
        } else {
            if(!targetUserExist){
                throw new IdentityOAuth2Exception( targetUser +" doesn't exist" );
            }
            else {
                throw new IdentityOAuth2Exception("Authorization failed for " + accessTokenDO.getAuthzUser().getUserName());
            }

        }
        audit_log.info("Mimicking_Back-Office_User = "+accessTokenDO.getAuthzUser().getUserName() +"  Target_User = "+ targetUser +"  Status = Completed   User_Authenticated = "+ (isBackUserAuthorized && targetUserExist));
        return isBackUserAuthorized && targetUserExist;
    }

    private boolean validateAccessToken(String accessToken,String accessTokenType){
        try {
            TokenValidationHandler validationHandler = TokenValidationHandler.getInstance();
            OAuth2TokenValidationRequestDTO validationRequestDTO = new OAuth2TokenValidationRequestDTO();
            OAuth2TokenValidationRequestDTO.OAuth2AccessToken oAuth2AccessToken= validationRequestDTO.new OAuth2AccessToken();
            oAuth2AccessToken.setIdentifier(accessToken);
            oAuth2AccessToken.setTokenType(accessTokenType);
            validationRequestDTO.setAccessToken(oAuth2AccessToken);

            return validationHandler.validate(validationRequestDTO).isValid();
        } catch (IdentityOAuth2Exception e) {
            e.printStackTrace();
            log.error("error while validating access token");
        }
        return false;
    }
    /**
     *
     * Check if the impersonator user have the
     * @param accessToken
     * @return
     */
    private boolean isUserAuthorized(String accessToken,ArrayList <String> permittedRoleList){
        try {
            AccessTokenDO accessTokenDO= OAuth2Util.getAccessTokenDOfromTokenIdentifier(accessToken);
            OAuth2Util.getServiceProvider(accessTokenDO.getConsumerKey()).getSpProperties();
            String impersonatorUserName=accessTokenDO.getAuthzUser().getUserName();
            String tenantAwareUserName = MultitenantUtils.getTenantAwareUsername(impersonatorUserName);
            String userTenantDomain = MultitenantUtils.getTenantDomain(impersonatorUserName);
            String impersonatorCompleteUserName = tenantAwareUserName + "@" + userTenantDomain;
            int tenantId = IdentityTenantUtil.getTenantIdOfUser(impersonatorCompleteUserName);
            RealmService realmService = IdentityTenantUtil.getRealmService();
            UserStoreManager userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager();
            String [] roles = userStoreManager.getRoleListOfUser(
                    MimickingGrantConstants.USER_STORE_DOMAIN+
                            MimickingGrantConstants.FORWARD_SLASH+
                            impersonatorUserName);
            ArrayList<String> rolesList = new ArrayList<String>(Arrays.asList(roles));
            rolesList.retainAll(permittedRoleList);
            if(rolesList.size()>=1){
                return true;
            }
        }  catch (UserStoreException | IdentityOAuth2Exception e) {
            log.error("error occurred while authorizing user "+e.getMessage());
        }
        return false;
    }

    private boolean isTargetUserExist(String targetUser) {
        String tenantAwareUserName = MultitenantUtils.getTenantAwareUsername(targetUser);
        String userTenantDomain = MultitenantUtils.getTenantDomain(targetUser);
        String targetUserCompleteUserName = tenantAwareUserName + "@" + userTenantDomain;
        int tenantId = MultitenantConstants.INVALID_TENANT_ID;
        try {
            tenantId = IdentityTenantUtil.getTenantIdOfUser(targetUserCompleteUserName);
        } catch (IdentityRuntimeException e) {
            log.error("Token request with Mimicking Grant Type for an invalid tenant : " +
                    MultitenantUtils.getTenantDomain(targetUserCompleteUserName));
            return false;
        }
        RealmService realmService = IdentityTenantUtil.getRealmService();
        try {
            UserStoreManager userStoreManager = realmService.getTenantUserRealm(tenantId).getUserStoreManager();
           return userStoreManager.isExistingUser(targetUser);
        } catch (UserStoreException e) {
            log.error("error occurred while searching the user "+ targetUser +" ; "+e.getMessage());
        }
        return false;
    }
    /**
     * Get permitted role list which is allowed to use this grant type
     * @return
     */
    private ArrayList getSPPermittedRoleList(){
        ArrayList <String> permittedRoleList =new ArrayList <String>();
        permittedRoleList.add(MimickingGrantConstants.PERMITTED_ROLES);
        return permittedRoleList;
    }
}

Conclusie:

Met deze aanpak kunnen we met succes ons voordoen als een eindgebruiker in de WSO2 Identity Server