LoginSignup
26
30

More than 5 years have passed since last update.

Spring Boot で 認証機能を実装してみる

Posted at

はじめに

Spring Boot で Web アプリケーション環境を構築します。
今回は認証処理が必要なページを作ってみます。
認証情報(ユーザー名とパスワード)はちょっとだけ実践的に MySQL で管理することにします。

前回 に続き、Eclipse Neon 上で実装をしています。

環境

  • Windows 10
  • Eclipse Neon
  • Java 1.8.0_111
  • MySQL 5.7

必要なモジュールを指定する

Spring Security というモジュールで認証処理を実現できます。
データベース接続は Spring Data JPA というモジュールを使います。
ページ作成はテンプレートエンジン(Thymeleaf)を利用するので、これらのモジュールについても読み込んでおきます。

Eclipse で新規プロジェクト作成時に読み込むモジュールを指定できますが、常に新規プロジェクトからスタートできるとは限りませんよね・・・。ということで、今回は直接、build.gradle ファイルに定義を書き込みます。
build.gradle に dependencies という項目がありますので、このような感じに設定します。

build.gradle
dependencies {
  compile('org.springframework.boot:spring-boot-starter-security')
  compile('org.springframework.boot:spring-boot-starter-data-jpa')
  compile('org.springframework.boot:spring-boot-starter-thymeleaf')
  compile('org.springframework.boot:spring-boot-starter-web')
  compile('org.springframework.boot:spring-boot-devtools')
  compile('mysql:mysql-connector-java')
  compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5'
  testCompile('org.springframework.boot:spring-boot-starter-test')
}

データベース接続の設定

次にデータベース接続の設定です。
application.properties に設定します。必要に応じてポート番号も書いてください。

application.properties
spring.datasource.url=jdbc:mysql://[MySQL のサーバ名]/[データベース名]
spring.datasource.username=[ユーザ名]
spring.datasource.password=[パスワード]
spring.datasource.driverClassName=com.mysql.jdbc.Driver

実装

下準備が終わったので、いよいよ実装です。
フォーム認証が成功したらトップページを表示する仕組みを実装します。
認証が行われていない場合はログインページを表示します。

メンバー情報にアクセス

データベースにあるメンバー情報にアクセスする処理を実装します。
その前に、データベースにメンバー情報を管理するテーブルを作成しておきます。

create.sql
create table member(
    id BIGINT auto_increment,
    username VARCHAR(20) NOT NULL,
    password VARCHAR(20) NOT NULL,
    unique index (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO member (username, password)
    VALUES ('user', 'pass')
;

このソースはサンプルなので平文でパスワードを保存してますが、本物のデータが入る場合にこんな実装しちゃいかんです

エンティティクラスを用意

作成したテーブルに対応したエンティティクラスを用意します。認証情報に利用するクラスなので、UserDetails を継承しておきます。いろいろとメソッドがついてきます。メソッド名からわかるとおりロック状態などを返すものですが、今回は軒並み true を返すことにします。

MemberEntity.java
package com.example.entity;

import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

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

@Entity
@Table(name = "member")
public class MemberEntity implements UserDetails
{
    private static final long serialVersionUID = 1667698003975566301L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

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

    @Override
    public String getPassword()
    {
        return this.password;
    }

    @Override
    public String getUsername()
    {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }

    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }

    @Override
    public boolean isEnabled()
    {
        return true;
    }
}

レポジトリインターフェイスとサービスを作成

メンバー情報を取得するためのクラス(インターフェイス)を作成します。

MemberRepository.java
package com.example.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.entity.MemberEntity;

public interface MemberRepository extends JpaRepository<MemberEntity, Long>
{
    public MemberEntity findByUsername(String username);
}

さらに、レポジトリを呼び出すサービスクラスを作成します。

MemberServiceImpl.java
package com.example.service.impl;

import org.apache.commons.lang3.StringUtils;
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.entity.MemberEntity;
import com.example.repository.MemberRepository;

@Service
public class MemberServiceImpl implements UserDetailsService
{
    private MemberRepository memberRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        if (StringUtils.isEmpty(username))
        {
            throw new UsernameNotFoundException("");
        }

        MemberEntity memberEntity = memberRepository.findByUsername(username);
        if (memberEntity == null)
        {
            throw new UsernameNotFoundException("");
        }

        return memberEntity;
    }

    @Autowired
    public void setMemberRepository(MemberRepository memberRepository)
    {
        this.memberRepository = memberRepository;
    }
}

