「複数のWebアプリを開発しているが、ログイン機能がバラバラで管理が大変…」
あなたは今、そんな課題に直面していませんか?
ユーザーはアプリごとにパスワードを覚える必要があり、開発者は各アプリに認証ロジックを実装しなければなりません。これは、ユーザー体験と開発効率の双方にとって大きな問題です。
こんにちは。システムエンジニアとして、多くのマイクロサービス連携プロジェクトに携わってきた筆者が、今回はJWT(JSON Web Token)ベースのSSO認証サービスの構築方法を、具体的なアーキテクチャから実装のポイントまで徹底解説します。
この記事を読めば、堅牢でスケーラブルな認証システムをどのように設計し、実装すればよいかが明確になり、あなたのプロジェクトを次のレベルへと引き上げることができるでしょう。
目次
- なぜSSO認証サービスが必要なのか?
- 認証サービスの核となる技術:JWTの構造と役割
- アーキテクチャの全体像:SSO認証サービスのデザイン
- 具体的な実装手順:各コンポーネントの役割とコード例
- まとめ:未来志向の認証基盤を構築する
なぜSSO認証サービスが必要なのか?
従来のモノリシックなアプリケーションでは、ログイン機能はアプリ内部に組み込まれていました。しかし、サービスが細分化され、マイクロサービス化が進むにつれて、このやり方では非効率になります。
認証を独立したサービスとして切り出すことで、以下のメリットが得られます。
- ユーザー体験の向上: ユーザーは一度ログインすれば、他のサービスでもシームレスに利用できます。
- 開発効率の向上: 各アプリケーションが認証ロジックを持つ必要がなくなり、本来のビジネスロジック開発に集中できます。
- セキュリティの一元管理: 認証・認可に関するセキュリティポリシーを単一のサービスで管理でき、脆弱性対策が容易になります。
認証サービスの核となる技術:JWTの構造と役割
JWTは、SSO認証サービスの中心的な役割を担います。
JWTは、**ステートレス(無状態)**な認証を実現するためのトークンです。ユーザーの状態をサーバーに保存しないため、どのサーバーがリクエストを受け取っても、トークンを検証するだけでユーザーを識別できます。
JWTは、以下の3つの部分から構成されています。
ヘッダー.ペイロード.署名
- ヘッダー (Header): 署名アルゴリズム(例: RS256)とトークンのタイプ(JWT)を定義します。
- ペイロード (Payload): ユーザーID、ロール、有効期限などの情報を格納します。ここにパスワードのような機密情報は含めません。
- 署名 (Signature): ヘッダーとペイロードが改ざんされていないことを保証するためのものです。
この署名があることで、JWTは安全に情報をやり取りできるのです。
アーキテクチャの全体像:SSO認証サービスのデザイン
- クライアント(ユーザー): ログイン後、認証サービスから受け取ったJWTをすべてのAPIリクエストに含めます。
- SSO認証サービス: 認証の全責任を担います。ログイン、ログアウト、トークン発行、更新、検証のAPIを提供します。
- 各マイクロサービス: JWT検証フィルターを実装し、受け取ったリクエストの認証状態をチェックします。有効なトークンを持たないリクエストは拒否します。
この設計により、各マイクロサービスはデータベースの認証テーブルやパスワードハッシュといった詳細な情報を知る必要がなくなります。
具体的な実装手順:各コンポーネントの役割とコード例
ステップ1:データベースの準備とBCryptによるパスワードハッシュ
パスワードは必ずハッシュ化して保存します。ここでは、パスワードハッシュに特化した**BCrypt**を使用します。
BCryptは、意図的に処理を遅くすることで総当たり攻撃への耐性を高め、自動的にソルトを生成・組み込むことでレインボーテーブル攻撃を防ぎます。
// PasswordUtil.java
public class PasswordUtil {
public static String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt(12)); // 12ラウンド
}
public static boolean checkPassword(String password, String hashed) {
return BCrypt.checkpw(password, hashed);
}
}
ステップ2:認証サービスのTomcat設定
Tomcatは、DataSourceRealmを使って認証情報を取得します。
META-INF/context.xml
データベース接続とDataSourceRealmを定義します。WEB-INF/web.xml
サーブレットフィルターとして**JWT検証フィルター**を登録します。
// web.xml
<filter>
<filter-name>JwtAuthenticationFilter</filter-name>
<filter-class>com.example.auth.filter.JwtAuthenticationFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>JwtAuthenticationFilter</filter-name>
<url-pattern>/api/*</url-pattern>
</filter-mapping>
ステップ3:JWT生成・検証ロジック
JWTの生成には**RS256(非対称鍵)**を使用し、認証サービスのみが持つ秘密鍵で署名します。
JWT生成(AuthService.java
)
// ログイン成功時にJWTを生成
String token = Jwts.builder()
.setSubject(user.getUserId())
.claim("roles", user.getRoles())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1時間
.signWith(privateKey, SignatureAlgorithm.RS256)
.compact();
JWT検証(JwtUtil.java
)
// 他のサービスがトークンを検証
public Jws<Claims> parseToken(String token) {
return Jwts.parserBuilder()
.setSigningKey(publicKey)
.build()
.parseClaimsJws(token);
}
公開鍵は`http://auth-service/api/config/jwks`のような公開エンドポイントで提供し、各マイクロサービスが動的に取得するように設計します。
ステップ4:リフレッシュトークンによるセッション維持
アクセストークンの有効期限が切れた場合、ユーザーはリフレッシュトークンを使って新しいアクセストークンを取得します。これにより、頻繁なログインを防ぎます。
フロー:
- ログイン時、認証サービスはアクセストークンとリフレッシュトークンを生成し、それぞれ返却します。
- リフレッシュトークンはデータベースに保存され、有効期限が長く設定されます。
- アクセストークンが期限切れになると、クライアントはリフレッシュトークンを使って
/api/refresh
エンドポイントにリクエストを送り、新しいアクセストークンを取得します。
これにより、ユーザーは長期間ログイン状態を維持でき、セキュリティも確保されます。
まとめ:未来志向の認証基盤を構築する
SSO認証サービスは、現代の複雑なWebアプリケーションをシンプルかつ安全に運用するために不可欠です。
JWTを核とし、パスワードに**BCrypt**、署名に**RS256**、そして**リフレッシュトークン**を組み合わせることで、堅牢でスケーラブルな認証システムを構築できます。この設計は、開発効率を大幅に改善し、将来的なサービス拡張にも柔軟に対応できる強固な基盤となるでしょう。
この記事を参考に、あなたのプロジェクトにSSO認証サービスを実装し、開発の未来を切り拓いてください。