Аутентификация пользователей на Arduino с RFID

от автора

Arduino

Введение

В предыдущей статье я только начинал работать с Arduino, в результате чего закономерно получилась метеостанция. В этой статье пойдём дальше — будем делать аутентификацию с помощью RFID карт и Arduino в приложении InterSystems Caché.

Передача аутентификации

В Caché есть механизм делегирования аутентификации — передачи процесса аутентификации пользовательскому коду. Чтобы его включить, надо сделать следующее:

  1. Написать код аутентификации пользователей в рутине ZAUTHENTICATE. У неё есть 4 точки входа: получения логина/пароля, их проверка и назначение прав, смена пароля, формирование токена. Подробнее об этом ниже.
  2. Включить передачу аутентификации в Caché (SMP → System Administration → Security → System Security → Authentication/CSP Session Options, установите флаг Allow Delegated authentication и сохраните настройки).
  3. Включить передачу аутентификации для требуемых сервисов (SMP → Menu → Manage Services → Сервис → Allowed Authentication Methods → выбрать Delegated → Save) и/или приложений (SMP → Menu → Manage Web Applications → Приложение → Allowed Authentication Methods → выбрать Delegated → Save).

Как это работает

Вот что происходит, когда пользователь аутентифицируется в сервисе или веб-приложении, для которого включена передача аутентификации:

  1. Вызывается рутина ZAUTHENTICATE. Код этой рутины пишется пользователем и может быть любым Caché ObjectScript кодом, в том числе и $ZF вызовы.
  2. Следующий шаг зависит от того, был ли успешным вызов ZAUTHENTICATE:
    • Если вызов ZAUTHENTICATE успешен и это первый раз, когда данный пользователь аутентифицируется с ZAUTHENTICATE, то для него создаётся запись пользователя типа “Delegated user”. Если ZAUTHENTICATE назначает пользователю права или другие свойства, они становятся соответствующими свойствами пользователя.
    • Если вызов ZAUTHENTICATE успешен и это не первый раз, когда данный пользователь аутентифицируется с ZAUTHENTICATE, то его запись пользователя обновляется.
    • Если вызов ZAUTHENTICATE не успешен, пользователю выдаётся ошибка доступа.
  3. Если для инстанса и сервиса включена двухфакторная аутентификация, то производится поиск номера телефона пользователя и оператора. Если они заданы, происходит двухфакторная аутентификация, если нет — юзер не аутентифицируется.
  4. Делегированный пользователь отображается в таблице пользователей.

Откуда пользователи?

Есть два метода аутентификации в зависимости от того какие способы аутентификации включены для приложения/сервиса:

  • Delegated — имя/пароль берутся из GetCredentials, проверяются средствами ZAUTHENTICATE (тип пользователя — делегированный).
  • Delegated и Password — имя/пароль берутся из GetCredentials, проверяются стандартными механизмами Caché (тип пользователя — Caché).

Теперь перейдём к рассмотрению рутины ZAUTHENTICATE и её точек входа.

ZAUTHENTICATE

Эта основная рутина, содержащая 4 точки входа.

▍GetCredentials

Эта точка входа вызывается в том случае, когда передача аутентификации включена для сервиса, и она вызывается вместо запроса логина/пароля у пользователя. Код этой рутины устанавливает логин и пароль (любым способом). Впоследствии (вне этой рутины) полученные логин и пароль аутентифицируются, будто пользователь их ввёл как обычно. Метод получения логина и пароля может быть любой — ввод с клавиатуры, API, считывание внешнего устройства — в этой статье будем использовать аутентификацию с помощью RFID карты.

Данная точка входа возвращает статус, и если это ошибка, то она будет записана в аудит, а попытка аутентификации будет отклонена. Исключение — ошибка $SYSTEM.Status.Error($$$GetCredentialsFailed), в таком случае пользователю предложат ввести логин/пароль стандартным методом Caché. Сигнатура следующая:

GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { }

Где:

  • ServiceName — название сервиса, через который идёт подключение
  • Namespace — область, если указана при подключении
  • Username — имя пользователя
  • Password — пароль
  • Credentials — в настоящее время не используется

Отмечу важную особенность этой точки входа. Если для сервиса/приложения включена и передача аутентификации и обычная аутентификация по паре логин/пароль (Password Authentication), то логин и пароль, полученные через GetCredentials, будут использованы для стандартной аутентификации по паролю.

