JSUG勉強会 2022年その2 Spring Security特集!に行ってきた #jsug

JSUG勉強会 2022年その2 Spring Security特集!に参加しました。オンライン開催。簡単に所感をまとめます。

jsug.doorkeeper.jp

所感

SecurityConfig の記述が変わって、Lambda 式になってたりしててちょっと違和感あるけど、Spring WebFlux の Router Functions もあんな感じの記述だったし、WebFlux 版はそういうもんなんだろうか。アーキテクチャの説明は分かりやすかった。Servlet Filter を自作するとこのあたりの挙動がより理解できるんじゃないかなと思います。

ちなみに AuthenticationProvider とか AuthorizationManager は複数実行されて、1つでも OK だったら全体として OK になるって言ってたけど、このあたりは並列で処理されるんだろうか。

(追記) 普通に AuthenticationProvider とか AuthorizationManager のリストをループしてる。(たぶん このあたり とか このあたり)

以下、メモから抜粋。資料が公開されたらリンクを追記します。YouTubeアーカイブも公開されるっぽい。

最新版の5.7で学ぶ!初めてのひとのためのSpring Security

  • 2021年 (5.5) から半年ごとのリリースに (5月/11月)
  • 認証/認可
    • SecurityFilterChain を Bean 定義する
    • WebSecurityConfigurerAdapter は非推奨に (5.7)
    • HttpSecurity#authorizeHttpRequests
      • パスと権限をセットで記述する
      • authorizeRequests は非推奨に
        • 内部で利用している manager が違う
    • ignoring => permitAll
  • Thymeleaf
  • GrantedAuthority ≒ ロール
  • アーキテクチャ
  • 認証情報の構造
    • HttpSession > SecurityContext > Authentication > UserDetails
  • 認証情報は Controller の引数でも取得可
  • SecurityContextPersistenceFilter
    • SecurityContext をスレッドローカルに保存する
    • レスポンスを返す際にスレッドローカルはクリアする
  • 認証処理 (AuthenticationProvider)
    • 1つでも認証成功したら認証OK
    • 独自の認証基盤を使う場合は AuthenticationProvider の実装クラスを Bean 定義する
  • 認可処理 (AuthorizationManager)
    • 1つでも認可成功したら認可OK
  • 非推奨などは Spring 公式ブログで確認しよう

qiita.com

JJUG CCC 2022 Spring に行ってきた #jjug #jjug_ccc

JJUG CCC 2022 Spring に参加しました。オンライン開催。簡単に所感をまとめます。

jjug.doorkeeper.jp

所感

久しぶりにリアルタイムで参加しました。質疑応答ができるのがリアルタイムのいいところ。時間の都合上、4セッションくらいしか参加できなかったけど、他にも気になるセッションがあるのであとでアーカイブを見直そう。

PayPay祭の事例ですが、負荷対策は自分としても気になるところなので興味深い内容でした。毎週、負荷試験してるのはすごい。やっぱり自動化してるんだろうなと思いますが、そのあたりのノウハウも知りたい。

リモートワークの話、オンボーディングのドキュメントは新規メンバーごとに用意しているらしい。環境セットアップの手順だけではなく、直近のオンボーディングタスク (簡単な修正タスク) まで書いてあるとよさそう。ドキュメントを継続的にメンテするのはなかなかハードル高いけど、リモートワークがデフォルトの環境で非同期コミュニケーションのメリットをフルに生かすなら乗り越えないといけない課題。このあたりは会社の文化として醸成する必要がありそう。

TiDB はどうなんだろう。MySQL プロトコル互換で導入はしやすそうかもしれないけど、運用まで見据えるとなんとなくもう少し検証しないといけないことが多そうな印象。

以下、メモから抜粋。セッション資料が公開されたらリンクを追記します。

