Pull to refresh
0
InterSystems
InterSystems IRIS: СУБД, ESB, BI, Healthcare

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

Reading time 15 min
Views 23K
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 {
	#Include %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)
Tags:
Hubs:
+11
Comments 7
Comments Comments 7

Articles

Information

Website
www.intersystems.com
Registered
Founded
1978
Employees
1,001–5,000 employees
Location
США