問題の現象(げんしょう)

BTP へのデプロイと XSUAA サービスインスタンスのバインドは成功していましたが、API アクセスに対して権限(けんげん)制限が全く効いていませんでした。表面上は「XSUAA が効いていない」ように見えましたが、実際には複数の問題が重なっていました。

💡 重要な教訓(きょうくん):XSUAA をバインドするだけでは認証は有効(ゆうこう)になりません。設定ファイル・CDS モデル・Spring コントローラー・依存関係(いぞんかんけい)・エンコード・起動設定の 6 層をすべて正しく設定する必要があります。

プロジェクト構成(こうせい)

このプロジェクトには 2 種類のエンドポイントがあります。これが問題の核心(かくしん)です。

① CAP CDS が自動生成する OData エンドポイント
   /odata/v4/CatalogService/Books

② 手書きの Spring MVC エンドポイント
   /api/zbooks
   /api/salesorders

→ この 2 つは認可(にんか)の仕組みが別々!

第 1 層:xs-security.json に問題がある

まず xs-security.json 自体を確認したところ、文字化けや JSON の構造(こうぞう)に問題がありました。ファイルが不正でも BTP 上に service instance は作れるため、「バインド成功」と表示されていても中身が正しいとは限りません。

修正後の xs-security.json

{
  "xsappname": "my-cap-java",
  "tenant-mode": "dedicated",
  "scopes": [
    {
      "name": "$XSAPPNAME.Books.Read",
      "description": "Read access for Books"
    },
    {
      "name": "$XSAPPNAME.Books.Write",
      "description": "Write access for Books"
    }
  ],
  "role-templates": [
    {
      "name": "Viewer",
      "description": "Read only",
      "scope-references": ["$XSAPPNAME.Books.Read"]
    },
    {
      "name": "Editor",
      "description": "Read and write",
      "scope-references": [
        "$XSAPPNAME.Books.Read",
        "$XSAPPNAME.Books.Write"
      ]
    }
  ]
}

修正後は BTP 上のサービスインスタンスも更新する必要があります:

cf update-service my-cap-java-xsuaa -c xs-security.json

第 2 層:CDS サービスに認可ルールがない

XSUAA が正常に動いていても、CDS サービス側に認可ルールがなければ誰でもアクセスできます。@requires@restrict を追加します。

修正後の cat-service.cds

@requires: 'authenticated-user'
service CatalogService {

  @restrict: [
    { grant: 'READ',                    to: ['Viewer', 'Editor'] },
    { grant: ['CREATE', 'UPDATE', 'DELETE'], to: 'Editor' }
  ]
  entity Books as projection on my.Books;
}

第 3 層:Spring 独自エンドポイントは CDS 認可が効かない

手書きの @RestController エンドポイントは CDS の @restrict の対象(たいしょう)外です。別途(べっと)Spring Security の @PreAuthorize を追加する必要があります。

ZBookController.java

@RestController
@RequestMapping("/api")
public class ZBookController {

    @Autowired
    private ZBookService zBookService;

    @GetMapping(value = "/zbooks", produces = MediaType.APPLICATION_JSON_VALUE)
    @PreAuthorize("hasAuthority('my-cap-java.Books.Read')")
    public ResponseEntity<String> getBooks() {
        try {
            String result = zBookService.getBooks();
            return ResponseEntity.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(result);
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body("{"error": "" + e.getMessage() + ""}");
        }
    }
}

MethodSecurityConfig.java(新規作成)

@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}

第 4 層:UTF-8 BOM によるコンパイルエラー

新しく作成・編集したファイルに UTF-8 BOM が含まれていると Maven コンパイル時に以下のエラーが出ます:

illegal character: ''

以下のファイルを UTF-8 without BOM で保存し直す必要があります:

  • MethodSecurityConfig.java
  • SalesOrderController.java
  • ZBookController.java
  • xs-security.json
  • cat-service.cds

第 5 層:Spring Security 関連の依存関係が不足

@EnableMethodSecurity を使うには追加の依存関係が必要です。srv/pom.xml に以下を追加します:

<!-- CAP XSUAA 認証 -->
<dependency>
    <groupId>com.sap.cds</groupId>
    <artifactId>cds-feature-identity</artifactId>
</dependency>

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- XSUAA Resource Server -->
<dependency>
    <groupId>com.sap.cloud.security</groupId>
    <artifactId>resourceserver-security-spring-boot-starter</artifactId>
    <version>3.3.5</version>
</dependency>

<!-- Spring Security Config -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>

第 6 層:application.yaml の手書き設定が起動失敗の原因

以下のような手書きの issuer-uri 設定は BTP 上で起動失敗の原因になりやすいです:

# ❌ 良くない書き方
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${vcap.services.my-cap-java-xsuaa.credentials.url}/oauth/token

代わりに CAP Java の自動設定に任せます:

# ✅ シンプルな書き方
cds:
  security:
    authentication:
      mode: model-strict

BTP 側でも必要な作業

xs-security.json を修正しても BTP 上のサービスインスタンスは自動更新されません。以下のコマンドで手動更新(しゅどうこうしん)が必要です:

# XSUAA サービスインスタンスを更新
cf update-service my-cap-java-xsuaa -c xs-security.json

# 再デプロイ
mvn package -DskipTests && cf push

また、BTP Cockpit でテストユーザーにロールコレクションを割り当て(わりあて)ることも忘れずに!

今回の修正内容まとめ

問題 修正内容
① xs-security.json文字化け・構造エラー正しい JSON に書き直す
② CDS サービス認可ルールがない@requires@restrict を追加
③ Spring エンドポイントCDS 認可が効かない@PreAuthorize を追加
④ UTF-8 BOMコンパイルエラーBOM なし UTF-8 で保存
⑤ 依存関係不足Security クラスが見つからないpom.xml に 4 つの依存関係を追加
⑥ application.yaml手書き設定で起動失敗CAP 自動設定に任せる

XSUAA トラブル時の排査(はいさ)順序

① xs-security.json は合法(ごうほう)か
② BTP 上の XSUAA service instance は更新済みか
③ テストユーザーにロールが割り当てられているか
④ CDS サービスに @requires / @restrict が書いてあるか
⑤ Spring 独自エンドポイントに @PreAuthorize があるか
⑥ application.yaml が自動設定と競合(きょうごう)していないか
⑦ BOM・依存関係・コンパイルエラーがないか

今日の学び

「XSUAA が効かない」という問題は一箇所(いっかしょ)を直せば解決するものではありませんでした。設定ファイル・CDS モデル・Spring コントローラー・依存関係・エンコード・起動設定という認証チェーン全体を正しく繋ぐ必要があります。

特に重要な発見(はっけん)は、CAP の OData エンドポイントと Spring の独自エンドポイントでは認可の仕組みが異なるという点です。両方に別々の対応が必要です。


この記事は「SAP BTP 学習」シリーズの第9回です。6ヶ月間の独学で SAP BTP 開発へのキャリアチェンジを目指しています。