冒頭で、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 Flow | 有 | Level-1 |
OAuth 2.0 User-Agent Flow | 有 | Level-1 |
OAuth 2.0 Username-Password Flow | 無 | Level-1 |
OAuth 2.0 Refresh Token Flow | 無 | Level-1 |
OAuth 2.0 Device Flow | 有 | Level-1 |
OAuth 2.0 JWT Bearer Flow | 無 | Level-2 |
OAuth 2.0 SAML Bearer Assertion Flow | 無 | Level-2 |
OAuth 2.0 Asset Token Flow | (-) | Level-3 |
OpenID Connect Token Introspection | 無 | Level-1 |
本題
さて、これから本題に入ります。
検証にあたって使った機能やツールなどは以下です。
- Salesforce 接続アプリケーション:OAuth認証の窓口、フローによって設定が多少異なる
- Postman:REST API 検証ツール
- OpenSSL:証明書発行
- VSCode:Javaクライアントサンプルコードなどの開発環境
1. Web Server Flow
ユーザーアカウントの認証にしても、データ連携にしても、一番よく利用されるフローと思われます。Salesforce開発者ドキュメントでフロー図などの詳細説明があります。サマリは以下です。
Step 1. 認証コードを要求する (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)
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を取得できます。
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)
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)
「1. Web Server Flow」 の Response Body と似たような内容を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 接続アプリケーションの設定
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 の両方をサポートします。
5. Device Flow
画面操作で確認コードを認証する必要のため、入力機能や表示機能が限定されたIoTデバイスで利用可能です。
サマリは以下です。
Step 1. デバイス要求認証 (HTTP POST) 及び Salesforce が確認コードを返す (Response body)
Step 2. ユーザによる認証および承認 (Browser)
Step 3. デバイスによるトークンエンドポイントのポーリングと Salesforce によるアクセストークンの付与 (HTTP POST)
- 「Step 1.」と同様、必要なパラメタを「Params」と「Body」のどちらに指定しても Access Token を取得できる
(Step 1.で取得した「device_code」を「code」に指定する)
Salesforce 接続アプリケーションの設定
6. JWT Bearer Flow
個人的にとても好きなフローですが、証明書の発行、署名及びアサーションの生成がやや煩雑です。SSLサーバー証明書で JWT に署名し、Base64エンコードで認証用のアサーションを作成します。
画面操作の必要がありません。ETLのTalendやCI/CDのGitlabなどのツールでは、Salesforceと連携する際に JWT Bearer Flow を使用します。
JWTの構成について、以下の属性でJSONの形になります。
- iss: 接続アプリケーション(どこから)
- aud: Salesforce ユーザーまたは Salesforce コミュニティユーザーのユーザー名(誰)
- sub: https://login.salesforce.com、https://test.salesforce.com、またはhttps://community.force.com/customersに固定
- exp: 有効時間(いつ)
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 取得
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 接続アプリケーションの設定
さらに
以下のお二方が書いた記事には、それぞれの観点からJWT Bearer Flowの詳細を記載しています。
- Shinichi Tomita さん ( @stomita ) が書いた実践的な「OAuth2 JWT Bearer Token フローを使ってSalesforceへアクセスする」
- 宮本さん ( @takahito0508 ) が書いた論理的な「Salesforce の JWT ベアラーフローの図を書いてみる」
宮本さんの記事はCTAのレビューボード向きのイメージです。目指していれば是非確認してみてください。
7. SAML Bearer Assertion Flow
Bearer Flow なので、JWT Bearer Flow との違いは、JWTアサーションをSAMLアサーションで入れ替えるだけです。ただ、ユースケースを見当たらないなので、どの場合このフローを利用するのをご存知の方はコメント欄に投稿いただければ幸いです。
Javaクライアントサンプル
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でエンコードのサンプルは以下です。
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();}}}
Salesforce 接続アプリケーションの設定
最後に
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/