認可

Webアプリケーションでの認可の方法について説明します。

認可処理の実装には Spring Security を使用します。

以下のサンプルコードの動作確認環境については、 動作確認環境と依存ライブラリについて を参照してください。

ユーザに対して権限を設定する

この例では テーブル認証 を使用するため、同じようにユーザ名や権限(ロール)を保持するテーブルを事前に作成します。

create table users (
  username varchar(255) not null,
  password varchar(60) not null,
  primary key (username)
);

create table user_role (
  username varchar(255) not null,
  role     varchar(255) not null,
  primary key (username, role)
);

認証時にログインしたユーザの権限を保持する

認可を正しく行うために、認証時にログインしたユーザの権限を保持する必要があります。 この例ではデータベースで認証・認可情報を管理しているため、ユーザ情報を取得する際に権限情報も取得し、ユーザ情報に設定します。

// UserDetailsServiceを実装して、ユーザ名に紐づく情報を取得するloadUserByUsernameメソッドを実装します。
@Service
public class UserService implements UserDetailsService {

    private final UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Daoを使用してユーザ情報を取得します。
        // ユーザ情報が存在しない場合には、UsernameNotFoundExceptionを送出し
        // Spring Security側での認証エラーの処理が行われるようにします。
        return userDao.loadUserByUserName(username)
                      // ユーザ情報には、ログインユーザに割り当てられた権限(ロール)も設定します。
                      .map(e -> new User(e.getUsername(), e.getPassword(), loadAuthorities(e.getUsername())))
                      .orElseThrow(() -> new UsernameNotFoundException("user not found."));
    }

    /**
     * ユーザ名に紐づく権限リストを取得します。
     *
     * @param username ユーザ名
     * @return ユーザ名に紐づく権限リスト(存在しない場合は空のリスト)
     */
    private List<GrantedAuthority> loadAuthorities(String username) {
        return userDao.loadUserRoles(username)
                      .stream()
                      .map(SimpleGrantedAuthority::new)
                      .collect(toList());
    }
}

URLパターンごとにアクセスに必要な権限を設定する

URLパターンごとにアクセスに必要な権限を設定します。 この例では、 /admin 配下はadmin権限を持つユーザだけがアクセスできるように設定します。 /admin 以外については、認証済みのユーザであればすべてのユーザがアクセスできるように設定します。

また、admin権限についてはuser権限を含めた権限にするため、Hierarchical Roles を使用して階層構造を設定します。

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/admin/**").hasRole("admin")
                        .requestMatchers("/user/**").hasRole("user")
                        .anyRequest().authenticated())
                .formLogin(form -> form
                        .loginPage("/login")
                        .usernameParameter("username")
                        .passwordParameter("password")
                        .defaultSuccessUrl("/top", true)
                        .permitAll())
                .logout(logout -> logout
                        .logoutSuccessUrl("/login?logout")
                        .invalidateHttpSession(true)
                        .permitAll())
                .headers(configurer -> configurer
                        .referrerPolicy(referrer -> referrer
                                .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)))
                .build();
    }

    @Bean
    public RoleHierarchy roleHierarchy() {
        // 権限の階層構造の設定をします。
        // admin権限は、user権限を含む権限となります。
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        hierarchy.setHierarchy("ROLE_admin > ROLE_user");
        return hierarchy;
    }

アクセス権限に応じて画面上のリンクやボタンを非表示にする

Thymeleaf を使用している場合に、権限に応じて画面上にあるリンクやボタンの表示を制御する方法を説明します。

ThymeleafのSpring Security用ライブラリである thymeleaf-extras-springsecurity を依存関係に追加します。

    <dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-springsecurity6</artifactId>
    </dependency>

Tip

thymeleaf-extras-springsecurityは、Spring Securityのバージョンによってライブラリ名(artifactId)が異なります。例えば、Spring Security 6.Xを使用している場合には、thymeleaf-extras-springsecurity6を使用します。

thymeleaf-extras-springsecurityの機能を使って、権限がない場合にはリンクやボタンを非表示にします。 この例では、ログインしているユーザがGET /adminへの権限を保持している場合にリンクが表示されます。

<div>
  admin権限を持つユーザでログインした場合↓に管理者用ページへのリンクが表示されます<br />
  <a sec:authorize-url="GET /admin" th:href="@{/admin}">管理者用ページへ</a>
</div>

サンプル全体は table-authentication-sample を参照してください。