Session Handling on CPI – Part 2
2024-1-7 21:57:42 Author: blogs.sap.com(查看原文) 阅读量:17 收藏

Introduction:

Hello all, Happy New Year 2024 🎆

This is the continuation of my first blog. I strongly recommend reading this before you jump onto this.

https://blogs.sap.com/2023/12/16/session-handling-on-cpi-part-1/

Methodology used:

As I mentioned at the end of my previous blog, we want to use two different users for services, but if we go with a message exchange/integration flow, I told the disadvantages.

Change the session handling to None😐. Then how?

  1. Log in with the first credential. We will get the response in the header Set-Cookie.
  2. Store the cookie in the global variable using the write variable option.
  3. Before calling the service, explicitly add the cookie header via the content modifier.
  4. Repeat the same strategy for another user.

So if the old cookie expires, a new session generated will overwrite the variable with new cookie details. Storing the cookie/session in a plain format over a variable is not a good practice.

S, I added one extra step with the help of hashing on nodejs and secure parameters on CPI.

I am generating hash on the node side with

  • Message -> session ID + user_secret( secure parameter stored in CPI and transferred via custom header )

The resultant hash will be stored on the session store against the session ID. So each time with a cookie they have to pass this secret. We will derive the hash again. If the derived hash and hash in the session match then only the session is valid, otherwise we can invalidate the session.

So if someone accesses the cookie, they are unable to use it until they get this secure parameter.

(i.e.: Hashing is also the costliest function in terms of execution, Maybe if there is a better secure way to store the cookie on the CPI side, please tell on comments ).

Steps:

Two global variables for storing the respective cookie

  1. session_normal
  2. session_admin

user_secret – Passed on customer header and used on hashing at server side.

  1. Get the user_secret  and transfer via custom header to the node.
  2. Check if general_user_cookie is available on the variable store. If available then set it onto the request header cookie and call the login endpoint.
  3. If the cookie is valid, the server performs the hashing and compares the hash. In case of the match it returns the status that the session was authorized else returns an error.
  4. If the cookie is expired/invalid/not passed, the server tries to authenticate and return the session in the Set-Cookie response header.
  5. If the 4th point has happened, then update the variable session_normal with a new one and modify the request header also.
  6. Call the employee endpoint.
  7. Repeat the same thing for the admin user with appropriate changes on settings.

( Attaching All images in last section  related to iflow)

Node JS code:

const express = require('express')
var parser = require('body-parser')
var session = require('express-session')
const bcrypt = require("bcrypt")

const expressApp = express(); // Created Express App

//URL PARSING Middleware
expressApp.use(parser.urlencoded({
    extended: true
}))
// New Mapping for storing Hash
let session_hash_map = new Map();

