本篇的實現方式是基於上一篇:https://my.oschina.net/u/1428688/blog/2252569java
註解定義:web
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Documented @Inherited @ParamMapResolver // 最高優先級 @Order(Ordered.HIGHEST_PRECEDENCE) public @interface SafeRequest { boolean disable() default false; Class<? extends BaseDecrypt> decriptClass() default BaseDecrypt.class; }
數據解密過濾器實現app
@Component @Slf4j public class SafeRequestInterceptor extends BaseAnnotationInterceptor<SafeRequest> { private static final String APPLICATION_FORM_URLENCODED_UTF8_VALUE = "application/x-www-form-urlencoded; charset=UTF-8"; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, SafeRequest annotation) { String contentType = request.getContentType(); if (!APPLICATION_FORM_URLENCODED_UTF8_VALUE.equalsIgnoreCase(contentType)) { log.error("錯誤的contentType:{}", contentType); ResponseUtils.writeJson(response, new Resp<>("請求contentType必須是" + APPLICATION_FORM_URLENCODED_UTF8_VALUE)); return false; } if (!RequestHold.isAjax(request)) { ResponseUtils.writeJson(response, new Resp<>("缺乏請求頭x-requested-with:XMLHttpRequest")); return false; } if (!handlerMethod.getReturnType().getMethod().getReturnType().isAssignableFrom(Resp.class)) { log.error("{}請求返回值不規範,必須使用Resp作爲響應值", request.getRequestURI()); ResponseUtils.writeJson(response, new Resp<>("控制器層響應值必須Resp")); return false; } String key = (String) WebUtils.getSessionAttribute(request, Const.SESSION_TOKEN); if (key == null || key.length() < 16) { return false; } key = key.substring(0, 16); BaseDecrypt baseDecrypt = null; try { baseDecrypt = annotation.decriptClass().newInstance(); } catch (InstantiationException | IllegalAccessException e) { log.error("baseDecrypt沒法實例化", annotation.decriptClass()); return false; } // 進行參數綁定 // 將key-value封裝爲map,傳給bind方法進行參數值綁定 Map<String, String> paramMap = new HashMap<>(); Map<String, String[]> params = request.getParameterMap(); for (Map.Entry<String, String[]> entry : params.entrySet()) { String value = null; for (String val : entry.getValue()) { if (StringUtils.isNotBlank(val)) { val = baseDecrypt.decrypt(key, val); if (val == null) { log.error("屬性{}解密失敗", entry.getKey()); ResponseUtils.writeJson(response, new Resp<>("屬性" + entry.getKey() + "解密失敗")); return false; } } if (value == null) { value = val; } else { value = "," + val; } } paramMap.put(entry.getKey(), value); } request.setAttribute(Const.REQUEST_RESOLVER_PARAM_MAP_NAME, paramMap); return true; } }
自定義的參數解析器實現邏輯:ide
@SuppressWarnings("unchecked") @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { if (binderFactory == null) { return null; } // 解析器中的自定義邏輯 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); Map<String, String> paramMap = (Map<String, String>) request .getAttribute(Const.REQUEST_RESOLVER_PARAM_MAP_NAME); Object arg = paramMap.get(parameter.getParameterName()); if (arg != null) { // 生成參數綁定器,第一個參數爲request請求對象,第二個參數爲須要綁定的目標對象,第三個參數爲須要綁定的目標對象名 WebDataBinder binder = binderFactory.createBinder(webRequest, null, parameter.getParameterName()); arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter); } else { String name = parameter.getParameterName(); // 查找是否已有名爲name的參數值的映射,若是沒有則建立一個 arg = mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) : BeanUtils.instantiateClass(parameter.getParameterType()); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (binder.getTarget() != null) { PropertyValues propertyValues = new MutablePropertyValues(paramMap); // 將K-V綁定到binder.target屬性上 binder.bind(propertyValues); } // 將參數轉到預期類型,第一個參數爲解析後的值,第二個參數爲綁定Controller參數的類型,第三個參數爲綁定的Controller參數 arg = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } return arg; }
控制器層使用方式,示例:加密
@PostMapping(value = "updateMyPassword") @SafeRequest @Token public Resp<?> updateMyPassword(HttpServletRequest request, String username, String oldPassword, String newPassword) { SysUser current = UserHolder.getCurrentSysUser(request); current = getService().load(current.getId()); if (!current.getUsername().equals(username)) { return new Resp<>("用戶名錯誤"); } if (!current.getPassword().equals(PasswordUtil.encode(oldPassword))) { return new Resp<>("密碼錯誤"); } PasswordUtil.validPassword(newPassword); current.setPassword(PasswordUtil.encode(newPassword)); current.setPasswordUpdateTime(new Date()); getService().save(current); WebUtils.setSessionAttribute(request, Const.SESSION_SYS_USER, current); return new Resp<>(); }
過程解析:url
頁面對每個參數的值進行加密,參數名不用加密,發送加密過的數據到後臺,後臺統一對帶有SafeRequest註解的請求進行攔截,取出加密過的數據進行解密,並經過自定義參數解析綁定到控制層來.net