Web 開発再入門 #11 ― モデル処理

# Web 開発再入門 #11 ― モデル処理

fmockup

## はじめに

Web アプリケーションのサーバー・サイドを開発します。
MVC の Model 部分となります。

MVC：Model View Controller

## 画面ショット

ログイン画面で認証 Web API を呼び出したとき、Web API の Model 部分でエラーを出しています。

![Java-fmockup_15-loginVerificationError.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3529613/f27b4504-da2f-55fe-9d75-a9c6a280205f.jpeg)

## フォルダー・ファイル構成

D:\\
└ Development\\
&nbsp;&nbsp;&nbsp;&nbsp;└ Workspace\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ fmockup\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ build\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ sql\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ src\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;└ main\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ java\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;└ cn\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ com\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ xxxx\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ fmockup\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ action\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ controller\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ customizer\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ entity\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ mapper\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ response\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ service\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;└ SessionAuthService.java ← コレ
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ util\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;└ CryptoUtil.java ← コレ
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ validator\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ validator_order\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;│&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ resources\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├ vue-vite\\
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└ WinSW.NET-nnn\\

## ファイルの文字コード

基本的に Eclipse でファイルを作成するので、あまり意識したことがありません。
多分、Unix 改行（LF）なのだと思います。

## ファイルの作成

