Quantcast
Channel: Postmanタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 470

Force.com における OAuth 認証フローのとりまとめ【実践篇】

$
0
0

冒頭で、SalesforceのOAuth認証フローをどこかのタイミングで一度まとめようとしています。Salesforceヘルプで記載の内容をベースにして行った検証を記録する形になります。Asset Token Flow まで時間を取れなかったため、別途で補足します。

この数年の投稿は全部OAuth関連のため、Force.com上で新しいOAuth認証フローが公開されない限り、実践編の投稿を最後とします。

この記事は Salesforce Platform Advent Calendar 2020 - Qiita第23日目の投稿です。

はじめに

Salesforceで提供するOAuth認証フローは、IETF OAuth Working Groupが開発した業界標準の認証プロトコル「OAuth2.0」に準じます。よって、元ネタは「oauth.net」または IETF (Internet Engineering Task Force) のWeb Authorization Protocol (oauth) の「Documents」で公開されています。

投稿した時点、Force.com上で公開した主なフローは以下です。順番はSalesforceヘルプと異なりますが、難易度によっておすすめの学習順を付けました。

フロー名画面認証学習順
OAuth 2.0 Web Server FlowLevel-1
OAuth 2.0 User-Agent FlowLevel-1
OAuth 2.0 Username-Password FlowLevel-1
OAuth 2.0 Refresh Token FlowLevel-1
OAuth 2.0 Device FlowLevel-1
OAuth 2.0 JWT Bearer FlowLevel-2
OAuth 2.0 SAML Bearer Assertion FlowLevel-2
OAuth 2.0 Asset Token Flow(-)Level-3
OpenID Connect Token IntrospectionLevel-1

本題

さて、これから本題に入ります。
検証にあたって使った機能やツールなどは以下です。

  • Salesforce 接続アプリケーション:OAuth認証の窓口、フローによって設定が多少異なる
  • Postman:REST API 検証ツール
  • OpenSSL:証明書発行
  • VSCode:Javaクライアントサンプルコードなどの開発環境

1. Web Server Flow

ユーザーアカウントの認証にしても、データ連携にしても、一番よく利用されるフローと思われます。Salesforce開発者ドキュメントでフロー図などの詳細説明があります。サマリは以下です。

Step 1. 認証コードを要求する (HTTP GET)

HTTP_GET
https://login.salesforce.com/services/oauth2/authorize?client_id='<Consumer Key>'&redirect_uri='<Callback URL>'&response_type=code

Step 2. ユーザによるアクセスの認証および承認 (Browser)
ログインを通したら以下の画面を表示します。

Step 3. Salesforce による認証コードの付与 (Response by Callback URL)
image.png
Step 4. アクセストークンの要求 (HTTP POST)

Step 5. Salesforce によるアクセストークンの付与 (Response Body)

Salesforce 接続アプリケーションの設定

  • OAuth設定

    「Require Secret for Web Server Flow」にチェックが入っていなければ、「Step 4. アクセストークンの要求 (HTTP POST)」のパラメタの中、「client_secret」を設定しなくてもAccess Token及びRefresh Tokenを取得できます。

  • OAuthポリシー

Web Server Flowの注意点

  • URLで返された認証コードは、URLエンコードされたものなので、デコードにしないとAccess Tokenを取得する際に、レスポンスのBodyでエラーが返されました。例えば、エンコードされた「%30D」に対してデコードしたら「=」に変換されます。

  • 一定な時間を経過すると、認証コードは失効なので、再度認証コードを取得する必要です。


2. User-Agent Flow

Salesforceヘルプで「このフローは、OAuth 2.0 暗黙的許可種別(OAuth 2.0 implicit grant type)を使用します。」と明記されています。しかし、OAuth2.0によると、「Implicit Flow」に対して「Legacy」が付けられました。今後どのように処置されるのを気になります。

Step 1. 認証エンドポイントへのリダイレクト (HTTP GET)

HTTP_GET
https://login.salesforce.com/services/oauth2/authorize?response_type=token+id_token&client_id='<Consumer Key>'&redirect_uri='<Callback URL>'&scope=api%20refresh_token%20openid&nonce=somevalue

Step 2. ユーザによるアクセスの認証および承認 (Browser)

Step 3. Salesforce によるアクセストークンの付与 (Browser)
image.png
「1. Web Server Flow」 の Response Body と似たような内容をURLで返しました。

