署名付き要求の検証および復号化
署名付き要求を使用している場合、Salesforce はユーザーコンテキストと認証情報をキャンバスアプリケーションの URL に送信します。署名付き要求を有効にするには、キャンバスアプリケーションの特定のコンシューマーの秘密を使用して、署名付き要求が署名されたことを確認する必要があります。正しいコンシューマーの秘密が使用されていた場合はそのコンテキストを信頼できますが、そうでない場合、その要求が Salesforce によって開始されなかったと想定できます。署名付き要求を検証および復号化するには、アプリケーションで次の操作を行う必要があります。
- Salesforce からの最初の署名付き要求を含む POST メッセージを受信します。
- 最初の期間で、署名付き要求を分割します。この結果、コンシューマーの秘密で署名されたハッシュ済み Based64 コンテキストと、Base64 で符号化されたコンテキスト自体という 2 つの文字列が生成されます。
- HMAC SHA-256 アルゴリズムを使用して、Base64 で符号化されたコンテキストをハッシュし、コンシューマーの秘密を使用してそれに署名します。
- 前のステップで作成した文字列を Base64 で符号化します。
- Base64 で符号化された文字列と、ステップ 2 で受け取ったコンシューマーの秘密で署名されたハッシュ済み Base64 コンテキストを比較します。
2 つの値が同じである場合、署名付き要求は自分のコンシューマーの秘密で署名されたものであり、信頼できることがわかります。これで、符号化されたコンテキストを Base64 で復号化し、必要な値を解析できるようになります。これらの値についての詳細は、「CanvasRequest」を参照してください。2 つの文字列が異なる場合、その要求は自分のコンシューマーの秘密を使用してハッシュおよび署名されていなかったことになります。この場合、ユーザーに適切なメッセージを返す必要があります。
検証および復号化用の関数
Canvas SDK (SalesforceCanvasFrameworkSDK\src\main\java\canvas\SignedRequest.java 内) にある次のいずれかの関数をコールして、署名付き要求を検証できます。
- verifyAndDecode — Java オブジェクトとして、検証および復号化されたバージョンの署名付き要求を返します。
- verifyAndDecodeAsJson — JSON 形式の文字列として、検証および復号化されたバージョンの署名付き要求を返します。
次のコード例に、SDK の関数を使用して署名付き要求を検証および復号化する方法を示します。このコードは、署名された秘密と Base64 JSON 文字列を解析するときに、署名付き要求の文字列を分割します。次に、HMAC SHA-256 アルゴリズムを使用して署名されたキャンバスアプリケーションのコンシューマーの秘密を暗号化し、その暗号化された値と、Salesforce によって送信された、暗号化された値を比較します。
2 つの値が同じである場合、そのコンテキストは有効で、Salesforce から生成されたことがわかります。2 つの値が異なる場合、その要求は Salesforce から行われたものではありません。
1/**
2*
3* The utility method can be used to validate/verify the signed request.
4* In this case, the signed request is verified that it's from Salesforce and that
5* it has not been tampered with.
6*
7* This utility class has two methods. One verifies and decodes the request
8* as a Java object, the other as a JSON String.
9*
10*/
11public class SignedRequest {
12 public static CanvasRequest verifyAndDecode(String input, String secret)
13 throws SecurityException {
14 String[] split = getParts(input);
15 String encodedSig = split[0];
16 String encodedEnvelope = split[1];
17
18 // Deserialize the JSON body.
19 String json_envelope = new String(new Base64(true).decode(encodedEnvelope));
20 ObjectMapper mapper = new ObjectMapper();
21 ObjectReader reader = mapper.reader(CanvasRequest.class);
22 CanvasRequest canvasRequest;
23 String algorithm;
24 try {
25 canvasRequest = reader.readValue(json_envelope);
26 algorithm = canvasRequest.getAlgorithm() == null ?
27 "HMACSHA256" : canvasRequest.getAlgorithm();
28 } catch (IOException e) {
29 throw new SecurityException(String.format("Error [%s] deserializing JSON to
30 Object [%s]", e.getMessage(), CanvasRequest.class.getName()), e);
31 }
32 verify(secret, algorithm, encodedEnvelope, encodedSig);
33 // If we got this far, then the request was not tampered with.
34 // Return the request as a Java object.
35 return canvasRequest;
36 }
37 public static String verifyAndDecodeAsJson(String input, String secret)
38 throws SecurityException {
39 String[] split = getParts(input);
40 String encodedSig = split[0];
41 String encodedEnvelope = split[1];
42 String json_envelope = new String(new Base64(true).decode(encodedEnvelope));
43 ObjectMapper mapper = new ObjectMapper();
44 String algorithm;
45 StringWriter writer;
46 TypeReference<HashMap<String,Object>> typeRef
47 = new TypeReference<HashMap<String, Object>>() { };
48 try {
49 HashMap<String,Object> o = mapper.readValue(json_envelope, typeRef);
50 writer = new StringWriter();
51 mapper.writeValue(writer, o);
52 algorithm = (String)o.get("algorithm");
53 } catch (IOException e) {
54 throw new SecurityException(String.format("Error [%s] deserializing
55 JSON to Object [%s]", e.getMessage(),
56 typeRef.getClass().getName()), e);
57 }
58 verify(secret, algorithm, encodedEnvelope, encodedSig);
59 // If we got this far, then the request was not tampered with.
60 // Return the request as a JSON string.
61 return writer.toString();
62 }
63
64 private static String[] getParts(String input) {
65 if (input == null || input.indexOf(".") <= 0) {
66 throw new SecurityException(String.format("Input [%s] doesn't
67 look like a signed request", input));
68 }
69 String[] split = input.split("[.]", 2);
70 return split;
71 }
72
73 private static void verify(String secret, String algorithm,
74 String encodedEnvelope, String encodedSig )
75 throws SecurityException
76 {
77 if (secret == null || secret.trim().length() == 0) {
78 throw new IllegalArgumentException("secret is null, did you
79 set your environment variable CANVAS_CONSUMER_SECRET?");
80 }
81 SecretKey hmacKey = null;
82 try {
83 byte[] key = secret.getBytes();
84 hmacKey = new SecretKeySpec(key, algorithm);
85 Mac mac = Mac.getInstance(algorithm);
86 mac.init(hmacKey);
87 // Check to see if the body was tampered with.
88 byte[] digest = mac.doFinal(encodedEnvelope.getBytes());
89 byte[] decode_sig = new Base64(true).decode(encodedSig);
90 if (! Arrays.equals(digest, decode_sig)) {
91 String label = "Warning: Request was tampered with";
92 throw new SecurityException(label);
93 }
94 } catch (NoSuchAlgorithmException e) {
95 throw new SecurityException(String.format("Problem with algorithm [%s]
96 Error [%s]", algorithm, e.getMessage()), e);
97 } catch (InvalidKeyException e) {
98 throw new SecurityException(String.format("Problem with key [%s]
99 Error [%s]", hmacKey, e.getMessage()), e);
100 }
101 // If we got here and didn't throw a SecurityException then all is good.
102 }
103}verifyAndDecode 関数のコール
次のコードに、署名付き要求を取得し、verifyAndDecode 関数を使用して要求を検証および復号化する例を示します。
1// From a JSP or servlet.
2<%@ page import="canvas.SignedRequest" %>
3<%@ page import="java.util.Map" %>
4<%
5 // Pull the signed request out of the request body and verify/decode it.
6 Map<String, String[]> parameters = request.getParameterMap();
7 String[] signedRequest = parameters.get("signed_request");
8 if (signedRequest == null) {%>
9 This app must be invoked via a signed request!<%
10 return;
11 }
12 String yourConsumerSecret=System.getenv("CANVAS_CONSUMER_SECRET");
13 String signedRequest = SignedRequest.verifyAndDecode(signedRequest[0], yourConsumerSecret);
14%>
15...
16// From JavaScript, you can handle the signed request as needed.
17var signedRequest = '<%=signedRequestJson%>';verifyAndDecodeAsJson 関数のコール
次のコードに、署名付き要求を取得し、verifyAndDecodeAsJson 関数を使用して要求を検証および復号化して、返された JSON 結果を解析する例を示します。
1// From a JSP or servlet.
2<%@ page import="canvas.SignedRequest" %>
3<%@ page import="java.util.Map" %>
4<%
5 // Pull the signed request out of the request body and verify/decode it.
6 Map<String, String[]> parameters = request.getParameterMap();
7 String[] signedRequest = parameters.get("signed_request");
8 if (signedRequest == null) {%>
9 This App must be invoked via a signed request!<%
10 return;
11 }
12 String yourConsumerSecret=System.getenv("CANVAS_CONSUMER_SECRET");
13 String signedRequestJson = SignedRequest.verifyAndDecodeAsJson(signedRequest[0], yourConsumerSecret);
14%>
15...
16// From JavaScript, you can parse with your favorite JSON library.
17var signedRequest = JSON.parse('<%=signedRequestJson%>');