1. Eclipse で、“SessionAuthService.java”、“CryptoUtil.java” を作成する。   
    ```java:SessionAuthService.java
    ・・・
    
    package cn.com.xxxx.fmockup.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import cn.com.xxxx.fmockup.entity.SessionAuthEntity;
    import cn.com.xxxx.fmockup.mapper.SessionAuthMapper;
    import cn.com.xxxx.fmockup.util.CryptoUtil;
    import cn.com.xxxx.fmockup.util.LogUtil;
    import cn.com.xxxx.fmockup.util.MessageUtil;
    import cn.com.xxxx.fmockup.util.PropertyUtil;
    import cn.com.xxxx.fmockup.validator.SessionAuthValidator;
    
    /**
     * Session Auth Service
     *   It processes login data.
     */
    
    @Service
    public class SessionAuthService {
    
    	@Autowired
    	private SessionAuthMapper mapper;
    
    	/**
    	 * Session Auth Service
    	 *   It processes login data.
    	 */
    	public SessionAuthEntity searchOneUser(SessionAuthValidator validator) throws Exception {
    
    		//
    		// If an error occurs in this process, it must output a blurred explanation in error message.
    		//
    
    		// Get Values from Database
    		SessionAuthEntity entity;
    		try {
    			entity = mapper.searchOneUser(validator);
    		} catch (Exception ex) {
    			LogUtil.info("searchOneUser : '" + ex.getMessage() + "'");
    			throw new Exception(MessageUtil.COMMON_ERROR_STRING_UNKNOWN_TROUBLE_HAS_OCCURRED);
    		}
    		if (entity == null) {
    			LogUtil.info("searchOneUser : 'User does not exists.'");															// A true problem explanation
    			throw new Exception(MessageUtil.getMessage(MessageUtil.LOGIN_ERROR_TITLE_USER_ID_OR_PASSWORD_IS_NOT_CORRECT));		// A blurred explanation
    		}
    
    		// Check Retry Count
    		if (entity.getFailedCount() >= PropertyUtil.getLoginRetryCount()) {
    			LogUtil.info("searchOneUser : 'Retry Count is overed by maximum count.'");											// A true problem explanation
    			throw new Exception(MessageUtil.getMessage(MessageUtil.LOGIN_ERROR_TITLE_USER_ID_OR_PASSWORD_IS_NOT_CORRECT));		// A blurred explanation
    		}
    
    		// Check Match Password
    		String password;
    		try {
    			password = CryptoUtil.decrypt(entity.getPasswordAes());
                // ★★★ ↑ CryptoUtil.java ★★★
    		} catch (Exception ex) {
    			LogUtil.info("searchOneUser : '" + ex.getMessage() + "'");		// A true problem explanation
    			throw new Exception(MessageUtil.getMessage(MessageUtil.LOGIN_ERROR_TITLE_USER_ID_OR_PASSWORD_IS_NOT_CORRECT));	// A blurred explanation
    		}
    		if (! password.equals(validator.getPassword())) {
    
    			try {
    				mapper.updateUserFailedCount(entity.getUserId());
    			} catch (Exception ex) {
    				LogUtil.info("searchOneUser : '" + ex.getMessage() + "'");
    				throw new Exception(MessageUtil.COMMON_ERROR_STRING_UNKNOWN_TROUBLE_HAS_OCCURRED);
    			}
    
    			LogUtil.info("searchOneUser : 'Password is not correct.'");														// A true problem explanation
    			throw new Exception(MessageUtil.getMessage(MessageUtil.LOGIN_ERROR_TITLE_USER_ID_OR_PASSWORD_IS_NOT_CORRECT));	// A blurred explanation
    		}
    
    		//
    		// Because authorization process is already ended,
    		// so if an error occurs in this process, it can output a true problem explanation in error message.
    		//
    
    		// Set Values to Database
    		int count;
    		try {
    			count = mapper.resetUserFailedCount(entity.getUserId());
    		} catch (Exception ex) {
    			LogUtil.info("searchOneUser : '" + ex.getMessage() + "'");
    			throw new Exception(MessageUtil.COMMON_ERROR_STRING_UNKNOWN_TROUBLE_HAS_OCCURRED);
    		}
    		if (count == 0) {
    			LogUtil.info("searchOneUser : 'This Account does not exist.'");
    			throw new Exception(MessageUtil.getMessage(MessageUtil.LOGIN_ERROR_TITLE_THIS_ACCOUNT_DOES_NOT_EXIST)); 
    		} else if (count > 1) {
    			LogUtil.info("searchOneUser : 'Assertion : Invalid count of manipulate data.'");
    			throw new Exception(MessageUtil.COMMON_ERROR_STRING_UNKNOWN_TROUBLE_HAS_OCCURRED);
    		}
    
    		return entity;
    	}
    
    	public void searchOneUserForSession(String userId, String tranName) throws Exception {
    
    		//
    		// Because authorization process is already ended,
    		// so if an error occurs in this process, it can output a true problem explanation in error message.
    		//
    
    		// Get Values from Database
    		SessionAuthEntity entity;
    		try {
    			entity = mapper.searchOneUserForSession(userId);
    		} catch (Exception ex) {
    			LogUtil.info("searchOneUserForSession : '" + ex.getMessage() + "'");
    			throw new Exception(MessageUtil.COMMON_ERROR_STRING_UNKNOWN_TROUBLE_HAS_OCCURRED);
    		}
    		if (entity == null) {
    			LogUtil.info("searchOneUserForSession : 'Yout Account does not exist.'");
    			throw new Exception(MessageUtil.getMessage(MessageUtil.USER_ERROR_TITLE_THIS_USER_DOES_NOT_EXIST));
    		}
    
    		// Check Account Is Locked or Not
    		if (entity.getUserLockedF().equals("Y")) {
    			LogUtil.info("searchOneUserForSession : 'Your Account was locked. Please logout, and try to login again.'");
    			throw new Exception(MessageUtil.getMessage(MessageUtil.ACCOUNT_ERROR_TITLE_YOUR_ACCOUNT_WAS_LOCKED_PLEASE_LOGOUT_AND_TRY_TO_LOGIN_AGAIN));
    		}
    
    		// Check Access Control
    		int count;
    		try {
    			count = mapper.searchOneUserTranCount(userId, tranName);
    		} catch (Exception ex) {
    			LogUtil.info("searchOneUserForSession : '" + ex.getMessage() + "'");
    			throw new Exception(MessageUtil.COMMON_ERROR_STRING_UNKNOWN_TROUBLE_HAS_OCCURRED);
    		}
    		if (count == 0) {
    			LogUtil.info("searchOneUserForSession : 'Your Account access is restricted.'");
    			throw new Exception(MessageUtil.getMessage(MessageUtil.ACCOUNT_ERROR_TITLE_YOUR_ACCOUNT_IS_RESTRICTED));
    		}
    
    		return;
    	}
    }
    ```
    説明：
    モデルの処理を行います。データベースをアクセスしたり、パスワードをチェックしてエラーを発行したり。
    ```java:CryptoUtil.java
    ・・・
    
    package cn.com.xxxx.fmockup.util;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    
    /**
     * Cripto Utility
     *   It encrypts or decrypts data.
     */
    
    public class CryptoUtil {
    
    	private static String charset = "utf-8";
    
    	/**
    	 * Cripto Utility
    	 *   It encrypts data.
    	 */
    	public static String encrypt(String inString, int charLength) throws Exception {
    
    		// Feature TODO : Change from "AES" to "AES/GCM/NoPadding".
    
    		String outString;
    		try {
    			SecretKeySpec key = new SecretKeySpec(PropertyUtil.getCryptoKey().getBytes(charset), "AES");
    			Cipher cipher = Cipher.getInstance("AES");
    			cipher.init(Cipher.ENCRYPT_MODE, key);
    			byte[] in = inString.getBytes(charset);
    			byte[] out = cipher.doFinal(in);
    			outString = new String(bin2hex(out));
    		} catch (Exception e) {
    			throw new Exception("Eecrypt trouble has occurred.");
    		}
    
    		int length = 16 * ( ( charLength * 6 / 16 ) + 1 ) * 2;
    
    		// Assertion
    		if (outString.length() > length) {
    			throw new Exception("Assertion : Invalid encrypt length (" + String.valueOf(outString.length()) + " > " + String.valueOf(length) + ")");
    		}
    
    		return outString;
    	}
    
    	/**
    	 * Cripto Utility
    	 *   It decrypts data.
    	 */
    	public static String decrypt(String inString) throws Exception {
    
    		String outString;
    		try {
    			SecretKeySpec key = new SecretKeySpec(PropertyUtil.getCryptoKey().getBytes(charset), "AES");
    			Cipher cipher = Cipher.getInstance("AES");
    			cipher.init(Cipher.DECRYPT_MODE, key);
    			byte[] in = hex2bin(inString);
    			byte[] out = cipher.doFinal(in);
    			outString = new String(out, charset);
    		} catch (Exception ex) {
    				throw new Exception("Decrypt trouble occurred.");
    		}
    
    		return outString;
    	}
    
    	// bin to hex
    	private static String bin2hex(byte[] data) {
    		StringBuffer sb = new StringBuffer();
    		for (byte b : data) {
    			String s = Integer.toHexString(0xff & b);
    			if (s.length() == 1) {
    				sb.append("0");
    			}
    			sb.append(s);
    		}
    	    return sb.toString();
    	}
    
    	// hex to bin
    	private static byte[] hex2bin(String hex) {
    		byte[] bytes = new byte[hex.length() / 2];
    		for (int index = 0; index < bytes.length; index++) {
    			bytes[index] = (byte) Integer.parseInt(hex.substring(index * 2, (index + 1) * 2), 16);
    		}
    		return bytes;
    	}
    }
    ```
    説明：
    パスワードのデクリプト（復号化）やエンクリプト（暗号化）の処理を行います。ログイン時においては、データベースの暗号化されたパスワードをデクリプト（復号化）して、ログイン画面のパスワードと一致するか否かを確認します。

# 参考

**Service**

https://www.purin-it.com/spring-boot-service

https://teratail.com/questions/207750

https://teratail.com/questions/199834