diff --git a/src/main/java/me/hatter/sample/common/WebConfig.java b/src/main/java/me/hatter/sample/common/WebConfig.java index d662b15..b8652b4 100644 --- a/src/main/java/me/hatter/sample/common/WebConfig.java +++ b/src/main/java/me/hatter/sample/common/WebConfig.java @@ -1,5 +1,7 @@ package me.hatter.sample.common; +import me.hatter.sample.common.interceptor.CustomHandlerInterceptorAdapter; +import me.hatter.sample.common.resolver.CustomSessionArgumentResolver; import me.hatter.sample.common.resolver.SystemTimeArgumentResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -14,8 +16,20 @@ public class WebConfig extends WebMvcConfigurerAdapter { @Autowired SystemTimeArgumentResolver systemTimeArgumentResolver; + @Autowired + CustomSessionArgumentResolver customSessionArgumentResolver; + + @Autowired + CustomHandlerInterceptorAdapter customHandlerInterceptorAdapter; + @Override public void addArgumentResolvers(List argumentResolvers) { argumentResolvers.add(systemTimeArgumentResolver); + argumentResolvers.add(customSessionArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(customHandlerInterceptorAdapter); } } diff --git a/src/main/java/me/hatter/sample/common/annotation/Permission.java b/src/main/java/me/hatter/sample/common/annotation/Permission.java new file mode 100644 index 0000000..0e02671 --- /dev/null +++ b/src/main/java/me/hatter/sample/common/annotation/Permission.java @@ -0,0 +1,9 @@ +package me.hatter.sample.common.annotation; + +import java.lang.annotation.*; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Permission { +} diff --git a/src/main/java/me/hatter/sample/common/handler/GlobalControllerExceptionHandler.java b/src/main/java/me/hatter/sample/common/handler/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000..4cf820c --- /dev/null +++ b/src/main/java/me/hatter/sample/common/handler/GlobalControllerExceptionHandler.java @@ -0,0 +1,32 @@ +package me.hatter.sample.common.handler; + +import com.alibaba.fastjson.JSON; +import me.hatter.sample.common.message.ErrorMessageException; +import me.hatter.tools.commons.exception.ExceptionUtil; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +@ControllerAdvice +public class GlobalControllerExceptionHandler { + + @ResponseBody +// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "internal error") + @ExceptionHandler(Throwable.class) + public void handleException(HttpServletResponse response, Throwable t) throws IOException { + if (t instanceof ErrorMessageException) { + response.setStatus(((ErrorMessageException) t).getStatusCode()); + response.getWriter().write(JSON.toJSONString(((ErrorMessageException) t).getErrorMessage(), true)); + } else { + final Map result = new LinkedHashMap<>(); + result.put("stacktrace", ExceptionUtil.printStackTrace(t)); + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.getWriter().write(JSON.toJSONString(result, true)); + } + } +} diff --git a/src/main/java/me/hatter/sample/common/interceptor/CustomHandlerInterceptorAdapter.java b/src/main/java/me/hatter/sample/common/interceptor/CustomHandlerInterceptorAdapter.java new file mode 100644 index 0000000..d5e422e --- /dev/null +++ b/src/main/java/me/hatter/sample/common/interceptor/CustomHandlerInterceptorAdapter.java @@ -0,0 +1,42 @@ +package me.hatter.sample.common.interceptor; + +import me.hatter.sample.common.annotation.Permission; +import me.hatter.sample.common.message.ErrorMessage; +import org.springframework.boot.autoconfigure.web.BasicErrorController; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class CustomHandlerInterceptorAdapter extends HandlerInterceptorAdapter { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + System.out.println("[CustomHandlerInterceptorAdapter] " + handler.getClass()); + if (!(handler instanceof HandlerMethod)) { + throw new IllegalArgumentException("Not supported handler: " + handler.getClass()); + } + HandlerMethod handlerMethod = (HandlerMethod) handler; + + // ignore error controller + if (handlerMethod.getMethod().getDeclaringClass().equals(BasicErrorController.class)) { + return true; + } + +// Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); +// +// RequestAttributes requestAttributes = new ServletRequestAttributes(request); +// DefaultErrorAttributes errorAttributes = new DefaultErrorAttributes(); +// Map attributes = errorAttributes.getErrorAttributes(requestAttributes, true); + + Permission p = handlerMethod.getMethodAnnotation(Permission.class); + if (p == null) { + throw new ErrorMessage("permission_denied", + "No permission: " + request.getRequestURI() + ).ex(HttpServletResponse.SC_FORBIDDEN); + } + return false; + } +} diff --git a/src/main/java/me/hatter/sample/common/message/ErrorMessage.java b/src/main/java/me/hatter/sample/common/message/ErrorMessage.java new file mode 100644 index 0000000..765b69c --- /dev/null +++ b/src/main/java/me/hatter/sample/common/message/ErrorMessage.java @@ -0,0 +1,34 @@ +package me.hatter.sample.common.message; + +public class ErrorMessage { + private String error; + private String error_description; + + public ErrorMessage() { + } + + public ErrorMessage(String error, String error_description) { + this.error = error; + this.error_description = error_description; + } + + public ErrorMessageException ex(int statusCode) { + return new ErrorMessageException(statusCode, this); + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getError_description() { + return error_description; + } + + public void setError_description(String error_description) { + this.error_description = error_description; + } +} diff --git a/src/main/java/me/hatter/sample/common/message/ErrorMessageException.java b/src/main/java/me/hatter/sample/common/message/ErrorMessageException.java new file mode 100644 index 0000000..3eb3cf4 --- /dev/null +++ b/src/main/java/me/hatter/sample/common/message/ErrorMessageException.java @@ -0,0 +1,24 @@ +package me.hatter.sample.common.message; + +public class ErrorMessageException extends RuntimeException { + private final int statusCode; + private final ErrorMessage errorMessage; + + public ErrorMessageException(int statusCode, ErrorMessage errorMessage) { + this.statusCode = statusCode; + this.errorMessage = errorMessage; + } + + public int getStatusCode() { + return statusCode; + } + + public ErrorMessage getErrorMessage() { + return errorMessage; + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/src/main/java/me/hatter/sample/common/resolver/CustomSessionArgumentResolver.java b/src/main/java/me/hatter/sample/common/resolver/CustomSessionArgumentResolver.java new file mode 100644 index 0000000..90b2275 --- /dev/null +++ b/src/main/java/me/hatter/sample/common/resolver/CustomSessionArgumentResolver.java @@ -0,0 +1,38 @@ +package me.hatter.sample.common.resolver; + +import me.hatter.sample.common.session.CustomSession; +import me.hatter.tools.commons.security.random.RandomTool; +import org.apache.catalina.servlet4preview.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.util.WebUtils; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +@Component +public class CustomSessionArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.getParameterType().equals(CustomSession.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + final HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest(); + final HttpServletResponse httpServletResponse = (HttpServletResponse) webRequest.getNativeResponse(); + Cookie cookie = WebUtils.getCookie(httpServletRequest, "hatter_session_id"); + if (cookie == null) { + cookie = new Cookie("hatter_session_id", RandomTool.secureRandom().nextBytes(64).asBase58()); + cookie.setPath("/"); + cookie.setHttpOnly(true); + httpServletResponse.addCookie(cookie); + } + final String prefix = cookie.getValue(); + return new CustomSession(prefix); + } +} diff --git a/src/main/java/me/hatter/sample/common/session/CustomSession.java b/src/main/java/me/hatter/sample/common/session/CustomSession.java new file mode 100644 index 0000000..f48a215 --- /dev/null +++ b/src/main/java/me/hatter/sample/common/session/CustomSession.java @@ -0,0 +1,25 @@ +package me.hatter.sample.common.session; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class CustomSession { + private static final ConcurrentMap cache = new ConcurrentHashMap<>(); + private final String prefix; + + public CustomSession(String prefix) { + this.prefix = prefix; + } + + public void put(String key, Object value) { + cache.put(prefix + ":" + key, value); + } + + public Object get(String key) { + return cache.get(prefix + ":" + key); + } + + public String getSessionId() { + return prefix; + } +} diff --git a/src/main/java/me/hatter/sample/web/controller/SampleController.java b/src/main/java/me/hatter/sample/web/controller/SampleController.java index 530b11d..d744999 100644 --- a/src/main/java/me/hatter/sample/web/controller/SampleController.java +++ b/src/main/java/me/hatter/sample/web/controller/SampleController.java @@ -1,6 +1,8 @@ package me.hatter.sample.web.controller; +import me.hatter.sample.common.annotation.Permission; import me.hatter.sample.common.annotation.SystemTime; +import me.hatter.sample.common.session.CustomSession; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -9,9 +11,34 @@ import java.util.Date; @RestController public class SampleController { + @Permission @GetMapping("/") public String home( @SystemTime Date time) { return "Hello, World: " + time; } + + @Permission + @GetMapping("/exception") + public String exception() { + throw new RuntimeException("error"); + } + + @GetMapping("/nopermission") + public String noPermission() { + return "Hello, World!"; + } + + @Permission + @GetMapping("/count") + public String count(CustomSession session) { + final Object c = session.get("count"); + int count = 0; + if (c != null) { + count = (Integer) c; + count++; + } + session.put("count", count); + return "Count @" + session.getSessionId() + " : " + count; + } }