//JSON PARSING Middleware
expressApp.use(parser.json());
// SESSION Middleware
expressApp.use(session({
    secret: 'dsufhodsfhdiajdbadkjbaoodhdjbfadljqslvsoif', // Usually a long secret maintained in environment. For testing I maintained here
    cookie: {
        httpOnly: true, // Prevent reading the cookie from other than HTTP
        maxAge: 150000, // Validity of session on milliseconds
        domain: 'localhost' // Domain for cookie
    },
    resave: true,
    saveUninitialized: false,
    name: 'Session_ID' // Name on session id cookie on response
}))
//Login Route
expressApp.get("/login", function (req, res, next) {
    const message = req.session.id + req.headers.secretid // Session ID + secret coming from CPI
    // console.log("Message to Hash:",message)
    if (req.session.loggedin || req.session.loggedinadmin) { // IF already loggedin
        console.log(req.session.id)
        if (req.session.loggedin) {
            console.log("Session Founded Normal User")
        } else {
            console.log("Session Founded Admin User")
        }
        // COmpares with original hash that stored during first time login
        bcrypt.compare(message, session_hash_map.get(req.session.userid), function (err, result) {
            console.log(result)
            if (result) {
                res.set('custom_status', 'A')

                res.status(200).send({
                    "message": "Session Authorized succesfully"
                })
            }
            //If hash not matches
            else {
                session_hash_map.delete(req.session.userid)
                req.session.destroy();
                res.status(401).send({
                    "message": "Session invalidated . Please Login Again"
                })

            }

        });
    } else {
        const auth = new Buffer.from(req.headers.authorization.split(' ')[1], 'base64').toString().split(':'); // Getting user name and password from auth header
        const user = auth[0];
        const pass = auth[1];

        if (user == "SAPCPI" && pass == '12345') { // For simplicity purpose 
            req.session.userid = user // user id property
            req.session.loggedin = true; // Logged In Normal
            bcrypt.genSalt(10, function (err, salt, res) {
                // Hash and store it on map 🧑‍💻
                console.log("New Login Normal")

                bcrypt.hash(message, salt, function (err, hash) {
                    console.log("Derived hash Normal", hash) // Printing Hash
                    session_hash_map.set(user, hash) // Setting Hash

                });

            })
            res.set('custom_status', 'N')
            res.status(200).send({
                "message": "Logged In succesfully and session set for 1 hour"
            })

        } else if (user == "SAPCPI_ADMIN" && pass == '98765') {
            req.session.userid = user
            req.session.loggedinadmin = true; // Logged in Admin
            bcrypt.genSalt(10, function (err, salt, res) {
                // Hash and store it on map 🧑‍💻
                bcrypt.hash(message, salt, function (err, hash) {
                    console.log("New Login Admin")

                    console.log("Derived hash Admin", hash) // Printing Hash
                    session_hash_map.set(user, hash) // Setting Hash


                });

            })
            res.set('custom_status', 'N')

            res.status(200).send({
                "message": "Logged In succesfully and session set for 1 hour"
            })

        } else {
            res.status(401).send({
                "message": "Invalid Credentials. Please try again"
            })
        }
    }
});
// Employee Details
expressApp.get('/getemployeedetails', function (req, res) {
    if (req.session.loggedin) {
        console.log("Giving Employee Details")
        res.status(200).send({
            "meessage": "Here you can found employee details"
        })
    } else {
        res.status(401).send({
            "message": "Unauthorized. Please login"
        })
    }
})
// Customer Details
expressApp.get('/getcustomerdetails', function (req, res) {
    if (req.session.loggedin) {
        console.log("Giving Customer Details")

        res.status(200).send({
            "meessage": "Here you can found vendor details"
        })
    } else {
        res.status(401).send({
            "message": "Unauthorized. Please login"
        })
    }
})
// Payment details
expressApp.get('/getpaymentdetails', function (req, res) {
    if (req.session.loggedinadmin) {
        console.log("Giving Payment Details")

        res.status(200).send({
            "meessage": "Here you can found payment details"
        })
    } else {
        if (req.session.loggedin) {
            res.status(401).send({
                "message": "You have no access to payment data.Contact Admin"
            })

        } else {
            res.status(401).send({
                "message": "Unauthorized. Please login"
            })

        }
    }
})
expressApp.listen(8086, function () {
    console.log("Server Listening on http://localhost:8086")
});

Iflow Design :

Iflow

Iflow-1

Iflow-2

Iflow-2

Setting%20secret%20in%20custom%20header

Setting secret in a custom header

Set%20Cookie%20For%20General%20User

Set Cookie For General User

Login%20Call-General%20User

Login Call-General User

Route

Route

Write%20Cookie%20in%20variable

Write a Cookie in the variable

Set%20cookie%20in%20header

Set cookie in the header

Endpoint%20Call

Endpoint Call

Overwrite%20cookie

Overwrite cookie

The process will be repeated for another set.

Cookie%20in%20Variable

Cookie in Variable

1st%20call

1st call

Further%20call%20until%20session%20expires

Further, call until the session expires

output

output

Summary

In these two parts, I explained the session handling mechanism and how actually it works with the help of Node JS.

As I said this requirement is all self-created for this blog purpose, I hope it will give better/deep insight into session handling with nooks and corners which will be used in realtime scenarios.

If you have any queries/suggestions ( over CPI / Node JS ) feel free to comment.

Thanks…


文章来源: https://blogs.sap.com/2024/01/07/session-handling-on-cpi-part-2/
如有侵权请联系:admin#unsafe.sh