クラウドネイティブ環境におけるJavaチューニングの進め方 - 超PayPay祭の事例

  • どのシステムで問題が発生しているか
  • システム間の相互作用が影響しているかも
  • マイクロサービスだと構成するコンポーネントが多い
    • どこに原因があるか特定しにくい
  • Metrics, Traces, Logs
  • 全体負荷試験
    • シナリオベース
  • 常時負荷試験
    • 毎週夜間に全体負荷試験の7割程度の負荷をかける
  • ポイント計算APIシステム
    • 認証用 Sidecar
    • キャンペーン情報は redis にキャッシュ
      • バッチで Oracle → redis に更新
    • ステートレス
      • ユーザーのログイン情報は加味しない
      • リクエストのパラメータから結果を算出する
      • 1sec 以内でレスポンスを返したい
    • 最大 20000rps 想定
    • 単体負荷試験では 27000rps 達成
  • Full GC 頻発
    • Dynatrace で検出
    • jvm のメモリチューニングのパラメータがデフォルトのまま
    • Old 領域枯渇
    • PaaS > CaaS 移行時に jvm のチューニングの考慮が漏れていた
      • ノイジーネイバー対策
  • リソース使用量は変えたくない
  • G1GC > CMSGC
    • Java 11 のデフォルトは G1GC
    • G1GC はヒープサイズ6GB以上でパフォーマンス発揮する
    • リソース使用量は抑えたい
    • CMSGC はヒープサイズが低くても性能が出やすい
  • New 領域で GC を完結させたい
    • ヒープのうち New 領域の割合を拡大
  • MaxTenuringThreshold
    • Old に移るマイナーGCの回数を指定する
    • デフォルト6 → 15
    • Full GC が起きないように
  • 最小ヒープサイズ = 最大ヒープサイズ
    • 稼働中にヒープ確保しない
  • OOM で exit させるように
    • k8s のオートヒーリングで自動復旧させる
  • 10000rps くらいから Pod ダウン
    • Sidecar がボトルネック
      • OOM でコンテナ Kill
    • sidecar 形式 > クライアントライブラリ形式に
    • リソース制限を考慮して
  • 同じ条件で負荷試験を実施する
  • すべての構成要素のメトリクスを確認する
  • 全体負荷試験はメンテナンスモードにして実施
  • 負荷対策チーム
    • 年度始めにチームを構成する
    • 通常の業務をやりながら負荷対策の役割が与えられるイメージ
  • 負荷試験で目標超えられなかったら
    • ポイント計算APIシステムで流量制限する

Lauchableで僕が学んだ働き方 〜リモートワークで会社もプロダクトも1から作る経験〜

  • エンジニアリングをデータドリブンに
  • 営業や企画はデータドリブンに仕事している
    • エンジニアは今でも経験や勘に頼っているところが多い
  • ドキュメント
  • オンボーディング
    • 入社から1週間分くらいのやることがまとまっている
  • 設計書は書かない
    • デザインドキュメントとして書く
    • ドキュメントでは How だけでなく Why (問題背景) を共有するのが大事
    • 仕様レビュー > ドキュメント
    • 実装レビュー > PR
  • コラボレーションマニフェスト
    • Swim Lanes
    • 自分の役割に対して説明責任がある
    • 透明性
      • 決定の内容や経緯が見えるように
    • 発見可能
    • Connected
    • アンバサダー
  • Writing Matters
    • 書くコストは高いが読む方は時間を節約できる
    • 会議を最後の手段に
  • 書くプロセスは考えることが必要
  • 非同期コミュニケーション/集中
    • 他者の時間を奪わないように
  • メンテ
    • 古い情報を見つけた人が直す
    • 不要なものはアーカイブする

