OAuth с использованием JWT на salesforce

Website development
Tutorial

Общая информация по JWT потоку OAuth





JSON Web Token (JWT, произносится как английское слово jot) это зашифрованный JSON секюрити токен (security token), который делает возможной идентификацию и предоставление защищенного доступа к информации со стороннего домена.

Пропустить теорию.

OAuth 2.0 JWT Bearer Token поток определяет как JWT-токен может быть использован для запроса OAuth access токен из Salesforce, когда клиентская сторона хочет воспользоваться ранее пройденой авторизацией. Данные входящие в JWT содержат имя пользователя (username), для которого нужен access токен, а так же идентификатор приложения, которое запрашивает доступ (client_id). Аутентификация запращивающего приложения предоставляется посредством применения цифровой подписи к JWT — т.е. json токен должен быть закодирован c помощью RSA ключа.

JWT Bearer Token поток поддерживает RSA/SHA256 алгоритм, т.е. вы должны загрузить подписаный сертификат в на странице конфигурации вашего Connected App, это позволит Salesforce удостовериться в том что ваш токен пришел из доверенного источника.

При использовании JWT Bearer Token потока, сам JWT-токен отсылается c помощю POST запроса (1) на url называемый OAuth endpoint — login.salesforce.com/services/oauth2/token и содержит следующие данные (payload)

grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=PHN...QZT
grant_type Set this to urn:ietf:params:oauth:grant-type:jwt-bearer
assertion The JWT bearer token.


Сервер проверяет JWT и создает токен доступа (access_token) (2), на основании ранее предоставленного приложению доступа. Однако, клиентская сторона не обязана хранить refresh_token (как при других OAuth потоках), в данном случае клиентское приложение генерирует новый JWT-токен когда необходимо получить access_token. Так же обратите внимание, что client_secret не является необходимым при отсылке запроса на получение токена — JWT авторизует приложение.

Больше теории про OAuth 2.0 JWT Bearer Token поток и посмотреть пример на java можно прочитать здесь — OAuth 2.0 JWT Bearer Token Flow.


Генерация приватного ключа и сертификата



JWT Bearer Flow требует RSA SHA256 сертификата (X.509). Сгенерировать это пару можно с помощью openssl утилиты. Ниже пример коммандной строки для генерации ключа и сертификата
openssl req -x509 -nodes -subj "/C=US/ST=CA/L=San Francisco/CN=web_site.com" -days 3650 -newkey rsa:2048 -keyout your_private_key.pem -out your_certificate.pem




your_private_key.pem — будет использован в клиентском приложении
your_certificate.pem — будет загружен в Connected app на salesforce (куда вы будете авторизоваться)

Внимание! Ключ и сертификат указаные ниже просто пример. Не вздумайте использовать их в продакшне.

your_private_key.pem
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA9aHYIsb3WWWfhSeDdZnZagxRrHvcam1QZc5QCGr1EyiuBedb
9xCdJx9qXKv7qvF0fTxZU5wUVicgvUYrTg9hBxx2rkZ4GKgy6DQSO3A/d/7CUk/d
6BrIQlYLW5iLZ68aN7RoVdTl8+wMRjW6yuAIl02bpeI1yuLFDL7jM9mA+PEtSRcB
4JrbvfKoiyG8Q0PWXpl1DyjpqjmP02ezGlUe9MwiZQ6w3MfR0tMOAaroYKo+Tpqp
yDy98ca6cwOr1G7H46n/ifITJp1QyWPUA8P0/Bu/R2nLhsec5Xp5FOJgIn4tOysJ
oAaZRAmg+OMMn7IosiOOtzxWOFROYP89zUwICQIDAQABAoIBAQDSAuNJsLXQ0EtY
TFVgWf8CZa/6+heVQwtHT+Mrn08UZ2aKk6oUjjbfOUP2IfFV0YWYgd2Z18uncWLT
kUf7QLjjnJl4HbYW1tBXqhWgjjMlxEKen7yJ80QfO3QQh1dBefu/ftbp89H8AGjq
KowfYvbVbjxS0xrM8aAqapbVQPS9YcJGm4VvzE+H+p/cdORfmcssBZtX8ym/EeGB
hOWmmQyHxrfR5SzxWyGwntT7kMbZMJ5rixiUnDUhHgTSOWbKYTKPE5sojrV8en3r
Ic/Sy/GqwHPfDhaA/ZfPI7d8wBueDrMOb4goO6oNmO6xQkozSOgIsOmpHOpkEaj/
Vlbqmw7RAoGBAP+GLKFSPSgKt5ZymRieZmPXAYohYIcDAExSYdQOA6vVlheXn9Kr
/fHZLNult0omevCb7stW192H2A2RN5HzqUO8eJ0/DyUR13ZluNGUUxLMh//wapKT
wfCXzDMYdYoeVf6PgZwLdY2BSXG3/Ycr7N27ak251Nffgnh4RxXW8eL1AoGBAPYW
9CweiJ1Vt5Z3KHDRsoCWqFYH+229j9tcxdJGt5VNKrkDxOJfhGu3N6dESo8qEGuu
ix6hZhMnF04s8oiBowXFfoopw4K3CUR9paaM+W1fsKDM0f51wZAxd2ZmKnNZW53a
zc5iQH0Zy1qT9M5iiXM3VutadSkwupETlOqRYOxFAoGAEf5Z0DZhVhuDGBYTz9b/
sNIoKpj1GizM7ZLzjqI6AfS1cA3eVFCGPmyjqwf9YzxYde8VHr6Lzu7M+Q+b0SxO
ZBW2jKQvJdYezRiWrjN6sh0zCoPcjVvYUV/vIj37sPE37wgeAWYRLhjHmjlxof4m
3Opgrv6CDX2Qy3j00IXlXK0CgYAU9smgRI2g0Z+NKuOAEO0i9TKr+Ywawi5SIqob
iriy+FruXfrUygxO3NHZ5wBvB8dUVQ828crvUMI0f7G9nUWVBUkNXhdwuEUK16VX
9eR9w8wZNrmg8skljoE8cPGm1/LtFKm5rjcOMTdYpQgS2OQas5ks/YzDkIokN8XU
4cOe/QKBgFp2xSur5d4OFm3yfwq9I2p+tLQrKQexgPExbs4opPmGvsg429xmiLDs
d1V0622jOeZflsSFJpsY/MPgxMM2mEfjfEWmqcyOHKoCGbsBc4HC7UJ5zXo4Txaz
1dU9+mRV9XS7mr5bSgNyRB3X1DEeJ4Wi8tp3gV4FH0DwSNGByS/1
-----END RSA PRIVATE KEY-----


