Привет, ребята, прежде чем я начну, позвольте мне предварить запись предупреждением о том, что это пример того, с чем я хотел поиграть, и его не следует копировать целиком для ваших приложений без тщательной проверки безопасности. Несколько месяцев назад меня довольно жестко атаковали в комментариях за то, что я допустил несколько ошибок в плане безопасности, поэтому я хочу, чтобы люди знали, что я публикую это как нечто, чем можно поделиться, но вы должны использовать его с осторожностью. Как всегда, если у вас есть конструктивные отзывы о том, насколько хороша/плоха/и т. д. эта демонстрация, я всегда рад прочитать ваши комментарии. Уф, извините за долгий отказ от ответственности. Давайте начнем, хорошо?

Еще в апреле я написал короткую демонстрацию веб-токенов JSON (JWT) и Auth0 для блокировки OpenWhisk (OpenWhisk, Serverless и Security — POC). В этой демонстрации я использую Auth0 и вход через социальные сети. Это сработало очень хорошо. Но предполагается, что вы просто хотите проверить пользователя, любого пользователя. Что, если вы хотите аутентифицироваться для определенного набора пользователей? Например, возможно, вы создаете интерфейс администратора для сайта и имеете один простой логин администратора. Это обычная вещь, которую я делал для многих сайтов, которые я создавал для клиентов. Хотя для внешнего интерфейса могут быть пользователи, администратор был более простой, отдельной пользовательской системой. Я решил создать простую демонстрацию того, как это может выглядеть.

Я начну с действия аутентификации.

const jwt = require('jsonwebtoken');
const creds = require('./creds.json');

function main(args) {
    return new Promise((resolve, reject) => {

        if(!args.username || !args.password) reject({message:'Invalid auth'});
        // hard coded auth
        if(args.username !== 'admin' || args.password !== 'letmein') reject({message:'Invalid auth'});

        let token = jwt.sign(args.username, creds.secret);
        resolve({
            token:token
        });

    });

}

exports.main = main;

Он начинается с базовой проверки аргументов. Затем он просто проверяет жестко закодированные значения имени пользователя и пароля. Обратите внимание, что я использую обещание для действия, хотя все синхронно. Я предполагаю, что в реальном приложении вы будете проверять учетные данные для службы или базы данных и, следовательно, не будете использовать полностью синхронное решение. После проверки учетных данных я создаю веб-токен JSON с помощью пакета npm, который я включил в начале. Подробнее о пакете можно прочитать здесь — https://www.npmjs.com/package/jsonwebtoken. Обратите внимание, что я не использую возможность добавления тайм-аута к токену. Я думаю — как правило — вам нужна разумная стоимость. Это можно было бы добавить так: jwt.sign(args.username, creds.secret, {'expiresIn':'1h'}). Да, и creds — это просто файл JSON, содержащий ключ для подписи и проверки:

{
    "secret":"mymilkshakeisbetterthanyoursdamnrightit"
}

Итак, это аутентификация. Что насчет проверки?

const jwt = require('jsonwebtoken');
const creds = require('./creds.json');

function main(args) {

    return new Promise((resolve, reject) => {
        let decoded = jwt.verify(args.token, creds.secret, (err,decoded) => {
            if(err) {
                console.log('err',err);
                reject({
                    name:err.name,
                    message:err.message,
                    stack:err.stack
                });
            } else {
                //passthrough, except token
                delete args.token;
                resolve(args);
            }

        });


    });

}

exports.main = main;

Я снова использую пакет jsonwebtoken для большей части работы. Я расшифровываю токен, и если он не работает, выдает ошибку. Если это удастся, обратите внимание, что я передаю каждый аргумент, отправленный в функцию, кроме самого токена. Почему? План здесь состоит в том, чтобы разрешить использование действия проверки в действии OpenWhisk. Я могу использовать его, чтобы заблокировать свои действия и разрешить передачу немаркерных аргументов по последовательности. Как бы это выглядело? Рассмотрим это очень простое действие «helloWorld»:

function main(args) {
    if(!args.name) args.name = 'Nameless';

    return { result: `Hello, ${args.name}`};
    
}

