2重登録制御

#### トーク制御
トークン制御は登録、更新、検索処理をボタンなどに対して、ボタンの二重クリックを防止する。

### 代表的な制御方法
#### 1.javaScript
更新処理を行うボタンを押下した際に、JavaScriptによるボタン制御を行うことで、2度押しされた際にリクエストが送信されないようにします。

- ボタンやリンクを非活性化することで、ボタンやリンクを押下できないように制御します。
- 処理状態をフラグとして保持しｍ処理中にボタンやリンクが押された場合に処理中であることを通知するメッセージを表示します。
などがあげられます。

#### 2.	PRG(Post-Redirect-Get)パターン
更新処理を行うリクエスト(POSTメソッドによるリクエスト)に対する応答としてリダイレクトを返却し、その後ブラウザから自動的にリクエストされるGETメソッドの応答として遷移先の画面を返却するようにします。
PRGパターンを適用することで、画面表示後にページの再読み込みを行った場合に発生するリクエストがGETメソッドになるため、更新処理の再実行を防ぐことができます。

	
#### 3.トランザクショントークンチェック
画面遷移毎にトークン値を払い出し、ブラウザから送信されたトークン値とサーバ上で保持しているトークン値を比較することで、トランザクション内で不正な画面操作が行われないようにします。
トランザクショントークンチェックを適用することで、ブラウザの戻るボタンを使ってページを移動した後の更新処理の再実行を防ぐことが出来る。
また、トークン値のチェックを行った後にサーバで管理しているトークン値を破棄することで、サーバ側の処理として二重送信を防ぐこともできます、

- サーバは、クライアントからリクエストが来た際に、サーバ上にトランザクションを一意に識別するための値（トークン）を保持します。
- サーバは、クライアントへトークンを送信します。画面を提供するWebアプリケーションの場合は、formのhiddenタグを使用してクライアントにトークンを保持させます。
- クライアントは次のリクエストを送信する際に、サーバから渡されたトークンを送信します。サーバは、クライアントから受け取ったトークンと、サーバ上で管理しているトークンを比較します。

#### トークの生成例
#### 1.無意味な文字列
```java
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.xml.bind.DatatypeConverter;

public class RandomToken {
    
    private static int TOKEN_LENGTH = 16;  //16*2=32バイト
    
    public static void main(String[] args) {
        byte token[] = new byte[TOKEN_LENGTH];
        StringBuffer buf = new StringBuffer();
        SecureRandom random = null;
        String tokenString = null;
        
        try {
            random = SecureRandom.getInstance("SHA1PRNG");
            random.nextBytes(token);
            
            for(int i = 0; i < token.length; i++) {
                buf.append(String.format("%02x", token[i]));
            }
            tokenString = buf.toString();
            
            System.out.println("String.format： " + tokenString);
            System.out.println("DatatypeConverter： " + DatatypeConverter.printHexBinary(token));
            
        } catch(NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
}
```

#### 2.ユーザーIDや有効期限などの情報をエンコード
使用ライブラリ : JJWT

```xml
<!-- pom.xml -->
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.7.0</version>
</dependency>
```


```java
import java.security.Key;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Base64;
import java.util.Date;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.impl.crypto.MacProvider;

public class JsonWebToken {

    public static void main(String[] args) {
        
        Key key = MacProvider.generateKey();
        
        LocalDateTime now = LocalDateTime.now();
        System.out.println("Current Timestamp: " + now);
        
        //7日後の日時
        ZonedDateTime expirationDate = now.plusDays(7).atZone(ZoneId.systemDefault());
        
        //JWTを生成
        System.out.println("*** JWT Create ***");
        String compactJws = Jwts.builder()
                .setSubject("hoge")
                .setExpiration(Date.from(expirationDate.toInstant()))
                .signWith(SignatureAlgorithm.HS512, key)
                .compact();
        System.out.println("JWT: " + compactJws);
        
        //Base64でデコード
        System.out.println("*** Base64 decode ***");
        String[] jwtSections = compactJws.split("\\.");
        String header = new String(Base64.getDecoder().decode(jwtSections[0]));
        String claim = new String(Base64.getDecoder().decode(jwtSections[1]));
        System.out.println("JWT Header: " + header);
        System.out.println("JWT Claim: " + claim);
        
        //クレームの確認
        System.out.println("*** Claim ***");
        Date exp = Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getExpiration();
        String sub = Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody().getSubject();
        System.out.println("exp(Expiration Time): " + LocalDateTime.ofInstant(exp.toInstant(), ZoneId.systemDefault()));
        System.out.println("sub(Subject): " + sub);
        
        //署名の検証
        System.out.println("*** Signature Validation ***");
        try {
            Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws);
            System.out.println("Use Correct Key: Validation Success");
        } catch(SignatureException e) {
            System.out.println("Use Correct Key: Validation Fail - " + e.getMessage());
        }
        
        Key wrongKey = MacProvider.generateKey();
        try {
            //生成時とは別のキーを使用
            Jwts.parser().setSigningKey(wrongKey).parseClaimsJws(compactJws);
            System.out.println("Use Wrong Key: Validation Success");
        } catch(SignatureException e) {
            System.out.println("Use Wrong Key: Validation Fail - " + e.getMessage());
        }
    }
}
```