1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Securityの基本的な使い方

Last updated at Posted at 2024-09-30

ログイン機能では、基本的に認証/認可が必要になります。これをいちいち実装すると、何らかのミスで脆弱性につながってしまいます。Spring Bootでは、Spring Securityという認証/認可のためのフレームワークが用意されていますので、こちらを用いてカスタムした方が得策です。本記事では、Spring Securityの基本的な使い方を紹介します。ただし、Spring Bootでのアプリケーションの作り方に関しては説明しません。

前提

環境

言語 Java 17.x
フレームワーク spring boot 3.x
テンプレートエンジン thymeleaf
ビルドツール gradle
DB postgreSQL 11.x
IDE IntellJ

クラスファイル

本記事では、Test~.javaというクラスファイルが出てきた場合は、自作のクラスです。以下では、上記のクラスが断りなく出てきますので、ご了承下さい。

gradleの追加

build.gradledependependenciesにspring securityを追加します(テストやthymeleafも含めています)。

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.security:spring-security-test'

その後、build.gradleを再ロードします。

セキュリティ設定

Spring Securityに必要な設定をします。まず、その設定ファイルを作成します。どこに作っても良いですが、ここでは、com.example.commonSecurityConfig.javaを作成しました。

SpringConfig.java
package com.example.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

@Configuration
public class SecurityConfig {

    @Bean
    protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
        http.authorizeHttpRequests(authz -> authz
                .requestMatchers("/css/**", "/js/**", "/img/**").permitAll()
                .requestMatchers("/").permitAll()
                .requestMatchers("/toInsert").permitAll()
                .requestMatchers("/insert").permitAll()
                .anyRequest().authenticated()
        ).formLogin(login -> login
                .loginPage("/")
                .loginProcessingUrl("/login")
                .failureUrl("/?error=true")
                .defaultSuccessUrl("/topPage", true)
                .usernameParameter("mailAddress")
                .passwordParameter("password")
        ).logout(logout -> logout
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout**"))
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies("JSESSIONID")
        );

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

ルーティング

URL 説明
/ ログイン画面へのgetリクエスト
/toInsert ユーザの新規登録画面へのgetリクエスト
/insert ユーザの新規登録のpostリクエスト

アノテーション

  • @Configuration:このクラスがSpringの設定クラスであることを示します。

HttpSecurityの設定

securityFilterChainメソッドでは、認証、ログイン、ログアウトの動作を定義しています。

