概要
特定のURLに対して認可ポリシーを定義して、アクセス制御を行う方法についてまとめた。
前提
以下の記事の続きとなる。
概要 Spring Securityでは、認証済みユーザー情報はまずSecurityContextHolderクラスに格納され、最終的にセッションで管理される。 今回は、SecurityContextHolderに格納された認証済みユー[…]
認可ポリシー
認可ポリシーとは、「誰がどのリソースにアクセスできるか」を決めるルールのこと。
アクセスの可否はユーザーに割り当てられた「ロール」や「権限」によって決まる。
したがって、このルールを定義するには両者を理解しておく必要がある。
ロール
「ロール」とは、ユーザーの役割のこと。
例えば、以下のような「ロール」を定義してユーザーに付与できる。
「ロール」ごとに操作範囲が制限されており、他の「ロール」の操作はできない。
| ロール名 | 意味 | 操作範囲 |
|---|---|---|
| ROLE_USER | 一般ユーザー | 自分の情報閲覧・更新、基本機能の利用 |
| ROLE_ADMIN | 管理者 | ユーザー管理、システム設定、監査 |
| ROLE_GUEST | ゲストユーザー | 閲覧のみ、更新不可 |
権限
「権限」とは、ユーザーが保持する「操作権限」のこと。
フォルダのアクセス制御のように、CRUD操作に関する権限のようなイメージ。
例えば、以下のような「権限」を定義してユーザーに付与できる。
「権限」ごとに操作範囲が制限されており、他の「権限」の操作はできない。
| 権限名 | 意味 | 操作範囲 |
|---|---|---|
| READ_PRIVILEGE | 読み取り権限 | データの参照、一覧表示 |
| WRITE_PRIVILEGE | 書き込み権限 | 新規作成、更新 |
| DELETE_PRIVILEGE | 削除権限 | レコード削除、アカウント削除 |
| EXECUTE_PRIVILEGE | 実行権限 | ジョブ起動 |
アーキテクチャ
Spring Securityにて認可処理を行う場合、AuthorizationFilterにてアクセス権チェックを行う。
具体的には、認証済みユーザー情報に付与された「ロール」又は「権限」と
特定のURLに対する認可ポリシーを照合してアクセス可能かどうかを判定する。