your_certificate.pem
-----BEGIN CERTIFICATE-----
MIIDwTCCAqmgAwIBAgIJAOuaqw3NQvmyMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEVMBMG
A1UEAxQMd2ViX3NpdGUuY29tMB4XDTE0MTIxODE0MzkxM1oXDTI0MTIxNTE0Mzkx
M1owSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJh
bmNpc2NvMRUwEwYDVQQDFAx3ZWJfc2l0ZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQD1odgixvdZZZ+FJ4N1mdlqDFGse9xqbVBlzlAIavUTKK4F
51v3EJ0nH2pcq/uq8XR9PFlTnBRWJyC9RitOD2EHHHauRngYqDLoNBI7cD93/sJS
T93oGshCVgtbmItnrxo3tGhV1OXz7AxGNbrK4AiXTZul4jXK4sUMvuMz2YD48S1J
FwHgmtu98qiLIbxDQ9ZemXUPKOmqOY/TZ7MaVR70zCJlDrDcx9HS0w4Bquhgqj5O
mqnIPL3xxrpzA6vUbsfjqf+J8hMmnVDJY9QDw/T8G79HacuGx5zlenkU4mAifi07
KwmgBplECaD44wyfsiiyI463PFY4VE5g/z3NTAgJAgMBAAGjgaswgagwHQYDVR0O
BBYEFComQlVarS6Y5vru8+0WVLEN5fkeMHkGA1UdIwRyMHCAFComQlVarS6Y5vru
8+0WVLEN5fkeoU2kSzBJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNV
BAcTDVNhbiBGcmFuY2lzY28xFTATBgNVBAMUDHdlYl9zaXRlLmNvbYIJAOuaqw3N
QvmyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAN4bpIEbkYe/fkMZ
gLplj5wVz4CQgTeYdjgQnZqlJV7bXUJnEREE1oyfS5UHHvKpAOzBhZFNc+T0LFV1
ivKBa6ZJnMY9iUqDuyf7x3QyV62bWeHqOuT8RcZpMtJ1YcQUZgotWlvm2/4zlbVE
sZNhsC8NxPRGu3T9d+GPa2zfICMMwDGOHJejPH0FBxdbByHDRkg59UYLFWhejY1G
qN2QRDOi6B8LytcMkMyiPZoXL8mFOhR9C6GdoLcc1IZZk5PnrAn0i8sgBs9d4ev1
JU42f8cI8QSG/xKftdVRsoSBPG+EGXJ4EmjUXt1XMu9zSij5WZP5mHfzdfkLgwnf
XFhp0s0=
-----END CERTIFICATE-----


Конфигурация salesforce орга



  1. Setup->Create-> App -> scroll down to Connected Apps -> click New

  2. Конфигурация Connected app и загрузка сертификата сгенерированого на первом шаге

  3. Сохранить измениния.
  4. Открыть страницу настройки приложения Setup -> Manage Apps -> Connected App -> выбрать ваше приложение и нажать кнопку Edit

  5. Выбрать опцию All users may self-authorize for Permitted Users
  6. Выбрать опцию Relax IP Restrictions for IP Restrictions
  7. Нажать Save


Процесс авторизации с использованием JWT



Для того чтобы разрешить соединение из одной salesforce организации к другой salesforce организации нужно прописать Remote Site Settings на стороне клиента.
Setup -> Security controls -> Remote Site Settings -> New


Чтобы разрешить предоставить доступ определенному приложению для определенного пользователя, надо будет скомпоновать и открыть в браузере определенный линк. Формат у него следующий:

https://login.salesforce.com/services/oauth2/authorize?client_id=[client_id]&redirect_uri=[redirect_url]&response_type=code

client_id т.н. консьюмер ключ (consumer key) сгенерированый при создании Connected App, в нашем случае — 3MVG9QDx8IX8nP5RvIRcKYiylTgtQ1RUJT7tgOC0REp4bEA3JwFM5t39RGF_H5Kwfh6hf_j3.XKTf.IMOTw1Q
redirect_url урл на который будет происходить редирект (он тоже должен соответствовать тому, что было указано при создании Connected App), в нашем случае login.salesforce.com/home/home.jsp




Чтобы получить токен ноступа (access token) мы должны будем создать JWT и подписать его с помощью RSA SHA256 ключа, сгенерпрованого на первом шаге.

Ниже код на Apex для генерации JWT и получения access_token


public with sharing class  JwtAuth {
    //JWT Header
    public class JwtHeader{
        public String alg = 'RS256';
    }
    //JWT Claims Set
    public class JsonClaimsSet{
        public String iss; //Consumer Key of Salesforce Connected App
        public String prn; //Salesforce username on destination Org
        public String aud = 'https://login.salesforce.com'; //https://login.salesforce.com OR https://test.salesforce.com
        public String exp = '' + System.currentTimeMillis() + (1000 * 60 *60); //Token expiration date - timestamp in miliseconds
        public String sub; //optional in JWT Bearer Token flow
    }
    // JWT Auth flow result
    public class JwtAuthResult {
        public String scope;
        public String instance_url;
        public String token_type;
        public String access_token;
        public String error;
        public String error_description;
    }
    // Singleton   
    private static JwtAuth self;
     
    private JwtAuth () {}
     
    public static JwtAuth getInstance() {
        if (self == null) {
            return new JwtAuth();
        }
        return self;
    }
     
     
    public String generateJWT(String consumerKey, String sfUsername, String x509PrivateKey) {
        //lifetime by defaultt = 10 minutes
        //connect to production by default
        return generateJWT(consumerKey, sfUsername, x509PrivateKey, 600, false);
    }
     
    private String generateJWT(String consumerKey, String sfUsername, String x509PrivateKey, Integer lifetimeSeconds, Boolean isSandbox) {
        //1. Construct a JWT Header
        JwtHeader header = new JwtHeader();
        header.alg = 'RS256';
 
        String jwtHeaderStr = JSON.serialize(header);
        jwtHeaderStr = base64EncodeUrl(jwtHeaderStr);
         
        //2. Construct a JSON Claims Set
        JsonClaimsSet claimsSet = new JsonClaimsSet();
        claimsSet.iss = consumerKey;
        claimsSet.prn = sfUsername;
        claimsSet.aud = isSandbox ? 'https://test.salesforce.com' : 'https://login.salesforce.com';
        claimsSet.exp = '' + (System.currentTimeMillis() + lifetimeSeconds*1000);
 
        String jsonClaimsSetStr = JSON.serialize(claimsSet);
        jsonClaimsSetStr = base64EncodeUrl(jsonClaimsSetStr);
         
        //3. Join JWT Header and JSON Claims Set
        String token = jwtHeaderStr + '.' + jsonClaimsSetStr;
         
        //4. Sign with RSA-SHA256 Certificate
        String signedPayload = base64EncodeUrl(
            Crypto.sign('RSA-SHA256', Blob.valueOf(token), EncodingUtil.base64Decode(x509PrivateKey))
        );
        return token + '.' + signedPayload;
    }
     
    public JwtAuthResult requestAccessToken (String endpoint, String jwtSignedToken) {
         
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setBody(
            EncodingUtil.urlDecode('grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer', 'UTF-8')
            + '&assertion=' + jwtSignedToken);
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setMethod('POST');
         
        Http http = new Http();
         HTTPResponse res = http.send(req);
        String result = res.getBody();
         
        return (JwtAuthResult) JSON.deserialize(result, JwtAuth.JwtAuthResult.class);
    }
    //Convert Plain base64 to base64Url
    public String base64EncodeUrl(Blob data) {
        return EncodingUtil.base64Encode(data).replaceAll('\\+','-').replaceAll('/','_');
         
    }
    //Convert Plain base64 to base64Url
    public String base64EncodeUrl(String data) {
        return EncodingUtil.base64Encode(Blob.valueOf(data)).replaceAll('\\+','-').replaceAll('/','_');
         
    }
}


Полезные ресурсы по теме:

Digging Deeper into OAuth 2.0 on Force.com
Remote Access. Oauth JWT flow
Apex Crypto Documentation
Apex Crypto Examples
Tags:salesforce.comoauth 2.0jwt
Hubs: Website development
+4
21.2k 38
Leave a comment

Popular right now

Team Lead 2.0
January 28, 202190,000 ₽OTUS
Product Manager IT-проектов
January 28, 202160,000 ₽OTUS
Тренажер product-менеджера
January 28, 202128,500 ₽SkillFactory
JavaScript Developer. Professional
January 28, 202170,000 ₽OTUS
Android-разработчик с нуля
January 29, 202179,900 ₽Нетология