Response_by_URL
https://login.salesforce.com/services/oauth2/success#access_token='<access_token>'&refresh_token='<refresh_token>'&instance_url=https%3A%2F%2Fap.salesforce.com&id=https%3A%2F%2Flogin.salesforce.com%2Fid%2F00D100000003GXbEAM%2F00510000007rhhQAAQ&issued_at=1608908106950&signature='<signature>'&id_token='<id_token>'&scope=api+refresh_token+openid&token_type=Bearer

Salesforce 接続アプリケーションの設定

  • OAuth設定
  • OAuthポリシー

3. Username-Password Flow

一番シンプルなOAuth認証フローです。画面操作の必要がないため、ユーザーアカウント認証でなくデータ連携に向いてます。
接続アプリケーションの「Consumer Key」、「Consumer Secret」、特定なユーザーのユーザー名とパスワードを合わせて認証する仕組です。「JWT Bear Flow」と「SAML Bear Flow」のような証明書で署名して認証する仕組みと比べてセキュリティ面が弱いです。
このフローも、OAuth2.0によってLegacyが付けられました。

Salesforce 接続アプリケーションの設定
「1. Web Server Flow」または「2. User-Agent Flow」と同様にして問題ありません。


4. Refresh Token Flow

このフローでは、Web Server Flow または User-Agent Flow、Device Flow によって発行された Access Token を更新するだけなので、あまりフローの感じがしないです。HTTP GET と POST の両方をサポートします。

  • HTTP GET
  • HTTP POST

5. Device Flow

画面操作で確認コードを認証する必要のため、入力機能や表示機能が限定されたIoTデバイスで利用可能です。
サマリは以下です。

Step 1. デバイス要求認証 (HTTP POST) 及び Salesforce が確認コードを返す (Response body)

  • HTTP POST:必要なパラメタを「Params」と「Body」のどちらに指定しても認証コードを取得できる
    • 「Params」に指定する場合
    • 「Body」に指定する場合
  • HTTP GET の場合、エラーになる

Step 2. ユーザによる認証および承認 (Browser)

  • リンク「verification_uri」を開いて「user_code」を認証する

Step 3. デバイスによるトークンエンドポイントのポーリングと Salesforce によるアクセストークンの付与 (HTTP POST)

  • 「Step 1.」と同様、必要なパラメタを「Params」と「Body」のどちらに指定しても Access Token を取得できる (Step 1.で取得した「device_code」を「code」に指定する)
    • 「Params」に指定する場合
    • 「Body」に指定する場合
    • 注意:デバイスの確認コード「device_code」一回の発行で(有効期限10分)、一度でしか利用できないため、再度 Access Token を取得する場合「device_code」の再発行及び確認コード(user_code)の認証が必要です。

Salesforce 接続アプリケーションの設定

  • OAuth設定
  • OAuthポリシー

6. JWT Bearer Flow

個人的にとても好きなフローですが、証明書の発行、署名及びアサーションの生成がやや煩雑です。SSLサーバー証明書で JWT に署名し、Base64エンコードで認証用のアサーションを作成します。
画面操作の必要がありません。ETLのTalendやCI/CDのGitlabなどのツールでは、Salesforceと連携する際に JWT Bearer Flow を使用します。
JWTの構成について、以下の属性でJSONの形になります。

Step 1. OpenSSLで証明書を作成する

  • 証明証作成のコマンド
$openssl genrsa 2048 > server_advent_2020.key
$openssl req -new-key server_advent_2020.key > server_advent_2020.csr
$openssl x509 -days 3650 -req-signkey server_advent_2020.key < server_advent_2020.csr > server_advent_2020.crt
  • JKS 形式キーストア作成のコマンド
$openssl pkcs12 -export-in server_advent_2020.crt -inkey server_advent_2020.key -out domain_advent_2020.p12
$keytool -importkeystore-srckeystore domain_advent_2020.p12 -srcstoretype PKCS12 -destkeystore server_advent_2020.jks -deststoretype JKS

キーストアパスワードの入力について
openssl pkcs12 を実行して求められたパスワード(Export Password)は、keytoolの「ソース・キーストアのパスワードを入力してください」のところで必要となります。そして、keytool を実行して求められる出力先キーストアのパスワードも、次の「Step 2. 署名、アサーションの作成」の Java クライアントで使用します。