分散データベースTiDB Cloudで構築するWebアプリケーション

  • PingCAP
  • MySQLプロトコル互換
  • TiDB
    • SQL解析
    • クエリ増加に応じてスケールアウト
  • TiKV
    • データ容量やI/O増加に応じてスケールアウト
  • どのサーバーに接続しても最新データにアクセス可
    • アプリケーション側でアクセス先を判断する必要なし
  • シャーディング
    • 内部でリージョン単位で分割
    • キー不要
  • スケールアップ
  • Spring Data JPA + Hibernate ORM
  • 外部キーはない
  • MySQL57Dialect
  • TiDBDialect
  • TiDB Cloud
  • TiFlash
  • スケールアップ
    • 載せ替え?
  • スケールアウト
    • TiUp?
    • 5-10分
    • TiKV だとデータの再バランスは少し時間かかりそう

RDRA + JavaによるレジャーSaaSプロダクトの要件定義と実装のシームレスな接続

  • 業務, BUC, アクティビティ
  • 情報
  • 関連する情報をどのようなユースケースで操作するのか
  • 情報は状態を持つ
    • どのUCが状態を遷移させるか
  • バリエーション/条件
    • システムがバリエーションごとにどのように振る舞いを変えるか
  • UCごとに条件 (ビジネスルール) を明確化
    • 消費税などは状態がないビジネスルールとして扱う
  • 情報 → 概念モデル
    • 情報の関連付けを整理する
    • オブジェクトモデル (具体例)
  • UC複合表
  • 外部システムの定義の網羅性を spreadsheet の関数で見つける
  • RDRA, 3層+ドメインアーキテクチャ
    • 1UC-1サービス
  • 状態を判断するのはドメインロジック
  • 状態を伝えるのは enum
  • UC実装
  • 条件を定義するのはドメインロジック
  • 条件を発動する (呼び出す) のは Validation, Controller, Service, ...
  • RDRA-Java の整合チェックは JIG ドキュメント

Spring Security の PasswordEncoder を移行したい

備忘録。

セキュリティ要件の変更などにより、Spring Security の PasswordEncoder (エンコード方式) を移行したいケースがあるとする。

AbstractDaoAuthenticationConfigurer#passwordEncoder を変更して DB に格納しているパスワードをマイグレーションするだけかと思ったけど、DB のパスワードはエンコードされているため機械的マイグレーションするのは無理そう。

Spring Security 5.x 以降であればマイグレーションの仕組みがあるっぽいけど、今回は 4.x のときにどうするかという話。(バージョンアップできるならそれでよし)

docs.spring.io

Migrating PasswordEncoder

2段階でパスワードをチェックする encoder を自作する。ここでは、BCryptPasswordEncoder から Pbkdf2PasswordEncoder に移行する例。

@Component
public class MigratingPasswordEncoder extends Pbkdf2PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return super.encode(rawPassword);
    }
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        try {
            // 新しい PasswordEncoder (PBKDF2) でチェックする
            return super.matches(rawPassword, encodedPassword);
        } catch (IllegalArgumentException e) {
            // チェックNGの場合は旧の PasswordEncoder (bcrypt) でチェックする
            return new BCryptPasswordEncoder().matches(rawPassword, encodedPassword);
        }
    }
}

これを AbstractDaoAuthenticationConfigurer#passwordEncoder に設定する。あとは、アカウント作成やパスワード変更の際に PBKDF2 でエンコードして DB に格納できるようにすれば、旧方式の既存パスワードを段階的に移行できるはず。

旧方式のときにパスワード変更を強制したい

AuthenticationSuccessHandler#onAuthenticationSuccess で、どちらの方式でエンコードされているか判定して、旧方式の場合はパスワード変更画面に飛ばすとかで対応できそう。ただし、旧方式のパスワードかどうかを判定できることが前提。

bcrypt から PBKDF2 の移行なら以下で判定できる。(bcrypt でエンコードした値は先頭が $2a$ で始まるため)

public boolean upgradeEncoding(String encoded) {
    try {
        Hex.decode(encoded);
        return false;
    } catch (Exception e) {
        return true;
    }
}

現場からは以上です。

プロダクトマネジメントのすべて 03