▍ZAUTHENTICATE

В случае если первоначальная аутентификация успешна, ZAUTHENTICATE устанавливает роли и другие свойства пользователя. В случае если это не первая аутентификация, свойства могут быть изменены. Для этого в коде рутины устанавливаются свойства массива Properties. Сигнатура:

ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) Public { } 

Массив Properties:

  • Properties("Comment") — комментарий
  • Properties("FullName") — имя и фамилия
  • Properties("NameSpace") — стартовая область
  • Properties("Roles") — список ролей через запятую
  • Properties("Routine") — стартовая рутина
  • Properties("Password") — пароль
  • Properties("Username") — имя пользователя
  • Properties("PhoneNumber") — телефонный номер пользователя
  • Properties("PhoneProvider") — оператор телефона
  • Properties("AutheEnabled") — включить стандартную двухфакторную аутентификацию (для этого надо установить значение, равное $$$AutheTwoFactorSMS)

▍ChangePassword

Точка входа для смены пароля пользователя. Сигнатура следующая:

ChangePassword(Username, NewPassword, OldPassword, Status) Public { }

Где:

  • NewPassword — новый пароль
  • OldPassword — старый пароль
  • Status — результат операции изменения пароля

▍SendTwoFactorToken

Для использования в стандартной двухфакторной аутентификации. Определяет формат запроса и токена аутентификации. Сигнатура:

SendTwoFactorToken(Username, ServiceName,Namespace,Application,Credentials,SecurityToken,TwoFactorTimeout,UserPhoneNumber) Public { }

Где:

  • Application — CSP приложение или рутина, к которой подключается пользователь
  • SecurityToken — токен, который будет отправлен пользователю
  • TwoFactorTimeout — время действия токена
  • UserPhoneNumber — телефонный номер пользователя

Пример

