【Spring MVC + Spring Security】認可設定(アクセス制御)を行う方法

概要

特定のURLに対して認可ポリシーを定義して、アクセス制御を行う方法についてまとめた。

 

前提

以下の記事の続きとなる。

あわせて読みたい

概要 Spring Securityでは、認証済みユーザー情報はまずSecurityContextHolderクラスに格納され、最終的にセッションで管理される。 今回は、SecurityContextHolderに格納された認証済みユー[…]

 

認可ポリシー

認可ポリシーとは、「誰がどのリソースにアクセスできるか」を決めるルールのこと。
アクセスの可否はユーザーに割り当てられた「ロール」や「権限」によって決まる。

したがって、このルールを定義するには両者を理解しておく必要がある。

 

ロール

「ロール」とは、ユーザーの役割のこと。

例えば、以下のような「ロール」を定義してユーザーに付与できる。
「ロール」ごとに操作範囲が制限されており、他の「ロール」の操作はできない。

ロール名 意味 操作範囲
ROLE_USER 一般ユーザー 自分の情報閲覧・更新、基本機能の利用
ROLE_ADMIN 管理者 ユーザー管理、システム設定、監査
ROLE_GUEST ゲストユーザー 閲覧のみ、更新不可

 

権限

「権限」とは、ユーザーが保持する「操作権限」のこと。
フォルダのアクセス制御のように、CRUD操作に関する権限のようなイメージ。

例えば、以下のような「権限」を定義してユーザーに付与できる。
「権限」ごとに操作範囲が制限されており、他の「権限」の操作はできない。

権限名 意味 操作範囲
READ_PRIVILEGE 読み取り権限 データの参照、一覧表示
WRITE_PRIVILEGE 書き込み権限 新規作成、更新
DELETE_PRIVILEGE 削除権限 レコード削除、アカウント削除
EXECUTE_PRIVILEGE 実行権限 ジョブ起動

 

アーキテクチャ

Spring Securityにて認可処理を行う場合、AuthorizationFilterにてアクセス権チェックを行う。

具体的には、認証済みユーザー情報に付与された「ロール」又は「権限」と
特定のURLに対する認可ポリシーを照合してアクセス可能かどうかを判定する。

 

▲実際にはAuthorizationManagerに委譲して認可チェックを行う

 

 

実装

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();
    }
}

 

.authorizeHttpRequests(auth -> auth

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パターン」+「認可ポリシーメソッド」の組み合わせで定義する

 

スポンサーリンク