トップページを作成

認証が必要なページを作成します。

TopController.java
package com.example.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class TopController
{
    public static final String PAGE = "/";
    private static final String HTML = "top";

    @RequestMapping(value = TopController.PAGE, method = RequestMethod.GET)
    public String top(Model model)
    {
        return TopController.HTML;
    }
}

テンプレートファイルも作成しておきます。templates ディレクトリ以下に置いておきます。

top.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
  こんにちは!
  <form action="#" th:action="@{/logout}" method="POST">
    <input type="submit" value="ログアウト" />
  </form>
</body>
</html>

アクセス権を設定

コンフィグクラスを作成して、アクセス権に関する設定を行います。
設定情報をソースに記載するのがイマドキなのです!

WebSecurityConfig.java
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
            throws Exception
    {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService);
    }

    @Autowired
    public void setUserDetailsService(UserDetailsService userDetailsService)
    {
        this.userDetailsService = userDetailsService;
    }
}

動作確認

Run > Run As > Spring Boot App で起動します。
起動後、ブラウザで以下のURLにアクセスしてください。

いかがでしょうか、無事に動きましたか?

いろいろカスタマイズできるよ

ログインページ

ここまでは標準のログインページを利用しましたが、もちろん、独自に作成することができます。
ログインページのコントローラーとテンプレートを用意して・・・。

LoginController.java
package com.example.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController
{
    public static final String PAGE = "/login";
    private static final String HTML = "login";

    @RequestMapping(value = LoginController.PAGE)
    public String top(Model model)
    {
        return LoginController.HTML;
    }
}
login.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ログイン</title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
  <form action="" th:action="@{/login}" method="post">
    <p>
      ユーザーID:
      <input type="text" name="user" />
    </p>
    <p>
      パスワード:
      <input type="password" name="pass" />
    </p>
    <p>
      <input type="submit" value="ログイン" />
    </p>
  </form>
  <div th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null">
    <span th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}"></span>
  </div>
</body>
</html>

設定を追加します。
定義内にユーザ名とパスワードのパラメータ名を記載する箇所がありますので、HTMLと一致するように記載してください。

WebSecurityConfig.java
package com.example.config;

import org.springframework.beans.factory.annotation.Autowired;
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 com.example.web.LoginController;

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity.authorizeRequests().anyRequest().authenticated();
        httpSecurity.formLogin().loginPage(LoginController.PAGE).usernameParameter("user")
                .passwordParameter("pass").permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
            throws Exception
    {
        authenticationManagerBuilder.userDetailsService(this.userDetailsService);
    }

    @Autowired
    public void setUserDetailsService(UserDetailsService userDetailsService)
    {
        this.userDetailsService = userDetailsService;
    }
}

ちょっと解説

Spring Security

Spring Security はセキュリティ対策を行うモジュールです。今回は認証機能を実装しましたが、CSRF対策など、いろいろな機能を持っています。認証機能も、BASIC認証にも対応していたり、認証対象外のパスを指定したりとカスタマイズが可能です。

Spring Data JPA

Spring Data JPA は SQL を書かずにデータベースにアクセスを行うモジュールです。基本的な処理(今回の場合は username という項目に該当するレコードを取得)はメソッド定義だけで完結することができます。便利!
奥深い世界が広がっているので詳しくはググっていただければ・・・と思いますが、ビリヤード協会とか、パラグライダー協会とか、パワーリフティング協会などの情報がヒットしてしまうので、ご注意を。。

参考

26
30
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
26
30