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