これまでは、Azure Functionsのアクセス権限をAnonymous(匿名)にしていて、APIキーが不要でもアクセスできていたが、関数へのアクセスをより困難にするために、APIキーを必須にすることもできる。
今回は、Azure Functionsのアクセス権限を関数レベルに変更し、ヘッダーにAPIキーを設定してみたので、そのサンプルプログラムを共有する。
前提条件
下記記事の実装が完了していること。
作成したサンプルプログラム(App Service側)の内容
作成したサンプルプログラム(App Service側)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
コントローラクラスの内容は以下の通りで、Azure Functionsを呼び出すときのヘッダーに「”x-functions-key”, “Azure FunctionsのAPIキーの値”」の値を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.client.RestTemplate; import com.fasterxml.jackson.databind.ObjectMapper; @Controller public class DemoController { /** RestTemplateオブジェクト */ @Autowired private RestTemplate restTemplate; /** ObjectMapperオブジェクト */ @Autowired private ObjectMapper objectMapper; /** application.propertiesからdemoAzureFunc.urlBaseの値を取得 */ @Value("${demoAzureFunc.urlBase}") private String demoAzureFuncBase; /** * 検索一覧画面を初期表示する. * @param model Modelオブジェクト * @return 検索一覧画面 */ @GetMapping("/") public String index(Model model) { SearchForm searchForm = new SearchForm(); model.addAttribute("searchForm", searchForm); return "list"; } /** * 検索条件に合うユーザーデータを取得し、一覧に表示する * @param searchForm 検索条件Form * @param model Modelオブジェクト * @return 検索一覧画面 */ @PostMapping("/search") public String search(SearchForm searchForm, Model model) { // Azure FunctionsのgetUserDataList関数を呼び出すためのヘッダー情報を設定する HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // Azure FunctionsにアクセスするためのAPIキーを設定する headers.add("x-functions-key", "(Azure FunctionsにアクセスするためのAPIキー)"); // Azure FunctionsのgetUserDataList関数を呼び出すための引数を設定する MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); try { map.add("searchName", searchForm.getSearchName()); map.add("searchSex", searchForm.getSearchSex()); } catch (Exception ex) { throw new RuntimeException(ex); } HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers); // Azure FunctionsのgetUserDataList関数を呼び出す ResponseEntity<String> response = restTemplate.exchange( demoAzureFuncBase + "getUserDataList", HttpMethod.POST, entity, String.class); // Azure Functionsの呼出結果のユーザーデータ一覧を、検索条件Formに設定する try { SearchResult searchResult = objectMapper.readValue( response.getBody(), SearchResult.class); searchForm.setUserDataList(searchResult.getUserDataList()); } catch (Exception ex) { throw new RuntimeException(ex); } model.addAttribute("searchForm", searchForm); return "list"; } } |
なお、「Azure FunctionsのAPIキーの値」は、以下に示すような、Azure Portal上、Azure Functionsの「アプリキー」メニュー、ホストキー「default」の値となる。
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-functions-api-key/demoAzureApp
作成したサンプルプログラム(Azure Functions側)の内容
作成したサンプルプログラム(Azure Functions側)の構成は以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
<2021年4月13日 追記>
spring-cloud-function-dependenciesのバージョンは、2021年3月16日にリリースしたバージョン3.1.2を利用すると、1つのAzure Functions内に複数のファンクションを含む場合の不具合が解消できている。
その場合、Handlerクラスの継承するクラスを「AzureSpringBootRequestHandler」クラスから「FunctionInvoker」クラスに変更する。
spring-cloud-function-dependenciesの3.1.2を利用した実装サンプルは、以下の記事を参照のこと。
ハンドラークラスの内容は以下の通りで、権限レベル(authLevel)をANONYMOUSからFUNCTIONに変更している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package com.example; import java.util.Optional; import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler; import com.example.model.SearchForm; import com.example.model.SearchResult; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.azure.functions.ExecutionContext; import com.microsoft.azure.functions.HttpMethod; import com.microsoft.azure.functions.HttpRequestMessage; import com.microsoft.azure.functions.HttpResponseMessage; import com.microsoft.azure.functions.HttpStatus; import com.microsoft.azure.functions.annotation.AuthorizationLevel; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.HttpTrigger; public class GetUserDataListHandler extends AzureSpringBootRequestHandler<SearchForm, SearchResult> { /** * HTTP要求に応じて、DemoAzureFunctionクラスのgetUserDataListメソッドを呼び出し、 * その戻り値をボディに設定したレスポンスを返す * @param request リクエストオブジェクト * @param context コンテキストオブジェクト * @return レスポンスオブジェクト */ @FunctionName("getUserDataList") public HttpResponseMessage execute( @HttpTrigger(name = "request", methods = HttpMethod.POST , authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request, ExecutionContext context) { // リクエストオブジェクトからパラメータ値を取得し、検索条件Formに設定する ObjectMapper mapper = new ObjectMapper(); String jsonParam = request.getBody().get(); jsonParam = jsonParam.replaceAll("\\[", "").replaceAll("\\]", ""); SearchForm searchForm = new SearchForm(); try { searchForm = mapper.readValue( jsonParam, new TypeReference<SearchForm>() {}); } catch (Exception ex) { throw new RuntimeException(ex); } // handleRequestメソッド内でDemoAzureFunctionクラスのgetUserDataListメソッドを // 呼び出し、その戻り値をボディに設定したレスポンスを、JSON形式で返す return request.createResponseBuilder(HttpStatus.OK) .body(handleRequest(searchForm, context)) .header("Content-Type", "text/json").build(); } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/azure/tree/master/azure-functions-api-key/demoAzureFunc
サンプルプログラムの実行結果
サンプルプログラムの実行結果は、以下の通り。
1) 前提条件のプログラムからAzure Functionsのみ変更し、Azure上で実行した場合は、以下のように、Azure Functionsの呼び出しが行えないことが確認できる。
2) 前提条件のプログラムからAzure Functionsのみ変更し、ローカルで実行した場合は、以下のように、正常に動作することが確認できる。
3) 前提条件のプログラムからAzure FunctionsとAzure App Serviceを変更し、Azure上で実行した場合は、以下のように、正常に動作することが確認できる。
4) 前提条件のプログラムからAzure FunctionsとAzure App Serviceを変更し、ローカル環境で実行した場合も、以下のように、正常に動作することが確認できる。
要点まとめ
- Azure Functionsのアクセス権限を関数レベルに変更すると、Anonymous(匿名)の場合より、関数へのアクセスを困難にすることができる。
- Azure Functionsのアクセス権限を関数レベルに変更した場合、Azure Functionsを呼び出す際に、ヘッダーにAPIキーを指定する必要がある。