LoginSignup
6
10

More than 3 years have passed since last update.

SpringSecurity+MyBatis+MySQLでログイン機能を作る※余計なものはナシ※

Last updated at Posted at 2021-02-18

目標物

Videotogif (6).gif

  • usernameとpasswordでログイン
  • ログインができたら、helloに遷移
  • helloではログインusernameを表示する
  • ログアウトボタンでログアウトする

今回はvalidationやユーザの新規登録は実装しません。
ログイン⇒ログイン後画面⇒ログアウトのみの簡単な実装となります。

対象者

使っている環境

■OS : Windows10
■IDE : Eclipse
■Maven
■SpringBoot
■DB : MySQL8.0.22

構成

スクリーンショット 2021-02-08 235818.png

pom.xml

楽ちんなのでlombokを使っています。



        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
                <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/XXXXX?serverTimezone=JST
spring.datasource.username=XXXXXX
spring.datasource.password=XXXXXX
spring.datasource.schema=classpath:schema.sql
spring.datasource.data=classpath:data.sql
spring.datasource.initialization-mode=always

schem.sql


CREATE TABLE IF NOT EXISTS loginUser(
  userId BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(64) NOT NULL,
  password VARCHAR(128) NOT NULL
);

data.sql

パスワードの変換は こちら を使いました。

  • username hoge
  • password 12345

※今回は簡単なusername,passwordですが、本当に使うときはセキュリティ強化してください。


INSERT INTO loginUser(username, password) VALUES('hoge', '$08$yI3HNSrpzBKPbSUaAtTxB.DgkicwoYkLFoIsupYMV6BqJ1x9zeyHO');

SecurityConfig.java

WebSecurityConfigurerAdapterを継承すると、security関係の設定ができるクラスになります。

  • BCryptでパスワードを暗号化します
  • セキュリティの設定をします
  • 認証方法の設定をします

詳細はコメントアウトの通りです。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean //パスワードのエンコード
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override //securityの設定をする
    protected void configure(HttpSecurity http) throws Exception {

        http
                .authorizeRequests()//ルール、アクセスポリシーの設定
                .antMatchers("/login").permitAll()//loginは認証なしでaccessできる
                .anyRequest().authenticated()//↑以外のすべてのURLリクエストをloginしないと見れない
                .and()
                .formLogin()//ログインの設定
                .loginPage("/login")
                .defaultSuccessUrl("/hello", true)//ログインが成功したら/helloにいく
                .and()
                .logout()//ログアウトの設定
                .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));//logoutのURLを/logoutにする
    }

    @Override //認証方法の設定を行う
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth
                //userDetailsServiceを使って、認証を行う
                .userDetailsService(userDetailsService);
    }
}

MyUser.java

UserDetailsはインターフェースなので、処理は何も書いていないメソッドだけのクラスです。
なので実装する必要があります。

package com.example.demo.securityUser;

import java.util.Collection;

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

import lombok.Getter;
import lombok.Setter;


public class MyUser implements UserDetails{

    @Getter
    @Setter
    private String username;

    @Getter
    @Setter
    private String password;

    private Collection<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        // TODO 自動生成されたメソッド・スタブ
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        // TODO 自動生成されたメソッド・スタブ
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        // TODO 自動生成されたメソッド・スタブ
        return true;
    }

    @Override
    public boolean isEnabled() {
        // TODO 自動生成されたメソッド・スタブ
        return true;
    }

}

今回はusernameとpasswordだけしか使わないので、このふたつのgetter,setterを書きます。
その他は今回は使いませんが、実装は必要なのでtrueにしておいてください。
(実装されていないメソッドの追加をすると、勝手に書いてくれます。)
インターフェース UserDetails公式説明

スクリーンショット 2021-02-17 233459.png

UserDetailsServiceImpl

UserDetailsServiceを実装したクラスを作る。
必ずloadUserByUsername(String username)が必要になります。
入力したusernameがDBにあるか探して、UserDetailsに結果を返します。