Учитывая, что мое проверочное действие называлось verify, а это helloWorld, я могу открыть заблокированную версию следующим образом:

wsk action update --sequence safeHelloWorld verify,helloworld --web true

Подводя итог, позвольте мне рассказать о том, что я построил.

  • У меня есть действие «auth», которое имеет веб-API. Это позволяет мне войти в систему и получить ответ JWT.
  • У меня есть действие «проверить», которое предназначено для использования в последовательности с другими бессерверными действиями OpenWhisk.
  • Наконец, я создал демонстрационное действие и последовательно связал его с действием проверки.

Чтобы проверить это, я быстро набросал внешний интерфейс Vue.js. Я собрал все это в один файл (чего обычно не делаю), так что давайте проверим:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>JWT Demo</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>

    <div id="app">

        <div v-if="needLogin">

            <h2>Login</h2>
            <p>
            <label for="username">Username</label>
            <input type="text" v-model="username" id="username" required>
            </p>
            <p>
            <label for="password">Password</label>
            <input type="password" v-model="password" id="password" required>
            </p>

            <p>
                <input type="submit" @click="login" value="Login">
            </p>

            <p v-if="invalidLogin">
                <b>Invalid Login.</b>
            </p>
                
        </div>
        <div v-else>
                <h2>Hello World Demo</h2>
                <p>
                <label for="name">Enter Name:</label>
                <input type="text" v-model="name" id="name" required>
                </p>

                <p>
                    <input type="submit" @click="helloWorld" value="Test">
                </p>

                <p v-if="nameResult"><b>Result: {{nameResult}}</b></p>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <script>
    const AUTH = 'https://openwhisk.ng.bluemix.net/api/v1/web/rcamden%40us.ibm.com_My%20Space/safeToDelete/auth.json';
    const HELLO = 'https://openwhisk.ng.bluemix.net/api/v1/web/rcamden%40us.ibm.com_My%20Space/safeToDelete/safeHelloWorld.json';

    const app = new Vue({
        el:'#app',
        data() {
            return {
                needLogin:true,
                username:null,
                password:null,
                invalidLogin:false,
                token:null,
                name:null,
                nameResult:null
            }
        },
        methods:{
            login() {
                this.invalidLogin = false;
                console.log('login');
                fetch(AUTH+'?username='+encodeURIComponent(this.username)+'&password='+encodeURIComponent(this.password))
                .then(res => res.json())
                .then(res => {
                    console.log('result',res);
                    if(res.message) {
                        this.invalidLogin = true;
                    } else if(res.token) {
                        this.token = res.token;
                        this.needLogin = false;
                    }
                });
            },
            helloWorld() {
                this.nameResult = '';
                if(this.name.trim() === '') return;
                fetch(HELLO+'?token='+encodeURIComponent(this.token)+'&name='+encodeURIComponent(this.name))
                .then(res => res.json())
                .then(res => {
                    console.log('result',res);
                    this.nameResult = res.result;
                });

            }
        }
    })
    </script>
</body>
</html>

Итак, сверху у нас есть простой макет, который использует v-if для динамического отображения или скрытия экрана входа в систему. Второй div обрабатывает форму, которая будет взаимодействовать с сервисом helloWorld, созданным ранее.

JavaScript в основном используется двумя способами: один для входа в систему и один для helloWorld. В обоих случаях я просто беру результаты и обновляю свои данные, а Vue обрабатывает обновление внешнего интерфейса. Обратите внимание, что login() запомнит значение токена, а helloWorld передаст его методу.

Вы можете запустить эту демонстрацию здесь: https://cfjedimaster.github.io/Serverless-Examples/jwtdemo/client.html

Помните, что имя пользователя admin, а пароль letmein. Вы можете найти полный исходный код для всех действий и внешнего интерфейса здесь: https://github.com/cfjedimaster/Serverless-Examples/tree/master/jwtdemo

Так что ты думаешь? Оставьте мне комментарий ниже.

Первоначально опубликовано на www.raymondcamden.com 22 декабря 2017 г.