1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【Spring Boot】ログイン機能とログインユーザーの取得

Last updated at Posted at 2024-07-10

Spring Bootを使ってログイン機能を作成しようとしたら思ったより複雑だったので記録(2022時点で動作確認済み)

前提

version

java 11
maven 3.6.3
Spring Boot 2.7.0

必要なdependencyの追加

pom.xml
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

次のようなアカウントのEntityクラスを使用想定

Account.java
@Entity
@Table(name = "accounts")
@Getter
@Setter
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column
    private Long id;
    @Column(nullable = false)
    private String name;
    @Column(nullable = false)
    private String password;
}

1. 認証の設定

WebSecurityConfigクラスの作成
認証の適用する範囲やどのように認証を行うか設定する

WebSecurityConfig.java
package com.example.todo;

import com.example.todo.action.auth.AccountDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/register").permitAll() // /regisdterにアクセス許可
                .anyRequest().authenticated() // 全てのリクエストに認証設定
                .and()
                .formLogin() // フォームログインで認証する
                .loginPage("/login") // ログインページのURL
                .loginProcessingUrl("/login") // ログイン処理を行うURL
                .usernameParameter("name") // 認証にnameとpasswordを使用
                .passwordParameter("password")
                .defaultSuccessUrl("/index") // ログイン後に飛ぶURL
                .failureUrl("/login?error") // ログイン失敗時に飛ぶURL
                .permitAll() // /loginページにアクセス許可
                .and()
                .logout()
                .logoutUrl("/logout") // ログアウト処理を行うURL
                .logoutSuccessUrl("/login") // ログアウト後に飛ぶURL
                .permitAll();
    }
}
  • @EnableWebSecurity:Spring Securityを有効化
  • WebSecurityConfigurerAdapterを継承

2. 認証サービスの実装

ユーザー情報クラス

ユーザー情報を保持するクラス

AccountDetail.java
package com.example.todo.action.auth;

import com.example.todo.entity.Account;
import org.springframework.security.core.userdetails.User;
import java.util.ArrayList;
import lombok.Getter;

public class AccountDetail extends User{
    @Getter
    private Account account;
    public AccountDetail(Account account){
        super(
                account.getName(),
                account.getPassword(),
                true,
                true,
                true,
                true,
                new ArrayList<>()
        );
        this.account = account;
    }
}
  • Userクラスを継承
  • UserクラスはUserDetailsインターフェイスを実装
  • name, passwordをユーザー情報として保持

ユーザー情報サービスクラス

ログインフォームで入力されたusernameからユーザーが存在するか検索

AccountDetailsService.java
package com.example.todo.action.auth;

import com.example.todo.entity.Account;
import com.example.todo.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
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;

@Service
public class AccountDetailsService implements UserDetailsService {
    
    @Autowired
    private AccountRepository repository;

    /**
     * 認証処理
     * @param name (Account.name)
     * @return AccountDetail
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        Account account = repository.findByName(name);
        if (account == null) {
            throw new UsernameNotFoundException("User " + name + " not found");
        }
        return new AccountDetail(account);
    }
}
  • UserDetailsServiceを継承
  • loadUserByUsernameの引数に入力されたnameが渡される
  • nameに一致するユーザー情報が存在するかをチェック

リポジトリ

AccountRepository.java
@Repository
public interface AccountRepository extends JpaRepository<Account,Long> {
    @Query("SELECT a FROM Account a WHERE a.name = :name")
    Account findByName(String name);
}
  • 存在した場合はユーザー情報のオブジェクトを生成

ここではnameだけで認証を行なっているので、WebSecurityConfigAuthenticationManagerBuilderを使用。

WebSecurityConfig.java
package com.example.todo;

import com.example.todo.action.auth.AccountDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
    
    @Autowired
    private AccountDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/register").permitAll() // /regisdterにアクセス許可
                .anyRequest().authenticated() // 全てのリクエストに認証設定
                .and()
                .formLogin() // フォームログインで認証する
                .loginPage("/login") // ログインページのURL
                .loginProcessingUrl("/login") // ログイン処理を行うURL
                .usernameParameter("name") // 認証にnameとpasswordを使用
                .passwordParameter("password")
                .defaultSuccessUrl("/index") // ログイン後に飛ぶURL
                .failureUrl("/login?error") // ログイン失敗時に飛ぶURL
                .permitAll() // /loginページにアクセス許可
                .and()
                .logout()
                .logoutUrl("/logout") // ログアウト処理を行うURL
                .logoutSuccessUrl("/login") // ログアウト後に飛ぶURL
                .permitAll();
    }

    /**
     * Passwordのハッシュ化メソッド
     * 認証時に使用
     * @return BCryptPasswordEncoder
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 認証処理時に呼び出されるメソッド
     * 入力されたpasswordをハッシュ化してチェック
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
}
  • passwordEncoder:入力されたパスワードをハッシュ化
  • DBのパスワードと一致するか確認
    -> これによってnameとpasswordで認証が可能に

3. ログインフォームの作成

自作ログインフォームの作成

login.html
<label th:if="${error}" th:text="${error}" class="alert alert-danger text-center"></label>
<form class="mt-sm-4" method="post" th:action="@{/login}" aria-label="Login">
  <div class="mb-3 input-group-lg">
    <label>Name</label>
    <input class="form-control" type="text" name="name" required>
  </div>
  <div class="mb-3 input-group-lg">
    <label>Password</label>
    <input class="form-control" type="password" name="password" required>
  </div>
  <div class="d-grid">
    <button type="submit" class="btn btn-lg btn-primary">Login</button>
  </div>
</form>

フォームの表示

AccountController.java
@RequestMapping(value = "/login")
public String showLoginForm(@RequestParam(name = "error",required = false) String error,Model model){
    if(error!=null){
        model.addAttribute("error","ログイン名またはパスワードが間違っています");
    }
    return "login";
}
  • /login?error:ログイン失敗時にエラーメッセージを表示

4. ログアウト

<form class="d-flex" method="post" th:action="@{/logout}">
  <button class="btn btn-outline-secondary" type="submit">Logout</button>
</form>
  • POSTメソッドで/logout
  • Controllerの設定は不要
1
4
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
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?