前回の続き。積読中の『プロダクトマネジメントのすべて』を (細々と) 読み進める。

プロダクトマネジメントのすべて 02 - kntmr-blog

今回は「ステークホルダーをまとめプロダクトチームを率いる」のところ。ちょっとやりすぎかもしれないけれど、バックログやタスクごとに RACI を決めてもいいかも。タスクの実行に関してメンバーの自律を促せるかもしれない。

動機づけの話は HIGH OUTPUT MANAGEMENT にも書いてあった気がする。あと、アウトソースは今まさに自分が気を付けないといけないところかもしれない...。

ドキュメンテーションに関してはプロダクトの階層ごとに書くべき情報が違いそう。たぶん。何か参考になるフォーマットないかなー。


以下、メモから抜粋。

Chapter 9

ステークホルダーとの関係
  • 誰がどんな権限を持っていて意思決定にどのように関与するか
  • DACI
    • Driver (推進者)
    • Approver (承認者)
    • Contributor (貢献者)
    • Informed (報告先)
プロダクトチームとの関係性
  • プロダクトチームのメンバーに権限を委譲する
    • 誰がタスクを完了する責任を持か可視化する
  • RACI
    • Responsible (実行責任者)
    • Accountable (説明責任者)
      • 成果物をレビューして外部に説明する責任を持つ
    • Consulted (協業先)
    • Informed (報告先)
プロダクトマネージャーの組織
  • プロダクト群全体のプロダクトマネージャー
    • プロダクト全体の方向性を決める
  • 個別機能はそれぞれのプロダクトマネージャーが担当する
    • プロダクトマネージャー間の責任範囲を明確に
    • 各プロダクトマネージャーがどのように貢献するかを可視化する
      • DACI, RACI
  • 意思決定のフローやドキュメント管理のルールを揃える
リーダーシップ
  • 内発的動機, 外発的動機
    • 興味/好奇心 (内発的) による動機づけを意識する
  • 極端なトップダウンボトムアップのバランスは弊害が大きい

Chapter 10

プロダクトに関する情報の透明化
  • コミュニケーションを可視化する
    • 意思決定の根拠や決定に至るまでの過程を共有する
  • プロダクト全体像を可視化する
    • 関わる機能が全体の中でどのような役割を担っているか
    • どうして必要なのか (Why) を理解する
    • 市場調査, ユーザーインタビュー, 仮説/検証結果 など
  • プロダクトの長期的な計画を共有する (ロードマップ)
チームビルディング
  • 心理的安全性, 信頼性, 目標設定 (期待値の調整), 仕事の目的, 成果の影響度
  • 認知共有
    • 価値観, 考え方, 組織文化, マナー を共有する
  • 動機づけ, 説明責任の意識づけ
    • 失敗を許容する文化
    • モチベーションを高く保つ, 責任を全うする
  • 快適ゾーン, 学習ゾーン, 不安ゾーン, 無関心ゾーン
    • 学習ゾーンを目指す (心理的安全性/説明責任が高い状態)
内製度の違い
  • アウトソースでも同じビジョンやミッションに沿って動くことを求める
  • 自社チームとアウトソースチームの役割を明確にする
    • DACI
  • アウトソースがプロダクト成長のボトルネックにならないか
チームビルディング
  • タックマンモデル
    • フォーミング (形成期)
    • ストーミング (混乱期)
    • ノーミング (統一期)
    • パフォーミング (機能期)
    • アジャーニング (散会期)
キックオフミーティング
  • 目的や全体像を共有する
  • チームのビジョンやミッションを共有する
  • インセプションデッキ
    • やらないことリスト
    • リスクを共有できる環境, 対策するひとを分散する仕組み
    • トレードオフスライダー (機能, 品質, 締切, 予算)
OKR / ふりかえり
  • 目標 (Objective)
    • 達成するもの, 定性的な目標
  • 主要結果 (Key Results)
    • 達成度を測るためのもの, 定量的な目標
    • 1つの目標に対して3-5個を定義する
  • ふりかえりでチームメンバーの働きを改善する
    • 問題 vs 私たち