  • .authorizeHttpRequests(authz -> authz
    • requestMatchers(~).permitAll()
      • この設定により、指定されたURLパターンに対するリクエストは全て認証なしでアクセスできるようになります。
    • anyRequest().authenticated()
      • 上記で許可されていない他の全てのリクエスト認証を要求します。
  • .formLogin(login -> login
    • loginPage("/")
      • 自作のログインページのURLを指定します。認証が必要なページにアクセスした場合、このページにリダイレクトされます。ログインページへのURLをpermitAllにしておかないと、403エラーが返ってくるので、注意です。
    • loginProcessingUrl("/login")
      • ログインフォームが送信されるURLを指定します。このURLへのPOSTリクエストをSpring Securityが処理します。
    • failureUrl("/?error=true")
      • ログイン失敗時にリダイレクトされるURLを指定します。
      • errorというクエリパラメータを設定しておくことで、コントローラ側でそのパラメータでログインミスか判断できます。
    • defaultSuccessUrl("/employee/showList", true)
      • ログイン成功後のリダイレクト先URLを指定します。
      • trueは常にこのURLにリダイレクトすることを意味します。
      • falseにすると、認可が必要なページにアクセスする時に認証後、元々遷移しようとしたページに移動します。
    • usernameParameter("mailAddress")
      • ログインフォームのユーザー名フィールドのnameを指定します。
    • passwordParameter("password")
      • ログインフォームのパスワードフィールドのnameを指定します。
  • .logout(logout -> logout
    • logoutRequestMatcher(new AntPathRequestMatcher("/logout**"))
      • ログアウトリクエストのURLパターンを指定します。
    • logoutSuccessUrl("/")
      • ログアウト成功後のリダイレクト先URLを指定します。
    • invalidateHttpSession(true)
      • ログアウト時にHTTPセッションを無効にします。
    • deleteCookies("JSESSIONID")
      • ログアウト時に特定のクッキー(ここではJSESSIONID)を削除します。

PasswordEncoderの設定

  • パスワードをハッシュ化するためのPasswordEncoderを定義しています。ここではBCryptPasswordEncoderを使用しています。Spring Securityでは、認証時にハッシュ化されたパスワードで比較をします。そのため、このメソッドをユーザ登録の際に用いることにより、登録するパスワードをハッシュ化する必要があります。

ログイン処理用の管理者情報

Spring SecurityのUserクラスを継承し、ログイン処理用の管理者情報を保持するドメインを作成します。

LoginAdministrator.java
package com.example.domain;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

public class LoginAdministrator extends User {
    
    private final Administrator administrator;

    public LoginAdministrator(Administrator administrator, Collection<GrantedAuthority> authorities){
        super(administrator.getMailAddress(), administrator.getPassword(), authorities);
        this.administrator = administrator;
    }

    public Administrator getAdministrator() {
        return administrator;
    }
}

このクラスは、自身で作成したドメインであるAdministratorオブジェクトを内部に保持し、必要に応じてアクセスできるようにすることで、Spring Securityの認証および認可プロセス中にカスタムのユーザーデータを利用できるようにしています。

ログイン処理用のサービス

Spring Securityの認証プロセスで使用されるUserDetailsServiceインターフェースを実装したサービスクラスを作成します。

AdministratorDetailService.java
package com.example.service;

import com.example.domain.Administrator;
import com.example.domain.LoginAdministrator;
import com.example.repository.AdministratorRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;

@Service
public class AdministratorDetailsService implements UserDetailsService {

    @Autowired
    private TestRepository testRepository;

    @Override
    public UserDetails loadUserByUsername(String mailAddress) throws UsernameNotFoundException {
        Administrator administrator = testRepository.findByMailAddress(mailAddress);

        if(administrator == null){
            throw new UsernameNotFoundException("Not found mail address:" + mailAddress);
        }

        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new LoginAdministrator(administrator, authorities);
    }
}

loadUserByUsernameメソッドのオーバーライド

このメソッドは、ユーザー名(ここではメールアドレス)を使用してユーザー情報をロードするためのメソッドです。

  • Administratorオブジェクトをリポジトリを使ってメールアドレスで検索します。
  • administratorが見つからない場合、UsernameNotFoundExceptionをスローします。
  • ユーザーが見つかった場合、権限リスト(Collection<GrantedAuthority>)を作成し、デフォルトで ROLE_USER 権限を追加します。
  • 最後に、LoginAdministratorオブジェクトを作成して返します。このオブジェクトにはユーザーの詳細情報と権限リストが含まれます。

処理の流れ

最後に全体の処理の流れをまとめます。

  1. ユーザーのリクエスト:
    ユーザーが保護されたリソース(認証が必要なページやAPIエンドポイント)にアクセスしようとします。

  2. フィルターチェーンによるリクエストのキャッチ:
    Spring Securityのフィルターチェーンがリクエストをキャッチします。ここでは、主要なフィルターとして UsernamePasswordAuthenticationFilterが使用されますが、今回はSecurityConfigで作成したsecurityFilterChainメソッドが一部使われます。

  3. ユーザー認証情報の取得:
    ユーザーがログインフォームを使用して認証情報(メールアドレスとパスワード)を送信します。
    UsernamePasswordAuthenticationFilterがその認証情報をキャッチします。

  4. AuthenticationManagerによる認証処理:
    UsernamePasswordAuthenticationFilterは、送信された認証情報をAuthenticationManagerに渡します。
    AuthenticationManagerは、AdministratorDetailsServiceを使用してユーザー情報をロードします。

  5. AdministratorDetailsServiceの呼び出し:
    TestRepositoryを使用してデータベースからユーザー情報を取得します。

  6. ユーザー情報の検証:
    AdministratorDetailsServiceがユーザー情報を見つけられなかった場合、UsernameNotFoundExceptionがスローされます。
    ユーザー情報が見つかった場合、そのユーザーの詳細情報(ユーザー名、パスワード、権限)がLoginAdministratorオブジェクトとして返されます。

  7. 認証情報の確認:
    AuthenticationManagerUserDetailsオブジェクトを使用して、送信されたパスワードが正しいかどうかを確認します。
    正しい場合、ユーザーは認証され、認証情報がセッションに保存されます。

  8. アクセス許可の判断:
    認証が成功した後、Spring Securityはユーザーがアクセスしようとしているリソースに対する適切な権限を持っているかどうかを確認します。
    権限があれば、ユーザーはリソースにアクセスできます。権限がなければ、アクセス拒否(403 Forbidden)が発生します。

補足

ログイン成功/失敗時の共通処理

ログイン成功した時や失敗した時に行いたい処理がある場合の作成の仕方を紹介します。また、成功時も失敗時も同じような処理ですので、ここでは、成功時の処理のみ説明します。

AuthenticationSuccessHandlerの作成

AuthenticationSuccessHandlerをimplementsしたCustomAuthenticationSuccessHandlerクラスを作成します。そのクラスでは、onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,Authentication authentication)メソッドをオーバーライドします。

具体的には以下の通りです。

CustomAuthenticationSuccessHandler.java
package com.example.security;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 認証後の処理用のクラス.
 */
@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException{

        // ここに処理を書きます

        SavedRequest savedRequest = requestCache.getRequest(request, response);
        String contextPath = request.getContextPath();

        if(savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            response.sendRedirect(targetUrl);
        }else {
            response.sendRedirect(contextPath + "/");
        }
    }
}

SecurityConfigファイルに追加

CustomAuthenticationSuccessHandlerクラスをSecurityConfigクラスに追加します。

@Autowired
private CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

フィールド変数にcustomAuthenticationSuccessHandler変数を用意しておきます。

.formLogin(login -> login
                .loginPage("/")
                .loginProcessingUrl("/login")
                .failureUrl("/?error=true")
                .successHandler(customAuthenticationSuccessHandler)
                .usernameParameter("mailAddress")
                .passwordParameter("password")
)

ここでは、.successHandler(customAuthenticationSuccessHandler)を追加します。そのクラス内で、ログイン後のページ遷移の設定をしているので、.defaultSuccessUrl("/employee/showList", true)は削除しておきます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?