spring boot實(shí)現(xiàn)在request里解密參數(shù)返回
spring boot在request里解密參數(shù)返回
前言
有個(gè)業(yè)務(wù)需求,一個(gè)請(qǐng)求來源web,一個(gè)請(qǐng)求來源APP,web需求驗(yàn)證簽名,APP的參數(shù)是經(jīng)過加密,所以出現(xiàn)了兩個(gè)Controller,除了解密獲取參數(shù)方式不一樣,其他內(nèi)容一模一樣,這樣不太合理,所以我決定重構(gòu)。
思路:既然只是解密不一樣,獲取到的參數(shù)是一樣的,那可以寫一個(gè)過濾器,在里面就把參數(shù)解密好,然后返回
Spring Boot在請(qǐng)求的時(shí)候是不允許直接修改HttpServletRequest里的paramsMap參數(shù)的,但是提供了一個(gè)HttpServletRequestWrapper類,繼承這個(gè)類重寫兩個(gè)方法就可以了。
代碼塊
重寫HttpServletRequestWrapper
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
public class ParameterRequest extends HttpServletRequestWrapper {
private Map<String, String[]> params = new HashMap<>(16);
public ParameterRequest(HttpServletRequest request) throws IOException {
super(request);
this.params.putAll(request.getParameterMap());
}
/**
* 重載一個(gè)構(gòu)造方法
*
* @param request
* @param extendParams
*/
public ParameterRequest(HttpServletRequest request, Map<String, String[]> extendParams) throws IOException {
this(request);
addAllParameters(extendParams);
}
@Override
public String getParameter(String name) {
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}
@Override
public String[] getParameterValues(String name) {
return params.get(name);
}
public void addAllParameters(Map<String, String[]> otherParams) {
for (Map.Entry<String, String[]> entry : otherParams.entrySet()) {
addParameter(entry.getKey(), entry.getValue());
}
}
public void addParameter(String name, Object value) {
if (value != null) {
if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}
}
思路是重寫自定義一個(gè)Map存入?yún)?shù),將解密后需要的參數(shù)放入,然后在過濾器中執(zhí)行這個(gè)新的request
過濾器
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class WebParamFilter implements Filter {
private static final String OPTIONS = "OPTIONS";
@Value("${jwt.info.urlPatterns}")
private List<String> urlPatterns;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, filterConfig.getServletContext());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
log.info("開始過濾器===============");
if (!isFilter(request)) {
writerError(response, RetEnum.RET_TOKEN_ERROR);
return;
}
// 從請(qǐng)求頭從獲取請(qǐng)求類型,1是WEB,2是APP
String requestType = request.getHeader("requestType");
if (StringUtils.isEmpty(requestType)) {
writerError(response, RetEnum.RET_NOT_HEADER_ERROR);
return;
}
Map<String, String[]> paramsMap = new HashMap<>();
if ("1".equals(requestType)) {
// 驗(yàn)證簽名,簽名錯(cuò)誤直接返回
if (!compareSign(request)) {
writerError(response, "簽名錯(cuò)誤", 500);
return;
}
// 將請(qǐng)求的參數(shù)從request中取出,轉(zhuǎn)換成JSON,放入自定義的Map中帶給控制器
paramsMap.put("params", new String[]{JSONUtil.getJSONParam(request).toJSONString()});
ParameterRequest req = new ParameterRequest(request, paramsMap);
filterChain.doFilter(req, response);
} else if ("2".equals(requestType)) {
// APP請(qǐng)求方式比較特殊,所以要從requestBody里讀出JSON加密數(shù)據(jù)
String bodyStr = RequestBodyUtil.read(request.getReader());
// 然后再解密,拿到真正的參數(shù)轉(zhuǎn)換成JSON,放入自定義的Map中帶給控制器
JSONObject jsonParam = getJsonParam(bodyStr);
paramsMap.put("params", new String[]{jsonParam.toJSONString()});
ParameterRequest req = new ParameterRequest(request, paramsMap);
filterChain.doFilter(req, response);
} else {
writerError(response, "無效的請(qǐng)求來源", 500);
}
}
@Override
public void destroy() {
}
/**
* 篩選
*
* @param request
* @return
*/
private boolean isFilter(HttpServletRequest request) {
if (OPTIONS.equals(request.getMethod())) {
return true;
}
if (isInclude(request)) {
//如果是屬于排除的URL,比如登錄,注冊(cè),驗(yàn)證碼等URL,則直接通行
log.info("直接通過");
return true;
}
return tokenCheck(request);
}
/**
* 排除不需要過濾的URL
*
* @param request
* @return
*/
private boolean isInclude(HttpServletRequest request) {
String url = request.getRequestURI().substring(request.getContextPath().length());
log.info("請(qǐng)求url:{}", url);
for (String patternUrl : urlPatterns) {
Pattern p = Pattern.compile(patternUrl);
Matcher m = p.matcher(url);
if (m.find()) {
return true;
}
}
return false;
}
/**
* 效驗(yàn)token是否有效
*
* @param request
* @return
*/
private boolean tokenCheck(HttpServletRequest request) {
String authToken = request.getHeader("accessToken");
log.info("請(qǐng)求頭中令牌token:{}", authToken);
// ...業(yè)務(wù)代碼
return false;
}
/**
* 錯(cuò)誤寫出
*
* @param response
* @param retEnum
* @throws IOException
*/
private void writerError(HttpServletResponse response, String msg, int code) throws IOException {
//驗(yàn)證不通過
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
//將驗(yàn)證不通過的錯(cuò)誤返回
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> resultMap = new HashMap<>(3);
resultMap.put("code", code);
resultMap.put("msg", msg);
resultMap.put("data", null);
response.getWriter().write(mapper.writeValueAsString(resultMap));
}
/**
* web效驗(yàn)簽名
*
* @param request
* @return
*/
public boolean compareSign(HttpServletRequest request) {
JSONObject param = JSONUtil.getJSONParam(request);
String sign = JSONUtil.getParamRequired(param, String.class, "sign");
// ...業(yè)務(wù)代碼
return s.equals(sign);
}
/**
* APP解密參數(shù)
*
* @param json
* @return
*/
public JSONObject getJsonParam(String json) {
JSONObject jsonParam = JSON.parseObject(json);
String aos = jsonParam.getString("aos");
String params = jsonParam.getString("params");
String param = null;
if (jsonParam != null && !StringUtils.isEmpty(aos) && !StringUtils.isEmpty(params)) {
String key = RSA.rsaDecrypt(aos, "自定義的私鑰");
if (StringUtils.isBlank(key)) {
return null;
}
try {
param = AES256.decrypt(params, key);
} catch (Exception e) {
e.printStackTrace();
}
if (StringUtils.isBlank(param)) {
return null;
}
}
if (StringUtils.isBlank(param)) {
return null;
}
return JSONObject.parseObject(param);
}
}
思路都在代碼中備注了,就是在過濾器中,一層層解析,比如token等,然后再分別解析兩種請(qǐng)求的參數(shù),放入params里,其中用到的兩個(gè)工具類如下
JSONUtil
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.MissingServletRequestParameterException;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
public class JSONUtil {
public static JSONObject getJSONParam(HttpServletRequest request){
Map<String, String[]> parameterMap = request.getParameterMap();
JSONObject returnObject = new JSONObject();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String value = "";
String[] values = entry.getValue();
if (values != null){
for (String s : values) {
value = s + ",";
}
value = value.substring(0, value.length() - 1);
}
returnObject.put(entry.getKey(), value);
}
return returnObject;
}
public static<T> T getParam(JSONObject param, Class<T> tClass, String key){
if (param == null) {
return null;
}
return param.getObject(key, tClass);
}
public static<T> T getParamRequired(JSONObject param, Class<T> tClass, String key){
if (param == null) {
throw new RuntimeException(getErrMsg(key));
}
T object = param.getObject(key, tClass);
if (object == null){
throw new RuntimeException(getErrMsg(key));
}
return object;
}
private static String getErrMsg(String key) {
return "參數(shù)" + key + "必填";
}
}
RequestBodyUtil
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
/**
* 解析Body數(shù)據(jù)
*/
public class RequestBodyUtil {
private static final int BUFFER_SIZE = 1024 * 8;
private RequestBodyUtil(){}
public static String read(Reader reader) throws IOException {
StringWriter writer = new StringWriter();
try {
write(reader, writer);
return writer.getBuffer().toString();
} finally {
writer.close();
}
}
public static long write(Reader reader, Writer writer) throws IOException {
return write(reader, writer, BUFFER_SIZE);
}
public static long write(Reader reader, Writer writer, int bufferSize) throws IOException {
int read;
long total = 0;
char[] buf = new char[BUFFER_SIZE];
while ((read = reader.read(buf)) != -1) {
writer.write(buf, 0, read);
total += read;
}
return total;
}
}
最后
注冊(cè)過濾器我就不說了,SpringBoot注冊(cè)過濾器方式很多,看如何在控制器中接收參數(shù)
@PostMapping("/test")
public Result test(@RequestParam String params){
System.out.println("解密后的參數(shù):" + params);
return ResponseMsgUtil.success(params);
}
名字只要和過濾器中自定義的Map里的Key對(duì)應(yīng),就會(huì)被拿到參數(shù)
Spring boot配置Aop獲取controller里的request中的參數(shù)及其返回值
示例:
當(dāng)前url:http://localhost:8080/CarsiLogCenter_new/idpstat.jsp?action=idp.sptopn
request.getRequestURL() http://localhost:8080/CarsiLogCenter_new/idpstat.jsp request.getRequestURI() /CarsiLogCenter_new/idpstat.jsp request.getContextPath()/CarsiLogCenter_new request.getServletPath() /idpstat.jsp request.getQueryString() action=idp.sptopn
public static String getLastAccessUrl(HttpServletRequest request) {
StringBuffer requestURL = request.getRequestURI();
String queryString = request.getQueryString();
if (queryString == null) {
return requestURL.toString();
}
return requestURL + "?" + queryString;
}
1、request.getRequestURL()
- 返回的是完整的url,包括Http協(xié)議,端口號(hào),servlet名字和映射路徑,但它不包含請(qǐng)求參數(shù)。
2、request.getRequestURI()
- 得到的是request URL的部分值,并且web容器沒有decode過的
3、request.getContextPath()
- 返回 the context of the request.
4、request.getServletPath()
- 返回調(diào)用servlet的部分url.
5、request.getQueryString()
- 返回url路徑后面的查詢字符串
首先在你的Maven的pom文件里加入aop的依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在spring boot里面一切配置都是很簡(jiǎn)單的,
下面為我所有被請(qǐng)求到的controller加上Aop的功能
看碼吧:
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;;
@Aspect //定義一個(gè)切面
@Configuration
public class LogRecordAspect {
private static final Logger logger = LoggerFactory.getLogger(UserInterceptor.class);
// 定義切點(diǎn)Pointcut
@Pointcut("execution(* com.jiaobuchong.web.*Controller.*(..))")
public void excudeService() {
}
@Around("excudeService()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String url = request.getRequestURL().toString();
String method = request.getMethod();
String uri = request.getRequestURI();
String queryString = request.getQueryString();
logger.info("請(qǐng)求開始, 各個(gè)參數(shù), url: {}, method: {}, uri: {}, params: {}", url, method, uri, queryString);
// result的值就是被攔截方法的返回值
Object result = pjp.proceed();
Gson gson = new Gson();
logger.info("請(qǐng)求結(jié)束,controller的返回值是 " + gson.toJson(result));
return result;
}
}
只要加上上面這個(gè)類,Aop就算配置好了,不信,去訪問以下你的Controller試試。對(duì)比以前配置aop的方式(xml文件),現(xiàn)在的配置都到Java代碼里來了,@Configuration這個(gè)Annotation就是JavaConfig的典型代表,Spring boot在啟動(dòng)時(shí)會(huì)會(huì)自動(dòng)去加載這些配置,實(shí)現(xiàn)相應(yīng)的配置功能。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring中@EnableScheduling實(shí)現(xiàn)定時(shí)任務(wù)代碼實(shí)例
這篇文章主要介紹了Spring中@EnableScheduling實(shí)現(xiàn)定時(shí)任務(wù)代碼實(shí)例,@EnableScheduling 注解開啟定時(shí)任務(wù)功能,可以將多個(gè)方法寫在一個(gè)類,也可以分多個(gè)類寫,當(dāng)然也可以將方法直接寫在上面ScheddulConfig類中,需要的朋友可以參考下2024-01-01
springboot實(shí)現(xiàn)異步調(diào)用@Async的示例
這篇文章主要介紹了springboot實(shí)現(xiàn)異步調(diào)用@Async的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Java數(shù)組使用binarySearch()方法查找指定元素的實(shí)現(xiàn)
這篇文章主要介紹了Java數(shù)組使用binarySearch()方法查找指定元素的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Spring Boot 如何自定義返回錯(cuò)誤碼錯(cuò)誤信息
這篇文章主要介紹了Spring Boot 如何自定義返回錯(cuò)誤碼錯(cuò)誤信息的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
SpringMVC?RESTFul實(shí)戰(zhàn)案例修改功能實(shí)現(xiàn)
這篇文章主要為大家介紹了SpringMVC?RESTFul實(shí)戰(zhàn)案例修改功能實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05