Chapter 11

ドキュメンテーション
  • プロダクトの4階層に照らし合わせてドキュメントを作る
  • Product Requirements Document (PRD)
  • ビジョン/ステートメント (Core)
  • 戦略 (Core, Why)
    • 何をやるか or 何をやらないか
  • コンセプト (Why-What)
  • ロードマップ (What)
    • 何をどのような順番で作るかの見通し
  • プロダクト要件 (What-How)
コーチン
  • 傾聴, 問いかけ
    • 本人の気付きを促す
  • リフレクティングリスニング
プレゼンテーション
  • PREP
    • Point (結論) > Reason (理由) > Example (事例) > Point (結論)
  • プロダクトストーリー
    • どのようなユーザーがどのような状況でどのような課題を抱えているか
  • ストーリーテリング

プロダクトマネジメントのすべて 02

前回の続き。積読中の『プロダクトマネジメントのすべて』を読み進めてる。が、あまり進捗よくない...。

プロダクトマネジメントのすべて 01 - kntmr-blog

前回は基礎知識のところを読んだので、今回は冒頭の「プロダクトの成功」を斜め読み。ここでも前回と同じようにプロダクトステージがポイントっぽい。ステージに合わせてバランスやゴールを決めるのが大事。

4階層の Core, Why, What, How を Fit & Refine するのはまさにアジャイルなアプローチのような気がする。設計やプログラミングでも同じだけど、プロダクトを検討するときも抽象度を上げたり下げたりすることで改善していく感じ。


以下、メモから抜粋。

Chapter 1

プロダクトの成功
  • ビジョンの実現, ユーザー価値の向上, 事業収益の向上
    • 3要素のバランスを保つ
  • プロダクトマーケットフィット (PMF)
    • 価値検証を繰り返して PMF を見つける
  • 安定した事業収益を得る
プロダクト
  • プロダクト群
    • プロダクトを分割してプロダクトマネージャーの責任範囲を分担できる
  • プロダクト≒事業
  • プロダクトチーム
    • 機能型組織を横断したチームを構成する
プロダクトを検討するための4階層
  • Core, Why, What, How
    • ミッション, ビジョン
    • 誰にどんな価値を提供するのか
    • ユーザー体験, ビジネスモデル, ロードマップ, 指標の立案
    • どのように実現するのか
  • Fit & Refine
    • 階層間の整合性を保つ
    • 抽象度を変えながら検討する
プロダクトマネージャーに必要なスキル
  • 発想力, 計画力, 実行力, 仮説検証, リスク管理, チーム構築力

プロダクトマネジメントのすべて 01

しばらく積読しちゃってた『プロダクトマネジメントのすべて』を読む。

https://www.amazon.co.jp/dp/B08W51KLQJwww.amazon.co.jp

まずは前提知識として「プロダクトマネージャーに必要な基礎知識」から...。特に、Chapter 19 は参考になりました。プロダクトステージを認識して適切にアプローチするのが重要そう。Chapter 21 の QA との関わり方は悩ましいところだけど、単にテスターではなくて品質管理の役割としてコミュニケーションできるようにしたいし、ある程度、仕様ホルダーとしての立ち回りを期待しちゃう。

あと、アジャイル開発とWF開発は対立するものではなく直交する概念だと思うので、Git のブランチモデルなんかと同じように現場に合わせてカスタマイズするのがよいと思う。スクラムとカンバンについても同様。


以下、メモから抜粋。

Chapter 19

収益モデル
  • フリーユーザーから有料ユーザーへの転換は10%以下
  • ターゲットユーザーとの相性
  • プロダクトステージ
  • 競合優位性
収益拡大
コスト
  • 原価, 販売管理費, 研究開発費
  • ROI (費用対効果)