package com.example.demo.SecurityUserDetails;

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;

import com.example.demo.securityMapper.UserMapper;
import com.example.demo.securityUser.MyUser;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired //mapperをインスタンス化。DB接続をするMapperクラスを参照
    private UserMapper userMapper;

    //loadUserByUsernameは実装が必要
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        //findByUsernameで見つけてきたユーザ情報をmyUserに入れる
        MyUser myUser = userMapper.findByUsername(username);

        //UserDetailsにreturn
        return myUser;

    }
}

UserMapper.java

Mapperクラスなのでinterfaceにします。
入力されたusernameで、一致したレコードを取ってきています。

package com.example.demo.securityMapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import com.example.demo.securityUser.MyUser;

@Mapper
public interface UserMapper {

    //select文。userテーブルから、usernameが一致しているものを検索
    @Select("select * from loginuser where username = #{username}")
    public MyUser findByUsername(String username);//識別する

}

SecurityController.java

独自のログイン画面に行く指示と、ログインが成功したらhello.htmlに行く指示をしています。
helloに行くときはusernameを表示できるように、Authenticationに入っているログイン情報を"username"に詰めます。

package com.example.demo.securityController;

import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SecurityController {

    @GetMapping("/login") //configのloginPageでloginに来るように指定したので
    public String login() {
        return "login";//自作のloginページを表示
    }

    @GetMapping("/hello") //configのdefaultSuccessUrlでhelloに来るように指定したので
    public String hello(Authentication loginUser,Model model) {

        /*AuthenticationでログインUserの情報を使うことができるので
        modelを使って、"username"にusernameを詰める*/

        model.addAttribute("username",loginUser.getName());

        return "hello";//helloを表示。usernameをhelloで表示できる
    }

}

login.html

なんでもいいのでform画面を作ります。
th:action="@{/login}" method="post"は絶対postにしてください。
デフォルトだとgetになってしまっているので、SpringSecurityの認証機能が使えなくなります。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>


    <div class="wrapper">
        <form th:action="@{/login}" method="post" name="Login_Form"
            class="form-signin">
            <h2 class="form-signin-heading">ログインしてください</h2>

            <hr class="colorgraph">
            <br>
            <input type="text"  name="username" placeholder="username" /><br>
            <input type="password"  name="password" placeholder="password" /> <br>
            <button name="Submit" value="Login" type="Submit">Login</button>
        </form>
        <br>
    </div>
</body>
</body>
</html>

hello.html

なんでもいいのでログイン成功後の画面を作ります。
controllerでusernameに名前を詰めたので、th:text="${username}"で表示できます。
th:action="@{/logout}"と書けばログアウトしてくれます。(勝手にやってくれる、すごい)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>

    <h1>ログイン成功</h1>
    <h2><span th:text="${username}" ></span> さんHello!!!!</h2>

    <form th:action="@{/logout}" method="post">
    <button>Logout</button>
    </form>

</body>
</html>

http://localhost:8080を実行する

port番号を指定していたら変えてください。
無事にログイン⇒ログアウトできれば成功です!
お疲れ様でした!

ソースコード

参考

YouTube
書籍:Spring Boot 2 入門: 基礎から実演まで
書籍:Spring解体新書
公式チュートリアル

はまったところ

  • Userクラスをimplementsしたクラスを作っていなかった。(作る意味が分からなかった)
  • login.htmlが th:action="@{/login}になっておらず、CSRF対策用になっていなかった(thがなかった。formに書いておくと勝手にCSRF対策してくれるようになる。)

CSRF・・・ログイン情報を保持したまま、悪意のある人がURLをクリックした場合、意図せず情報・リクエスト送信されてしまうこと。

最後に

スッキリしたログイン機能を作ってみました。
SpringSecurity、裏で色々やってくれすぎて難しいですね。

6
10
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
6
10