Для начала покажу простейший пример для терминала Caché в Windows — сервиса %Service_Console, который будет спрашивать логин и пароль у пользователя. Включим передачу аутентификации в системе для этого сервиса. После этого напишем рутину ZAUTHENTICATE (в области %SYS):

ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC { 	#Iinclude %occErrors 	#Include %occStatus 	Quit $$$OK }  GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { 	#Include %occErrors 	#Include %occStatus 	 	Do ##class(%Prompt).GetString("USER:",.Username) 	Do ##class(%Prompt).GetString("PASS:",.Password)	 	Quit $$$OK  }

В терминале это будет выглядеть аналогично обычному логину.

>USER: _SYSTEM >PASS: SYS

RFID

Перейдём к аутентификации на RFID. Идея состоит в следующем — из Caché сделаем возможность записывать в зашифрованном виде информацию на карточку, а при аутентификации будем её считывать, расшифровывать и возвращать на проверку.

Для начала соберём схему из Arduino Uno и модуля RFID-RC522:

Вот код на С, использующий библиотеку MF522 (там же есть распиновка для других моделей Arduino). Он по COM порту принимает 2 команды:

  • Get — по ней на com порт передаётся содержимое блоков RFID карты 2, 4, 5, 6
  • Set@bloc2@bloc4@bloc5@bloc6 — по ней содержимое блоков 2, 4, 5, 6 на карте перезаписывается пришедшими данными

Код на C

#include <SPI.h> //include the SPI bus library #include <MFRC522.h> //include the RFID reader library  #define SS_PIN 10  //slave select pin #define RST_PIN 9  //reset pin  #define u1b 2  //Block on a card for user1 byte array #define u2b 4  //Block on a card for user2 byte array #define p1b 5  //Block on a card for pass1 byte array #define p2b 6  //Block on a card for pass2 byte array  MFRC522 mfrc522(SS_PIN, RST_PIN);        // instatiate a MFRC522 reader object. MFRC522::MIFARE_Key key; //create a MIFARE_Key struct named 'key', which will hold the card information  byte readbackblock[18]; //This array is used for reading out a block. The MIFARE_Read method requires a buffer that is at least 18  String inString = ""; // COM port incoming data buffer  void setup() {   Serial.begin(9600);        // Initialize serial communications with the PC   SPI.begin();               // Init SPI bus   mfrc522.PCD_Init();        // Init MFRC522 card (in case you wonder what PCD means: proximity coupling device)   // Serial.println("Scan a MIFARE Classic card");    // Prepare the security key for the read and write functions - all six key bytes are set to 0xFF at chip delivery from the factory   // Since the cards in the kit are new and the keys were never defined, they are 0xFF   // if we had a card that was programmed by someone else, we would need to know the key to be able to access it.    // This key would then need to be stored in 'key' instead.    for (byte i = 0; i < 6; i++) {     key.keyByte[i] = 0xFF; // keyByte is defined in the "MIFARE_Key" 'struct' definition in the .h file of the library   } }  void loop() {   // put your main code here, to run repeatedly:   // Receive data from com port   while (Serial.available() > 0) {     int inChar = Serial.read();      if (inChar != '\n') {       inString += (char)inChar;      } else {       // New line       while (!initCard()); // connect to an RFID card       String Action = inString.substring(0, 3);       if (Action == "Set") {         // Write login and pass into the card         setUserAndPassToCard(inString);       } else if (Action == "Get") {         // Read login and pass from the card         readUserAndPassToCom();       } else {         Serial.println(Action);       }              disconnectCard(); // disconnect RFID card       inString = "";     }   } }  /// Read blocks with user/pass info and output the to COM port: /// user1user2@pass1pass2 void readUserAndPassToCom() {   readBlockToCom(u1b);   readBlockToCom(u2b);   Serial.write("@");   readBlockToCom(p1b);   readBlockToCom(p2b);   Serial.println(""); }  /// Set user/pass info into a card /// Data: Set@user1@user2@pass1@pass2 /// Data sample: Set@1234567890123456@1234567890123456@1234567890123456@1234567890123456 void setUserAndPassToCard(String Data) {   // Serial.println(Data);   byte user1[16], user2[16], pass1[16], pass2[16];    String user1str = inString.substring(4, 20);   String user2str = inString.substring(21, 37);   String pass1str = inString.substring(38, 54);   String pass2str = inString.substring(55, 71);    stringToArray(user1str, user1, sizeof(user1));   stringToArray(user2str, user2, sizeof(user2));   stringToArray(pass1str, pass1, sizeof(pass1));   stringToArray(pass2str, pass2, sizeof(pass2));    writeBlock(u1b, user1); // u1b is the block number, user1 is the block content   writeBlock(u2b, user2);   writeBlock(p1b, pass1);   writeBlock(p2b, pass2);      Serial.println("Done"); }  void stringToArray(String str, byte array[], int arrlength) {   for (int j = 0 ; j < arrlength ; j++)   {     array[j] =  str.charAt(j);   } }  bool initCard() {   // Look for new cards (in case you wonder what PICC means: proximity integrated circuit card)   if ( ! mfrc522.PICC_IsNewCardPresent()) {//if PICC_IsNewCardPresent returns 1, a new card has been found and we continue     return false; //if it did not find a new card is returns a '0' and we return to the start of the loop   }    // Select one of the cards   if ( ! mfrc522.PICC_ReadCardSerial()) {//if PICC_ReadCardSerial returns 1, the "uid" struct (see MFRC522.h lines 238-45)) contains the ID of the read card.     return false; //if it returns a '0' something went wrong and we return to the start of the loop   }   return true; }  void disconnectCard() {   // Halt PICC   mfrc522.PICC_HaltA();   // Stop encryption on PCD   mfrc522.PCD_StopCrypto1(); }  void readBlockToCom(int number) {   readBlock(number, readbackblock);//read the block back   for (int j = 0 ; j < 16 ; j++) //print the block contents   {     Serial.write (readbackblock[j]);//Serial.write() transmits the ASCII numbers as human readable characters to serial monitor   } }  int writeBlock(int blockNumber, byte arrayAddress[]) {   // this makes sure that we only write into data blocks. Every 4th block is a trailer block for the access/security info.   int largestModulo4Number = blockNumber / 4 * 4;   int trailerBlock = largestModulo4Number + 3; //determine trailer block for the sector   if (blockNumber > 2 && (blockNumber + 1) % 4 == 0) {     Serial.print(blockNumber);  //block number is a trailer block (modulo 4); quit and send error code 2     Serial.println(" is a trailer block:");     return 2;   }   //Serial.print(blockNumber);   //Serial.println(" is a data block:");    /*****************************************authentication of the desired block for access***********************************/   byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));   // byte PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, Uid *uid);   // this method is used to authenticate a certain block for writing or reading   // command: See enumerations above -> PICC_CMD_MF_AUTH_KEY_A	= 0x60 (=1100000),		   // this command performs authentication with Key A   // blockAddr is the number of the block from 0 to 15.   // MIFARE_Key *key is a pointer to the MIFARE_Key struct defined above, this struct needs to be defined for each block.    // New cards have all A/B= FF FF FF FF FF FF   // Uid *uid is a pointer to the UID struct that contains the user ID of the card.   if (status != MFRC522::STATUS_OK) {     Serial.print("PCD_Authenticate() failed: ");     Serial.println(mfrc522.GetStatusCodeName(status));     return 3;//return "3" as error message   }   // it appears the authentication needs to be made before every block read/write within a specific sector.   // If a different sector is being authenticated access to the previous one is lost.    /*****************************************writing the block***********************************************************/    status = mfrc522.MIFARE_Write(blockNumber, arrayAddress, 16);   //valueBlockA is the block number, MIFARE_Write(block number (0-15), byte array containing 16 values, number of bytes in block (=16))   // status = mfrc522.MIFARE_Write(9, value1Block, 16);   if (status != MFRC522::STATUS_OK) {     Serial.print("MIFARE_Write() failed: ");     Serial.println(mfrc522.GetStatusCodeName(status));     return 4;//return "4" as error message   }   //Serial.println("block was written"); }  int readBlock(int blockNumber, byte arrayAddress[]) {   int largestModulo4Number = blockNumber / 4 * 4;   int trailerBlock = largestModulo4Number + 3; //determine trailer block for the sector    /*****************************************authentication of the desired block for access********************************************/   byte status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));   // byte PCD_Authenticate(byte command, byte blockAddr, MIFARE_Key *key, Uid *uid);   // this method is used to authenticate a certain block for writing or reading   // command: See enumerations above -> PICC_CMD_MF_AUTH_KEY_A	= 0x60 (=1100000),		   // this command performs authentication with Key A   // blockAddr is the number of the block from 0 to 15.   // MIFARE_Key *key is a pointer to the MIFARE_Key struct defined above, this struct needs to be defined for each block.    // New cards have all A/B= FF FF FF FF FF FF   // Uid *uid is a pointer to the UID struct that contains the user ID of the card.   if (status != MFRC522::STATUS_OK) {     Serial.print("PCD_Authenticate() failed (read): ");     Serial.println(mfrc522.GetStatusCodeName(status));     return 3;//return "3" as error message   }   // it appears the authentication needs to be made before every block read/write within a specific sector.   // If a different sector is being authenticated access to the previous one is lost.    /*****************************************reading a block***********************************************************/    byte buffersize = 18;//we need to define a variable with the read buffer size, since the MIFARE_Read method below needs a pointer to the variable that contains the size...   status = mfrc522.MIFARE_Read(blockNumber, arrayAddress, &buffersize);//&buffersize is a pointer to the buffersize variable; MIFARE_Read requires a pointer instead of just a number   if (status != MFRC522::STATUS_OK) {     Serial.print("MIFARE_read() failed: ");     Serial.println(mfrc522.GetStatusCodeName(status));     return 4;//return "4" as error message   } }

Класс Arduino.Delegate, который имеет 2 точки входа:

  • SetCredentials — принимает на вход логин и пароль, шифрует их AES шифрованием с помощью ключа хранящегося в системе и записывает на RFID карту.
  • GetCredentials — получает шифротекст с карты и расшифровывает его, возвращая логин, пароль и статус операции.

Arduino.Delegate

/// Delegated Authentication with Arduino. /// Installation steps:<br> /// 1. Connect arduino (and upload C code from Delegated.ino there)<br> /// 2. Make this class visible in %SYS namespace (import there or map pckage)<br> /// 3. Set SerialPort parameter to a correct value and recompile the class<br> /// 4. Run <example>Do ##class(Arduino.Delegated).InitEncryption(Key, IV)</example> /// 5. Write encrypted user credentials to RFID card  with SetCredentials<br> /// 6. Import ZAUTHENTICATE into %SYS<br> /// 7. Enable Delegated and password auth for relevant services and/or apps Class Arduino.Delegated [ Abstract ] {  Parameter SerialPort As %String = "com3";  /// Creates managed encryption key.<br> /// key - Input key material. /// Key material 16, 24, or 32 characters long (on Unicode systems, with all character values < 256) is used directly. /// Otherwise, Password-Based Key Derivation Function #2 (PBKDF2) /// is used with HMAC-SHA-1, /// no salt, and one iteration /// to generate an AES key of the next larger valid size (up to 32 bytes). /// (See RSA Laboratories Public-Key Cryptography Standards #5 for more information.) /// <br><br> /// IV - Initialization vector (optional). /// If this argument is present it must be 16 characters long (on Unicode systems, with all character values < 256). /// If this argument is omitted (or is an empty string), a null initialization vector is used. /// <br> /// <example>Do ##class(Arduino.Delegated).Init("", "")</example> ClassMethod Init(Key As %String, IV As %String) { 	New $Namespace 	Set $Namespace = "%SYS" 	Set ^Arduino("Key")= Key 	Set ^Arduino("IV")= IV }  /// Send Arduino the command to set credentials on a card to Username/Password (encrypted) /// <example>Do ##class(Arduino.Delegated).SetCredentials("_SYSTEM", "SYS")</example> ClassMethod SetCredentials(Username As %String(MAXLEN=15), Password As %String(MAXLEN=15)) As %Status { 	 	Set Status = $$$OK 	 	Set CipherUsername = ..EncryptText(Username) 	Set CipherPassword = ..EncryptText(Password) 	 	Set User1 = $Extract(CipherUsername, 1, 16) 	 	Set User2 = $Extract(CipherUsername, 17, 32) 	Set User2 = ..AppendToString(User2, , 16) 	 	Set Pass1 = $Extract(CipherPassword, 1, 16) 	 	Set Pass2 = $Extract(CipherPassword, 17, 32) 	Set Pass2 = ..AppendToString(Pass2, , 16) 	 	Set CommandList = $ListBuild("Set", User1, User2, Pass1, Pass2) 	Set Command = $ListToString(CommandList, "@")  	Set Status = ..ExecuteCommand(.Command) 	If (Status = "Done") { 		Set Status = $$$OK 	} Else { 		Set Status = $$$ERROR($$$GeneralError, "SetCredentials failure, received: " _ Status)  	} 	 	Return Status }  /// Connect to an Arduino device, receive credentials, decode them and set to Username/Password variables. /// <example>do ##class(Arduino.Delegated).GetCredentials(.Username, .Password)</example> ClassMethod GetCredentials(Output Username As %String, Output Password As %String) As %Status { 	Kill Username, Password 	Set Username = "" 	Set Password = "" 	Set Status = $$$OK 	 	Set Credentials = ..ExecuteCommand("Get") 	If (($L(Credentials) =65) && ($L(Credentials,"@") = 2)) { 		Set CipherUsername = $Piece(Credentials, "@", 1) 		Set CipherPassword = $Piece(Credentials, "@", 2) 		Set CipherUsername = $Extract(CipherUsername, 1, 24) // we need only first 24 characters 		Set CipherPassword = $Extract(CipherPassword, 1, 24) 		Set Username = ..DecryptText(CipherUsername) 		Set Password = ..DecryptText(CipherPassword) 	} Else { 		Set Status = $$$ERROR($$$GeneralError, "GetCredentials failure, received: " _ Credentials)  	} 	 	Return Status }  /// Send one line at a time, using common terminating characters (i.e., CR) and receive output /// Possible comands:<br> /// <b>Get</b> - reads an RFID card and returns information in a format: user@pass<br> /// <b>Set@user1@user2@pass1@pass2</b> - sets information on a RFID card  /// in a format: user@pass (where user = user1@user2)<br> /// Returns output, produced by Arduino /// <example>w ##class(Arduino.Delegated).ExecuteCommand("Get")</example> ClassMethod ExecuteCommand(ByRef Command As %String, SerialPort = {..#SerialPort}) As %String { 	set x="" 	try {         //Parameters used to open the serial device:         //  portstate = " 0801n0" - by byte position:         //      1: space indicates "don't disconnect the port"         //      2: 0 indicates "don't use modem control"         //      3: 8 indicates 8 data bits         //      4: 0 indicates no parity         //      5: 1 indicates one stop bit         //      6: n indicates that flow control is disabled         //      7: 0 indicates disable DTR         //  /BAUD=9600 determines the baud rate, of course.         open SerialPort:(:::" 0801n0":/BAUD=9600)         set old = $io //Keep track of the original device         use SerialPort  	write $char(10)         hang 1         write Command _ $Char(10)                  read x //Read until a termination character is reached         use old         close SerialPort            } catch ex {         close SerialPort         w $System.Status.GetErrorText(ex.AsStatus())     }          return x }  /// Get key to encode/decode via EncryptText/DecryptText ClassMethod GetKey() [ CodeMode = expression ] { $Get(^Arduino("Key")) }  /// Get IV to encode/decode via EncryptText/DecryptText ClassMethod GetIV() [ CodeMode = expression ] { $Get(^Arduino("IV")) }  /// Encrypt PlainText with AESCBCEncrypt /// <example>Write ##class(Arduino.Delegated).EncryptText("string")</example> ClassMethod EncryptText(PlainText As %String) As %String { 	 	Set Text=$ZConvert(PlainText,"O","UTF8") 	Set Text=$System.Encryption.AESCBCEncrypt(Text, ..GetKey(), ..GetIV()) 	Set Ciphertext=$System.Encryption.Base64Encode(Text) 	Return Ciphertext }  /// Decrypt PlainText with AESCBCEncrypt /// <example>Write ##class(Arduino.Delegated).DecryptText("sFgKzZVle187N4OqhhcXPw==")</example> ClassMethod DecryptText(CipherText As %String) As %String { 	Set Text=$System.Encryption.Base64Decode(CipherText) 	Set Text=$System.Encryption.AESCBCDecrypt(Text, ..GetKey(), ..GetIV()) 	Set PlainText=$ZConvert(Text,"I","UTF8")  	Return PlainText }  /// Extends right side of a String by Character up to Length chars /// <example>Write ##class(Arduino.Delegated).AppendToString("")</example> ClassMethod AppendToString(String As %String, Character As %String(MAXLEN=1) = "_", Length As %Integer = {$Length(String)}) As %String { 	Set Difference = Length - $Length(String)   	Return:Difference<=0 String 	Set Tail = $Justify("", Difference) 	Set Tail = $Translate(Tail, " ", Character) 	Return String _ Tail }  }

Рутина ZAUTHENTICATE, которая вызывает класс Arduino.Delegated, метод GetCredentials:

ZAUTHENTICATE(ServiceName, Namespace, Username, Password, Credentials, Properties) PUBLIC { 	#Include %occStatus 	Quit $$$OK }  GetCredentials(ServiceName, Namespace, Username, Password, Credentials) Public { 	#Include %occErrors 	#Include %occStatus	 	Quit ##class(Arduino.Delegated).GetCredentials(.Username, .Password)  }

Готово! Собранное устройство выглядит вот так:

Устанавливаем ключи шифрования в терминале, области %SYS (там должен быть доступен класс Arduino.Delegated):

Do ##class(Arduino.Delegated).InitEncryption(Key, IV)

Где Key — ключ шифрования, IV — вектор инициализации. Они будут использоваться для шифрования логина и пароля. Подключаем Arduino к Caché и записываем на карточку информацию для аутентификации командой:

Do ##class(Arduino.Delegated).SetCredentials("_SYSTEM", "SYS")

Включаем аутентификацию Delegated и Password в нужных сервисах/веб-приложениях и можно аутентифицироваться (например, в терминале или портале управления системой) поднося карту к считывателю RFID карт.

Возможные улучшения

  • Повышение уровня безопасности с помощью использования управляемых ключей шифрования для шифрования логина и пароля.
  • Повышение уровня безопасности с помощью использования двухфакторной аутентификации — сначала получать пару логин/пароль, а потом считывать карту, на которой хранится ключ, уникальный для пользователя. Затем нужно сверять полученный ключ с тем, который хранится в системе для данного пользователя. Варианты хранения произвольных данных пользователя обсуждались на Community портале InterSystems.
  • Добавить возможность хранения логина и пароля длиннее 15 символов каждый.

Выводы

Гибкая система аутентификации Caché позволяет реализовать произвольную логику аутентификации пользователей.

Ссылки

» Документация
» GitHub репозиторий с кодом (в области SAMPLES есть пример рутины ZAUTHENTICATE)

ссылка на оригинал статьи https://habrahabr.ru/post/279893/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *