🔖

Re:ゼロから始めるSpring Boot実践#1 WebBlog開発#3 ユーザログイン

に公開

機能一覧※再掲

  1. ユーザ
    1.1 新規登録
    1.2 ログイン ← 本記事の内容
    1.3 ユーザ情報取得
    1.4 ユーザ情報更新
    1.5 アバター画像変更
    1.6 パスワード変更
  2. 記事カテゴリ
  3. 記事管理

API設計

1.2 ログイン

1.2.1 基本情報

リクエストパス:/user/login
リクエスト方式:POST
インターフェース概要:このインターフェースはログインに使用される。

1.2.2 リクエストパラメータ

リクエストパラメータ形式:x-www-form-urlencoded

リクエストパラメータ説明:

パラメータ名 説明 必須 備考
username ユーザー名 string 必須 5~16文字の非空文字列
password パスワード string 必須 5~16文字の非空文字列

リクエストデータ例:

username=zhangsan&password=123456

1.2.3 レスポンスデータ

  • レスポンスデータタイプ:application/json

  • レスポンスパラメータ説明:

項目名 必須 デフォルト値 説明 その他情報
code number 必須 - レスポンスコード、0-成功、1-失敗
message string 任意 - メッセージ情報
data string 必須 - レスポンスデータ、JWTトークン
  • レスポンスデータ例:
{
    "code": 0,
    "message": "操作成功",
    "data": "ここはJWTが入る想定"
}

1.2.4 備考

ユーザーがログインに成功すると、システムは自動的にJWTトークンを発行。以後、すべてのリクエストでは、ブラウザがリクエストヘッダー(Authorization)にこのJWTトークンを付与してサーバに送信する必要がある。
ユーザーが未ログインの場合、HTTPレスポンスステータスコードは 401 となる。

API開発

1. 開発するメソッド・クラスを考える

1.1 ユーザログイン機能

  • UserControllerにloginメソッドを作成し以下の機能を提供
    (1). ユーザー名を用いてユーザを検索
    (2). ユーザが存在するかを判断
    (3). 入力PWが正しいかを判断
  • UserviceにfindeByUsernameメソッドを作成し以下の機能を提供
    (1). ユーザー名を用いてユーザを検索
  • UserMapperにユーザ名検索メソッドを作成以下のsql文を発行
    (1). select * from user where username=?;

1.2 ログインステータスの検証

  • 実現したいこと:
      (1). ログインしていない状態で、新規登録とログインAPIのみアクセス可能。
      (2). ログインしている状態で、上記2つAPI以外のAPIにアクセス可能。
    よって、JWT認証と用いたログインステータスを検証する共通機能を作成する。
    参考:https://qiita.com/asagohan2301/items/cef8bcb969fef9064a5c

  • 実装方法:
    (1). Interceptorを使ってAPI実行前にログインステータスを検証する共通処理を実行
    (2). /user/login と /user/register パスは除外

2. ログインステータスを検証する機能を作成

2.1 JWT生成・取得のメソッド

public class JwtUtil {
    //暗号キーを設定
    private static final String KEY = "rezerosb";
	
    //リクエストデータを受け取ってトークンを生成して返す。
    public static String genToken(Map<String, Object> claims) {
        return JWT.create()
                .withClaim("claims", claims)
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 *12 )) //有効期間を設定
                .sign(Algorithm.HMAC256(KEY));
    }
    //トークンを受け取って検証した後、リクエストデータを返す。
    public static Map<String, Object> parseToken(String token) {
        return JWT.require(Algorithm.HMAC256(KEY))
                .build()
                .verify(token)
                .getClaim("claims")
                .asMap();
    }
}

2.2 ログインステータスを検証するメソッド

@Component //自動でインスタンス化し、他クラスに注入できるようにするため。
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        //tokenを検証
        String token =  request.getHeader("Authorization");
        System.out.println(token);
        try {
            Map<String, Object> claims = JwtUtil.parseToken(token);
            return true;
        } catch (Exception e) {
            //httpステータスコード401
            response.setStatus(401);
            return false;
        }
    }
}

2.3 InterceptorをSpringの処理フローに追加

@Configuration //LoginInterceptorをSpring MVCの処理の流れに追加するため。
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        //ユーザ登録とユーザログインを対象外とする。
    registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");
    }
}

3. Controllerの作成

UserControllerクラスに以下のメソッドを追加

    @PostMapping("/login")
    public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
        //(1). ユーザー名を用いてユーザを検索
        User loginUser = userService.findByUserName(username);
        //(2). ユーザが存在するかを判断
        if (loginUser == null) {
            return Result.error("ユーザ名は存在しません。");
        }
        //(3). 入力PWが正しいかを判断
        if (Md5Util.getMD5String(password).equals(loginUser.getPassword())){
            //JWT生成
            Map<String, Object> claims = new HashMap<>();
            claims.put("id",loginUser.getId());
            claims.put("username",loginUser.getUsername());
            String token = JwtUtil.genToken(claims);
            System.out.println(token);
            return Result.success(token);
        }
        return Result.error("パスワードは正しくありません。");
    }

4. Seriveの作成

UserServiceImplには、findByUserNameメソッドはは作成済みのため作業不要。

4 Mapperの開発

ユーザ名でユーザ検索するメソッドは作成済みのため、作業不要。

APIテスト

  1. PostmanでログインAPIを叩いて、トークンを取得する。
  2. トークンをヘッダーについて、/article/list を叩て401にならないことを確認する。
    トークンがない場合: 401になる。

    トークンがある場合: 200 OKになる

Discussion