Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ public class DefaultEntityListener<ENTITY> implements EntityListener<ENTITY> {

@Override
public void preInsert(ENTITY entity, PreInsertContext<ENTITY> context) {
// 二重送信防止チェック
val expected = DoubleSubmitCheckTokenHolder.getExpectedToken();
val actual = DoubleSubmitCheckTokenHolder.getActualToken();

if (expected != null && actual != null && !Objects.equals(expected, actual)) {
throw new DoubleSubmitErrorException();
if( ! DoubleSubmitCheckTokenHolder.isExcludeCheck()) {
if( ! DoubleSubmitCheckTokenHolder.isExistsExpectedTokenKey()){
throw new IllegalStateException("指定されたキーが見つかりませんでした。@TokenKeyの設定を確認してください");
}

// 二重送信防止チェック
val expected = DoubleSubmitCheckTokenHolder.getExpectedToken();
val actual = DoubleSubmitCheckTokenHolder.getActualToken();
if (expected != null && actual != null && !Objects.equals(expected, actual)) {
throw new DoubleSubmitErrorException();
}
}

if (entity instanceof DomaDto) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,47 @@
package com.sample.domain.dao;

import lombok.val;

import java.util.Objects;

/**
* 二重送信防止チェックトークンホルダー
*/
public class DoubleSubmitCheckTokenHolder {

private static final ThreadLocal<String> EXPECTED_TOKEN = new ThreadLocal<>();

private static final ThreadLocal<String> ACTUAL_TOKEN = new ThreadLocal<>();
private static final ThreadLocal<DoubleSubmitCheckTokenHolder> HOLDER = new ThreadLocal<>();
private String key;
private String expected;
private String actual;
private boolean existsExpectedTokenKey;
private boolean excludeCheck;

/**
* トークンを保存します。
*
* @param key
* @param expected
* @param actual
* @param existsExpectedTokenKey
* @aparam excludeCheck
*/
public static void set(String key, String expected, String actual, boolean existsExpectedTokenKey, boolean excludeCheck) {
val me = new DoubleSubmitCheckTokenHolder();
me.key = key;
me.expected = expected;
me.actual = actual;
me.existsExpectedTokenKey = existsExpectedTokenKey;
me.excludeCheck = excludeCheck;
HOLDER.set(me);
}

/**
* トークンのキーを返します。
*
* @return
*/
public static void set(String expected, String actual) {
EXPECTED_TOKEN.set(expected);
ACTUAL_TOKEN.set(actual);
public static String getTokenKey() {
return me().key;
}

/**
Expand All @@ -26,7 +50,7 @@ public static void set(String expected, String actual) {
* @return
*/
public static String getExpectedToken() {
return EXPECTED_TOKEN.get();
return me().expected;
}

/**
Expand All @@ -35,14 +59,38 @@ public static String getExpectedToken() {
* @return
*/
public static String getActualToken() {
return ACTUAL_TOKEN.get();
return me().actual;
}

/**
* セッション中にトークンキーがあるかを返します
*
* @return
*/
public static boolean isExistsExpectedTokenKey(){
return me().existsExpectedTokenKey;
}

/**
* トークンチェックの対象から除外するかを返します。
*
* @return
*/
public static boolean isExcludeCheck() {
return me().excludeCheck;
}

/**
* 監査情報をクリアします。
*/
public static void clear() {
EXPECTED_TOKEN.remove();
ACTUAL_TOKEN.remove();
HOLDER.remove();
}

private static DoubleSubmitCheckTokenHolder me(){
if( Objects.isNull(HOLDER.get()) ){
return new DoubleSubmitCheckTokenHolder();
}
return HOLDER.get();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.sample.web.base.aop;

import java.util.Objects;
import java.util.Optional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.sample.web.base.security.annotation.TokenKey;
import com.sample.web.base.security.annotation.ExcludeCheckToken;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;

import com.sample.domain.dao.DoubleSubmitCheckTokenHolder;
Expand All @@ -24,9 +28,12 @@ public class SetDoubleSubmitCheckTokenInterceptor extends BaseHandlerInterceptor
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// コントローラーの動作前
val expected = DoubleSubmitCheckToken.getExpectedToken(request);
val key = takeOutTokenKey(handler).orElse(null);
val expected = DoubleSubmitCheckToken.getExpectedToken(request, key);
val actual = DoubleSubmitCheckToken.getActualToken(request);
DoubleSubmitCheckTokenHolder.set(expected, actual);
val existsExpectedTokenKey = DoubleSubmitCheckToken.isExistsExpectedTokenKey(request, key);
val excludeCheck = excludeCheckToken(handler);
DoubleSubmitCheckTokenHolder.set(key, expected, actual, existsExpectedTokenKey, excludeCheck);
return true;
}

Expand All @@ -35,20 +42,56 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response,
ModelAndView modelAndView) throws Exception {
// コントローラーの動作後
if (StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) {
if(DoubleSubmitCheckTokenHolder.isExcludeCheck()){
return;
}

if( ! DoubleSubmitCheckTokenHolder.isExistsExpectedTokenKey()){
throw new IllegalStateException("指定されたキーが見つかりませんでした。@TokenKeyの設定を確認してください");
}

// POSTされたときにトークンが一致していれば新たなトークンを発行する
val expected = DoubleSubmitCheckToken.getExpectedToken(request);
val key = DoubleSubmitCheckTokenHolder.getTokenKey();
val expected = DoubleSubmitCheckToken.getExpectedToken(request, key);
val actual = DoubleSubmitCheckToken.getActualToken(request);

if (expected != null && actual != null && Objects.equals(expected, actual)) {
DoubleSubmitCheckToken.renewToken(request);
DoubleSubmitCheckToken.renewToken(request, key);
}
}
}

boolean excludeCheckToken(Object handler) {
if (!(handler instanceof HandlerMethod)) {
return false;
}
val hm = (HandlerMethod)handler;
if (hm.getBeanType().isAnnotationPresent(ExcludeCheckToken.class)
|| hm.getMethod().isAnnotationPresent(ExcludeCheckToken.class)) {
return true;
}
return false;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 処理完了後
DoubleSubmitCheckTokenHolder.clear();
}

Optional<String> takeOutTokenKey(Object handler) {
if (!(handler instanceof HandlerMethod)) {
return Optional.empty();
}
val hm = (HandlerMethod)handler;
if(hm.getMethod().isAnnotationPresent(TokenKey.class)){
return Optional.of(hm.getMethod().getAnnotation(TokenKey.class).value());
}
if(hm.getBeanType().isAnnotationPresent(TokenKey.class)){
return Optional.of(hm.getBeanType().getAnnotation(TokenKey.class).value());
}
return Optional.empty();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import lombok.val;

import java.util.Objects;

public class DoubleSubmitCheckToken {

public static final String DOUBLE_SUBMIT_CHECK_PARAMETER = "_double";
Expand Down Expand Up @@ -58,8 +60,11 @@ public static String getExpectedToken(HttpServletRequest request, String key) {
*
* @param request
* @return expected token
* @Deprecated @link{com.sample.web.base.security.DoubleSubmitCheckToken#getExpectedToken(HttpServletRequest, String)}を使ってください
*
*/
@SuppressWarnings("unchecked")
@Deprecated
public static String getExpectedToken(HttpServletRequest request) {
return getExpectedToken(request, null);
}
Expand Down Expand Up @@ -124,6 +129,22 @@ protected static LRUMap getLRUMap(HttpServletRequest request) {
return map;
}

public static boolean isExistsExpectedTokenKey(HttpServletRequest request, String key){
//キーが指定されている場合は、キーがSession中のMapにあることを保証する
if(Objects.isNull(key) || ! hasTokenInSession(request)) {
return true;
}
LRUMap map = getLRUMap(request);
if( map.containsKey(key) ){
return true;
}
return false;
}

protected static boolean hasTokenInSession(HttpServletRequest request) {
return Objects.nonNull(SessionUtils.getAttribute(request, DOUBLE_SUBMIT_CHECK_CONTEXT));
}

/**
* トークンを取得する。
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.sample.web.base.security;

import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

import com.sample.domain.dao.DoubleSubmitCheckTokenHolder;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.servlet.support.RequestDataValueProcessor;

Expand Down Expand Up @@ -34,11 +36,14 @@ public Map<String, String> getExtraHiddenFields(HttpServletRequest request) {
val map = PROCESSOR.getExtraHiddenFields(request);

if (!map.isEmpty()) {
val action = ACTION_HOLDER.get();
String token = DoubleSubmitCheckToken.getExpectedToken(request, action);
String key = DoubleSubmitCheckTokenHolder.getTokenKey();
if(Objects.isNull(key)) {
key = ACTION_HOLDER.get();
}
String token = DoubleSubmitCheckToken.getExpectedToken(request, key);

if (token == null) {
token = DoubleSubmitCheckToken.renewToken(request, action);
if (Objects.isNull(token)) {
token = DoubleSubmitCheckToken.renewToken(request, key);
}

map.put(DoubleSubmitCheckToken.DOUBLE_SUBMIT_CHECK_PARAMETER, token);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.sample.web.base.security.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* このアノテーションは二重送信時のトークンチェックを行わないことをあらわすアノテーションです
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ExcludeCheckToken {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.sample.web.base.security.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* このアノテーションは二重送信時のトークンのキーをあらわすアノテーションです
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface TokenKey {
String value();
}