実装
SecurityFilterChainに認可ポリシーを定義する。
SecurityConfig.java
package com.example.prototype.biz.security.config;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.example.prototype.biz.security.handler.LoginFailureHandler;
import com.example.prototype.biz.security.handler.LoginSuccessHandler;
import com.example.prototype.biz.security.handler.LogoutSuccessHandler;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private LoginFailureHandler loginFailureHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
UserDetails disabledUser = User.withUsername("user")
.password(encoder.encode("password"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(disabledUser);
}
@Bean
public DaoAuthenticationProvider authenticationProvider(MessageSource messageSource,
PasswordEncoder passwordEncoder) {
var provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService(passwordEncoder));
provider.setPasswordEncoder(passwordEncoder);
provider.setMessageSource(messageSource);
return provider;
}
@Bean
public LogoutHandler compositeLogoutHandler() {
List<LogoutHandler> handlers = Arrays.asList(
new SecurityContextLogoutHandler(),
new LogoutSuccessHandler()
);
return new CompositeLogoutHandler(handlers);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// CSRF機能を無効
.csrf(csrf -> csrf.disable())
// 認可ポリシー定義
.authorizeHttpRequests(auth -> auth
// 認証不要のURL
.requestMatchers(
new AntPathRequestMatcher("/authentication"),
new AntPathRequestMatcher("/logout")
).permitAll()
// ロールベースの認可
.requestMatchers(new AntPathRequestMatcher("/user/**")).hasAnyRole("USER", "ADMIN")
.requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN")
// 権限ベースの認可
.requestMatchers(new AntPathRequestMatcher("/report/list")).hasAuthority("READ_PRIVILEGE")
.requestMatchers(new AntPathRequestMatcher("/user/delete")).hasAuthority("DELETE_PRIVILEGE")
// その他は認証済みなら許可
.anyRequest().authenticated())
// ログイン機能定義
.formLogin(form -> form
.loginPage("/authentication")
.loginProcessingUrl("/authentication/process")
.usernameParameter("loginId")
.passwordParameter("pass")
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler))
// ログアウト機能定義
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.addLogoutHandler(compositeLogoutHandler())
.logoutSuccessUrl("/authentication?logout=true")
);
return http.build();
}
}
HTTPリクエストに対する認可ポリシーを構成するためのメソッド。
SecurityFilterChainの中に、AuthorizationFilterなどの認可に必要なフィルター群が組み込まれる。
// 認証不要のURL
.requestMatchers(
new AntPathRequestMatcher(“/login”),
new AntPathRequestMatcher(“/session-invalid”)
).permitAll()
// ロールベースの認可
.requestMatchers(new AntPathRequestMatcher(“/user/**”)).hasAnyRole(“USER”, “ADMIN”)
.requestMatchers(new AntPathRequestMatcher(“/admin/**”)).hasRole(“ADMIN”)
// 権限ベースの認可
.requestMatchers(new AntPathRequestMatcher(“/report/list”)).hasAuthority(“READ_PRIVILEGE”)
.requestMatchers(new AntPathRequestMatcher(“/report/delete”)).hasAuthority(“DELETE_PRIVILEGE”)
// その他は認証済みなら許可
.anyRequest().authenticated()
認可ポリシーは、基本的に「URLパターン」+「認可ポリシーメソッド」で構成する。
URLパターンが複数存在する場合、上から順次判定される。
各ルールの意味は以下のようになる。
| 優先 | URLパターンマッチ | 認可ポリシーメソッド | 認可ポリシーの意味 |
|---|---|---|---|
| 1 | 「/login」、「/session-invalid」 | permitAll() | 認証不要(誰でもアクセス可能) |
| 2 | 「/user/**」 | hasAnyRole(“USER”, “ADMIN”) | 「ロール」が ROLE_USERまたはROLE_ADMINであれば、アクセス可能 |
| 3 | 「/admin/**」 | hasRole(“ADMIN”) | 「ロール」が ROLE_ADMINのみアクセス可能 |
| 4 | 「/report/list」 | hasAuthority(“READ_PRIVILEGE”) | 「権限」が READ_PRIVILEGEのみアクセス可能 |
| 5 | 「/user/delete」 | hasAuthority(“DELETE_PRIVILEGE”) | 「権限」が DELETE_PRIVILEGEのみアクセス可能 |
| 6 | その他すべてのURL | authenticated() | 認証済みであれば、誰でもアクセス可能 |
「ロール」を”USER”や”ADMIN”と記述した場合、内部的に”ROLE_”というプレフィックスが自動で付加される。
そのため、実際のロール名は”ROLE_USER”や”ROLE_ADMIN”となる。
一方で、「権限」についてはプレフィックスの自動付加は行われず、
指定した権限名そのもの(例:”DELETE_PRIVILEGE”)で判定される。
補足
認可ポリシーのメソッドには以下のような種類がある。
| メソッド | 説明 |
|---|---|
| hasRole(“A”) | 指定ロール(ROLE_A)を持つユーザーのみ許可 |
| hasAnyRole(“A”, “B”) | いずれかのロール(ROLE_A, ROLE_B)を持つユーザーを許可 |
| hasAuthority(“X”) | 指定権限(X)を持つユーザーのみ許可 |
| hasAnyAuthority(“X”, “Y”) | いずれかの権限(X, Y)を持つユーザーを許可 |
| permitAll() | 全ユーザーを許可(認証不要) |
| denyAll() | 全ユーザーを拒否 |
| authenticated() | 認証済みユーザーのみ許可 |
まとめ
☑ 認可ポリシーとは、特定のURLに対するアクセス制御ルールのこと
☑ authorizeHttpRequests()によって認可ルールを宣言する
☑ 認可ポリシーは「URLパターン」+「認可ポリシーメソッド」の組み合わせで定義する