パートナーシップ
  • パートナーエコシステム
  • パートナー選定のための評価指標を作る
    • Core, Why に合っているか
    • ビジョンを達成できるか
指標
  • ユーザー継続率
  • 離脱率
    • 継続率とセットで計測する
    • AARRR モデル, ファネル分析
    • どの階層のユーザーに集中して働きかけるか
  • LTV > CAC
    • UE (= LTV ÷ CAC) ≧ 3 がプロダクトが順調に成長しているとみなせる
  • ARPU

Chapter 20

Chapter 21

品質
  • 品質の判断基準
    • ユーザー価値向上と事業収益向上の2視点を持つ
  • 品質基準
    • ユーザーニーズを満たせているか
    • ある品質が狩野モデルのどこに分類されるか
開発手法
  • アジャイル開発にWF開発のエッセンスを取り入れる
    • 設計や計画に時間をかける
  • スクラム, カンバン
  • スクラムチームのスケール
    • Scrum of Scrums
    • Large Scale Scrum (LeSS)
  • プロジェクトマネジメント
    • 作業とそれらの依存関係 (ガントチャート)
    • タスク全体の進捗の予実管理 (バーンダウンチャート)

CloudFront の TooManyInvalidationsInProgressException を CallerReference で抑止したい

前提

CloudFront のキャッシュを利用するシステムで、ユーザーの操作をトリガーに、条件に合致するオブジェクトパスに invalidation リクエストする仕様。

困ったこと

com.amazonaws.services.cloudfront.model.TooManyInvalidationsInProgressException: Processing your request will cause you to exceed the maximum number of in-progress wildcard invalidations.

進行中の invalidation プロセスにおいて、ワイルドカード (*) を含むオブジェクトパスの指定は最大15個までで、その上限を超えるとエラーが返る模様。

同時無効化リクエストの最大制限 | ファイルの無効化 - Amazon CloudFront

たまに CloudFront 側で進行中の invalidation が滞留することがある。invalidation のオブジェクトパスにワイルドカード (*) を含む場合に、表題のエラーが発生することがある。特に、同一ユーザーが連続して操作した際に、同様の invalidation プロセスが複数生成されてしまうのが痛い...。

対策

今回は、invalidation の CallerReference を利用してエラーの発生を抑えてみる。

単位時間あたりの同じオブジェクトパスに対する invalidation において、CallerReference に同じ値を指定することによって短時間で同じオブジェクトパスに対する invalidation が実行できなくなる。正確には、invalidation リクエストは投げるが、CloudFront 側で invalidation プロセスが生成されない。

CallerReference にどういう値を指定するか

docs.aws.amazon.com

このドキュメントの CallerReference に書かれているように、単純にタイムスタンプを指定するだけだと別々のユーザーが同時に操作した場合に invalidation がコンフリクトする可能性がある。というわけで、ユーザーのリクエスト情報を CallerReference に含める。

今回は、単位時間を1分間として、リクエストパラメータのオブジェクトパスを並び替えて結合したものを利用する。最終的には yyyyMMddHHmm_{param1}-{param2}-... のような文字列を CallerReference に指定する。

サンプルコードはこちら。

private String callerReference(List<String> params) {
    var str =  String.format("%s_%s",
            LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm")),
            String.join("-", params.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList()))
    );
    return str.substring(0, Math.min(str.length(), 128)); // max 128 characters
}

単位時間を3分間にするならこんな感じ。

var currentDateTime = LocalDateTime.now();
var rounded = currentDateTime.withMinute(currentDateTime.getMinute() - (currentDateTime.getMinute() % 3)).format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));

これで、短時間で同様の invalidation は実行されなくなるので、表題のエラー抑止にも効果がありそう。

補足)

com.amazonaws.services.cloudfront.model.InvalidArgumentException: The parameter CallerReference is too big.

CallerReference に指定できるのは最大128文字までの模様...。仕方ないので128文字で切り出している。