Step 2. 署名、アサーションの作成
Step 3. Access Token 取得

JWTExample
importjava.io.FileInputStream;importjava.io.IOException;importjava.io.UnsupportedEncodingException;importjava.net.URI;importjava.net.URLEncoder;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.http.HttpRequest.BodyPublishers;importjava.net.http.HttpResponse.BodyHandlers;importjava.security.KeyStore;importjava.security.PrivateKey;importjava.security.Signature;importjava.text.MessageFormat;importjava.util.LinkedHashMap;importjava.util.List;importjava.util.Map;importcom.fasterxml.jackson.core.JsonProcessingException;importcom.fasterxml.jackson.core.type.TypeReference;importcom.fasterxml.jackson.databind.JsonMappingException;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.apache.commons.codec.binary.Base64;publicclassJWTExampleAdvent2020{privatestaticStringclient_id="<Consumer Key>";privatestaticStringuserName="<Username>";// eg. advent@salesforce.com, the username who has Salesforce profile 'System Administrator'.privatestaticStringloginUrl="https://login.salesforce.com";privatestaticStringjksPath="<Path>";// Full path of JKS file.// Keytoolコマンド実行時のソース・キーストア(-srckeystore)のパスワードprivatestaticStringsrcPassword="<Password>";// The password of command 'openssl pkcs12'// Keytoolコマンド実行時の出力先キーストアのパスワードprivatestaticStringksPassword="<Password>";// The password of command 'keytool'/*
   * Step 2. 署名、アサーションの作成
   */publicstaticStringgeneralToken(StringclientId,StringuserName,StringloginUrl,StringjksPath,StringksPassword,StringsrcPassword,intexp_minutes){Stringheader="{\"alg\":\"RS256\"}";StringclaimTemplate="'{'\"iss\": \"{0}\", \"sub\": \"{1}\", \"aud\":\"{2}\", \"exp\": \"{3}\"'}'";StringBuffertoken=newStringBuffer();try{// * Encode the JWT Header and add it to our string to signtoken.append(Base64.encodeBase64URLSafeString(header.getBytes("UTF-8")));// Separate with a periodtoken.append(".");// * Create the JWT Claims ObjectString[]claimArray=newString[4];claimArray[0]=clientId;claimArray[1]=userName;claimArray[2]=loginUrl;claimArray[3]=Long.toString((System.currentTimeMillis()/1000)+60*exp_minutes);MessageFormatclaims;claims=newMessageFormat(claimTemplate);Stringpayload=claims.format(claimArray);// * Add the encoded claims objecttoken.append(Base64.encodeBase64URLSafeString(payload.getBytes("UTF-8")));// * Load the private key from a keystoreKeyStorekeystore=KeyStore.getInstance("JKS");keystore.load(newFileInputStream(jksPath),ksPassword.toCharArray());PrivateKeyprivateKey=(PrivateKey)keystore.getKey("1",srcPassword.toCharArray());// * Sign the JWT Header + "." + JWT Claims ObjectSignaturesignature=Signature.getInstance("SHA256withRSA");signature.initSign(privateKey);signature.update(token.toString().getBytes("UTF-8"));StringsignedPayload=Base64.encodeBase64URLSafeString(signature.sign());// Separate with a periodtoken.append(".");// * Add the encoded signaturetoken.append(signedPayload);returntoken.toString();}catch(Exceptione){e.printStackTrace();returnnull;}}/*
   * Step 3. Access Token 取得 (HTTP GET)
   */publicstaticStringrequestAccessTokenGET(Stringtoken){StringbodyStr="";HttpClientclient=HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();StringuriStr="https://login.salesforce.com/services/oauth2/token"+"?grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer"+"&assertion="+token;HttpRequestrequest=HttpRequest.newBuilder().GET().uri(URI.create(uriStr)).build();try{HttpResponse<String>response=client.send(request,BodyHandlers.ofString());// BodybodyStr=response.body();}catch(IOExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}returnbodyStr;}/*
   * Step 3. Access Token 取得 (HTTP POST)
   */publicstaticStringrequestAccessTokenPOST(Stringtoken){StringbodyStr="";StringuriStr="https://login.salesforce.com/services/oauth2/token";Map<String,Object>params=newLinkedHashMap<>();params.put("grant_type","urn:ietf:params:oauth:grant-type:jwt-bearer");params.put("assertion",token);StringBuilderpostData=newStringBuilder();try{for(Map.Entry<String,Object>param:params.entrySet()){if(postData.length()!=0)postData.append('&');postData.append(URLEncoder.encode(param.getKey(),"UTF-8"));postData.append('=');postData.append(URLEncoder.encode(String.valueOf(param.getValue()),"UTF-8"));}HttpClientclient=HttpClient.newBuilder().build();HttpRequestrequest=HttpRequest.newBuilder().uri(URI.create(uriStr)).header("Content-Type","application/x-www-form-urlencoded").POST(BodyPublishers.ofString(postData.toString())).build();HttpResponse<String>response=client.send(request,BodyHandlers.ofString());// BodybodyStr=response.body();}catch(UnsupportedEncodingExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}returnbodyStr;}publicstaticStringgetAccessToken(Stringjson){StringaccessToken="";ObjectMappermapper=newObjectMapper();// convert JSON string to MapMap<String,String>map;try{map=mapper.readValue(json,newTypeReference<Map<String,String>>(){});accessToken=map.get("access_token");}catch(JsonMappingExceptione){e.printStackTrace();}catch(JsonProcessingExceptione){e.printStackTrace();}returnaccessToken;}publicstaticvoidmain(String[]args){Stringtoken=generalToken(client_id,userName,loginUrl,jksPath,ksPassword,srcPassword,3);if(token!=null){StringbodyStrGet=requestAccessTokenGET(token);System.out.println();System.out.println("-----------------------------------------");System.out.println("Response Body by GET: "+bodyStrGet);StringaccessTokenGet=getAccessToken(bodyStrGet);System.out.println("Access Token by GET: "+accessTokenGet);StringbodyStrPost=requestAccessTokenPOST(token);System.out.println();System.out.println("-----------------------------------------");System.out.println("Response Body by POST: "+bodyStrPost);StringaccessTokenPost=getAccessToken(bodyStrPost);System.out.println("Access Token by POST: "+accessTokenPost);}}}
  • 実行結果

Salesforce 接続アプリケーションの設定

  • OAuth設定
  • OAuthポリシー

さらに
以下のお二方が書いた記事には、それぞれの観点からJWT Bearer Flowの詳細を記載しています。

宮本さんの記事はCTAのレビューボード向きのイメージです。目指していれば是非確認してみてください。


7. SAML Bearer Assertion Flow

Bearer Flow なので、JWT Bearer Flow との違いは、JWTアサーションをSAMLアサーションで入れ替えるだけです。ただ、ユースケースを見当たらないなので、どの場合このフローを利用するのをご存知の方はコメント欄に投稿いただければ幸いです。

Javaクライアントサンプル

SAMLBearerAssertionExample
importjava.io.FileInputStream;importjava.io.IOException;importjava.io.UnsupportedEncodingException;importjava.net.URI;importjava.net.URLEncoder;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.http.HttpRequest.BodyPublishers;importjava.net.http.HttpResponse.BodyHandlers;importjava.security.InvalidKeyException;importjava.security.KeyStore;importjava.security.KeyStoreException;importjava.security.MessageDigest;importjava.security.NoSuchAlgorithmException;importjava.security.PrivateKey;importjava.security.Signature;importjava.security.SignatureException;importjava.security.UnrecoverableKeyException;importjava.security.cert.CertificateException;importjava.text.SimpleDateFormat;importjava.util.Calendar;importjava.util.Date;importjava.util.LinkedHashMap;importjava.util.Map;importjava.util.TimeZone;importorg.apache.commons.codec.binary.Base64;importorg.apache.commons.codec.binary.Hex;publicclassSAMLBearerAssertionExampleAdvent2020{privateStringissuer="<Consumer Key>";privateStringsubject="<Username>";// eg. advent@salesforce.com, the username who has Salesforce profile 'System Administrator'.privateStringaudience="https://login.salesforce.com";privateStringaction="https://login.salesforce.com/services/oauth2/token";privateStringnotBefore;privateStringnotOnOrAfter;privateStringassertionId;privatestaticStringjksPath="<Path>";// Full path of JKS file.// Keytoolコマンド実行時のソース・キーストア(-srckeystore)のパスワードprivatestaticStringsrcPassword="<Password>";// The password of command 'openssl pkcs12'// Keytoolコマンド実行時の出力先キーストアのパスワードprivatestaticStringksPassword="<Password>";// The password of command 'keytool'// SAML TempleteprivateStringpreCannonicalizedResponse="<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ASSERTION_ID\" IssueInstant=\"NOT_BEFORE\" Version=\"2.0\"><saml:Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">ISSUER</saml:Issuer><saml:Subject><saml:NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">SUBJECT</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData NotOnOrAfter=\"NOT_ON_OR_AFTER\" Recipient=\"RECIPIENT\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"NOT_BEFORE\" NotOnOrAfter=\"NOT_ON_OR_AFTER\"><saml:AudienceRestriction><saml:Audience>AUDIENCE</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"NOT_BEFORE\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion>";privateStringpreCannonicalizedSignedInfo="<ds:SignedInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"></ds:SignatureMethod><ds:Reference URI=\"#ASSERTION_ID\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"></ds:Transform><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></ds:DigestMethod><ds:DigestValue>DIGEST</ds:DigestValue></ds:Reference></ds:SignedInfo>";privateStringsignatureBlock="<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">SIGNED_INFO<ds:SignatureValue>SIGNATURE_VALUE</ds:SignatureValue></ds:Signature><saml:Subject>";// millisecsstaticfinallongONE_MINUTE_IN_MILLIS=60000;SimpleDateFormatdateTimeFormat=newSimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");publicSAMLBearerAssertionExampleAdvent2020(){CalendarutcCal=Calendar.getInstance(TimeZone.getTimeZone("UTC"));dateTimeFormat.setTimeZone(utcCal.getTimeZone());DateutcDate=utcCal.getTime();longnowLong=utcDate.getTime();DatenotBeforeDt=newDate(nowLong-2*ONE_MINUTE_IN_MILLIS);notBefore=dateTimeFormat.format(notBeforeDt);DatenotOnOrAfterDt=newDate(nowLong+5*ONE_MINUTE_IN_MILLIS);notOnOrAfter=dateTimeFormat.format(notOnOrAfterDt);try{Doublerandom=Math.random();MessageDigestdigest=MessageDigest.getInstance("SHA-256");assertionId=newString(Hex.encodeHex(digest.digest(("assertion"+random).getBytes())));}catch(NoSuchAlgorithmExceptione){e.printStackTrace();}}publicStringgetSAMLResult(){preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("ASSERTION_ID",assertionId);preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("ISSUER",issuer);preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("AUDIENCE",audience);preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("RECIPIENT",action);preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("SUBJECT",subject);preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("NOT_BEFORE",notBefore);preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("NOT_ON_OR_AFTER",notOnOrAfter);try{// Prepare the DigestMessageDigestdigest=MessageDigest.getInstance("SHA-1");byte[]result=digest.digest(preCannonicalizedResponse.getBytes());StringdigestString=Base64.encodeBase64String(result);// Prepare the SignedInfopreCannonicalizedSignedInfo=preCannonicalizedSignedInfo.replaceAll("ASSERTION_ID",assertionId);preCannonicalizedSignedInfo=preCannonicalizedSignedInfo.replaceAll("DIGEST",digestString);byte[]input=preCannonicalizedSignedInfo.getBytes("UTF-8");KeyStorekeystore=KeyStore.getInstance("JKS");keystore.load(newFileInputStream(jksPath),ksPassword.toCharArray());PrivateKeyprivateKey=(PrivateKey)keystore.getKey("1",srcPassword.toCharArray());// Sign the SignedInfoSignaturesignature=Signature.getInstance("SHA1withRSA");signature.initSign(privateKey);signature.update(input);StringsignatureString=Base64.encodeBase64String(signature.sign());// Prepare the signature blocksignatureBlock=signatureBlock.replaceAll("SIGNED_INFO",preCannonicalizedSignedInfo);signatureBlock=signatureBlock.replaceAll("SIGNATURE_VALUE",signatureString);}catch(SignatureExceptione){e.printStackTrace();}catch(InvalidKeyExceptione){e.printStackTrace();}catch(NoSuchAlgorithmExceptione){e.printStackTrace();}catch(UnsupportedEncodingExceptione){e.printStackTrace();}catch(CertificateExceptione){e.printStackTrace();}catch(KeyStoreExceptione){e.printStackTrace();}catch(UnrecoverableKeyExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}preCannonicalizedResponse=preCannonicalizedResponse.replaceAll("<saml:Subject>",signatureBlock);returnpreCannonicalizedResponse;}publicvoidpostSAML(){StringbodyStr="";Stringsaml=getSAMLResult();try{Stringassertion=Base64.encodeBase64URLSafeString(saml.getBytes("UTF-8"));Map<String,Object>params=newLinkedHashMap<>();params.put("grant_type","urn:ietf:params:oauth:grant-type:saml2-bearer");params.put("assertion",assertion);StringBuilderpostData=newStringBuilder();for(Map.Entry<String,Object>param:params.entrySet()){if(postData.length()!=0)postData.append('&');postData.append(URLEncoder.encode(param.getKey(),"UTF-8"));postData.append('=');postData.append(URLEncoder.encode(String.valueOf(param.getValue()),"UTF-8"));}HttpClientclient=HttpClient.newBuilder().build();HttpRequestrequest=HttpRequest.newBuilder().uri(URI.create(action)).header("Content-Type","application/x-www-form-urlencoded").POST(BodyPublishers.ofString(postData.toString())).build();HttpResponse<String>response=client.send(request,BodyHandlers.ofString());bodyStr=response.body();System.out.println();System.out.println("-----------------------------------------");System.out.println("Response Body by POST: "+bodyStr);}catch(UnsupportedEncodingExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}catch(InterruptedExceptione){e.printStackTrace();}}publicstaticvoidmain(String[]args){SAMLBearerAssertionExampleAdvent2020samlExample=newSAMLBearerAssertionExampleAdvent2020();samlExample.postSAML();}}
  • 実行結果

Salesforce 接続アプリケーションの設定
「6. JWT Bearer Flow」と同様です。


8. Asset Token Flow

別途で補足します。


Appendix 1. OpenID Connect Token Introspection

厳密に言うと、OAuth認証フローではないですが、Access TokenやRefresh Tokenなどの状態を確認する機能なので、紹介したいです。

例 1. 無効になったAccess Token

例 2. 有効な Refresh Token

例 3. 基本認証(Basic Authentication)の利用
一般的なBasic認証では、ユーザー名とパスワードの組みをコロン ":" でつなぎ、Base64でエンコードして送信します。Salesforce OAuth認証の基本認証では、ユーザー名とパスワードでなく、「Consumer Key」と「Consumer Secret」を組むこととなります。Base64でエンコードのサンプルは以下です。

BasicAuthorization
importjava.io.UnsupportedEncodingException;importorg.apache.commons.codec.binary.Base64;publicclassBasicAuthorization{publicstaticvoidmain(String[]args){Stringclient_id="<Consumer Key>";Stringclient_secret="<Consumer Secret>";Stringbasic=client_id+":"+client_secret;System.out.println("basic: "+basic);try{Stringtoken=Base64.encodeBase64String(basic.getBytes("UTF-8"));System.out.println("Authorization Basic Token: "+token);}catch(UnsupportedEncodingExceptione){e.printStackTrace();}}}
  • ヘッダーで基本認証の設定
  • Access Token ステータスの確認

Salesforce 接続アプリケーションの設定

  • OAuth設定
  • OAuthポリシー

最後に

OAuth認証を通して REST API データ連携の事例がよく見かけていますが、ユーザーアカウント認証の事例は、私の経験上でまだないです。企業内部ユーザーの認証管理は、OAuthよりSSOのほうが向いてます。

OAuth認証フローは、Salesforce特有なものではないですが、「OAuth アクセスポリシーの管理」のようなSalesforceならでは的な機能があります。ただし、プラットフォームとしてForce.comが公開するOAuth認証フローの種類が充実しています。Salesforceで学んだOAuth関連の知識を他のプラットフォームでも活かせます。システムアーキテクトを目指すなら、必修科目の一つとなります。

他の参考資料

1. Salesforce Identity - Github
OAuth関連だけでなく、Salesforceプラットフォーム認証系のサンプルソースコードが大量にあります。
https://github.com/salesforceidentity

2. 株式会社 Authlete の 川崎 貴彦さんの技術ブログ
ほとんど認証系の記事が書かれています。図形や動画の説明が多く記載され、非常に勉強になりました。
https://qiita.com/TakahikoKawasaki

3. 川崎 貴彦さんの英語の技術ブログ
「2.」とほとんど同じ内容ですが、英語に慣れた方は是非こちらへ
https://darutk.medium.com/


Viewing all articles
Browse latest Browse all 470

Trending Articles