2
2

More than 1 year has passed since last update.

独自ログイン機能(UserDetailsService編)

Posted at

独自のログイン機能でユーザー情報を取得するための実装です。かなりハマりましたので備忘録としての記録。

O/R mapperとしてMyBatisを利用しております。

Configurer設定についてはこちら→

UserDetailsServiceインターフェースを継承したクラスを作成

package io.post.novel.service;

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 io.post.novel.mapper.LoginMapper;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    LoginMapper loginMapper;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        return loginMapper.identifyUser(username);
    }

}
  • @Serviceアノテーションを付ける。
  • @Autowired LoginMapper loginMapper;にて依存性注入をしておく。

UserDetails loadUserByUsername(String username)メソッド

  • 取得した認証情報をUserDtailsに渡して認証するためのメソッド。
  • returnloginMapper.identifyUser(username);はMyBatisで取得したユーザー情報のオブジェクトを渡す。正しくSQLで取得できていれば、このクラスの実装はこれだけ。

MyBatisが取ってきた認証情報を格納するクラス

package io.post.novel.auth;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

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

import lombok.Data;

@Data
public class UserForm implements UserDetails {

    private static final long serialVersionUID = 1L;

//DBのテーブル
  private long id;
    private String penName;//認証のID
    private String password;//認証のPW
    private List<String> roles;//ユーザーロール
    private boolean locked;//アカウントロック
    private boolean expired;//アカウント有効化

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        return roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {

        return password;
    }

    @Override
    public String getUsername() {

        return penName;
    }

    @Override
    public boolean isAccountNonExpired() {

        return !expired;
    }

    @Override
    public boolean isAccountNonLocked() {

        return !locked;
    }

    @Override
    public boolean isCredentialsNonExpired() {

        return !expired;
    }

    @Override
    public boolean isEnabled() {

        return !locked;
    }

}

ユーザー情報を格納するUserFormクラス

  • UserDtailsインターフェースを継承したクラス
  • import lombok.Data;@Dataアノテーションでゲッターセッターを自動生成してくれる
  • このクラスの情報がセッションに保存されるため、個人情報保護の観点で、ログイン認証に必要な最低限の情報を持たせればよいと思います。(個人の感想)
  • ユーザーロールは一人で2つ以上持っている可能性を考慮しコレクション型でリストを格納するように実装してます。

MyBatisのMapperインターフェースの実装

package io.post.novel.mapper;

import org.apache.ibatis.annotations.Mapper;

import io.post.novel.auth.UserForm;

@Mapper
public interface LoginMapper {

    public UserForm identifyUser(String username);
  • @Mapperアノテーションを付ける
  • 記述としては上記メソッド一つの実装のみ。
  • SQLの中身は同じパッケージ改装のxmlファイルに記述する

Untitled2.png

Mapper.xmlファイルの実装

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace = "io.post.novel.mapper.LoginMapper">

    <select id = "identifyUser" parameterType = "java.lang.String" resultMap = "userDetails">
    <![CDATA[
        SELECT users.id,
               users.pen_name,
               users.password,
               users.locked,
               users.expired,
         user_roles.role_id,
               role_master.role_name
        FROM   users
        INNER JOIN
                (
                    SELECT users.id,
                        @RN := @RN + 1 AS RN
                    FROM    users,
                            (SELECT @RN := 0)   RC
                    WHERE   users.pen_name = #{penName}
                    ) RC
        ON     users.id = RC.id
        INNER JOIN user_roles
        ON     user_roles.user_id = users.id
        INNER JOIN role_master
        ON     role_master.id = 1
        AND    role_master.id = user_roles.role_id
        WHERE users.pen_name = #{penName}
        AND RC.RN = 1

        ]]>
        </select>

        <resultMap type="io.post.novel.auth.UserForm" id="userDetails">
            <result property = "id" column = "id" />
            <result property = "penName" column = "pen_name" />
            <result property = "password" column = "password" />
            <result property = "locked" column = "locked" />
            <result property = "expired" column = "expired" />
            <collection property="roles" ofType = "java.lang.String">
                <result column = "role_name"/>
            </collection>
        </resultMap>
</mapper>
  • <mapper namespace = "io.post.novel.mapper.LoginMapper">で対応するMapperファイルを指定する。パッケージも含めて指定が必要なので注意。
  • <resultMap type="io.post.novel.auth.UserForm" id="userDetails">でUserFormクラスのプロパティとDBのカラムを関連付けをする。type指定はパッケージも含めるので注意。
  • <select id = "identifyUser" parameterType = "java.lang.String" resultMap = "userDetails">のid属性にMapperインターフェースで実装したメソッド名を指定する。このメソッドとSQL一本が対応する。resultMap属性でカラムの紐付けを行ったresultMapのidをしていして関連付ける。
  • <![CDATAセクションで囲むと、その中を文字列として認識するので<や>の記号をそのまま使える

エラーが発生する場合

  • SQLExceptionが発生している場合は、Mapperは動作しているので、カラムとプロパティの紐付けを見直したり、SQL文そのものがおかしい可能性あり。また外部キー制約などDB側の設定も確認をする。
  • XMLファイル内のMySQL上で目的のレコードをとってこれるか試してみる。
  • #{  }で囲まれた変数に値が入っているかデバッグなどで確認する。
2
2
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
2
2