目前项目一般都前后端分离,但是前端再访问后端服务器时,都会有接口校验,如果用户没有登录是无法访问该接口的,因此我们需要定义拦截器,而在拦截器中验证有没有访问该接口的权限;
1、我们需要配置拦截器
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Autowired
private RequestAuthInterceptor requestAuthInterceptor;
/**
* 这个方法是用来配置静态资源的,比如html,js,css,等等
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
/**
* 这个方法用来注册拦截器,
* 我们自己写好的拦截器需要通过这里添加注册才能生效
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login", "/register");
InterceptorRegistration registration = registry.addInterceptor(requestAuthInterceptor);
//将这个controller放行
registration.excludePathPatterns("/servicer");
//拦截全部
registration.addPathPatterns("/**");
}
@Bean
public FilterRegistrationBean filterRegist() {
FilterRegistrationBean frBean = new FilterRegistrationBean();
frBean.setFilter(new RequestAuthFilter());
frBean.addUrlPatterns("/*");
return frBean;
}
}
2、自定义拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class);
/**
* 这个方法是在访问接口之前执行的,
* 我们只需要在这里写验证登陆状态的业务逻辑,
* 就可以在用户调用指定接口之前验证登陆状态了
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LOG.info(" 请求地址 : " + request.getRequestURI());
//每一个项目对于登陆的实现逻辑都有所区别,我这里使用最简单的Session提取User来验证登陆。
HttpSession session = request.getSession();
//这里的User是登陆时放入session的
// String user = (String) session.getAttribute("user");
//如果session中没有user,表示没登陆
// if (user == null) {
//这个方法返回false表示忽略当前请求,如果一个用户调用了需要登陆才能使用的接口,如果他没有登陆这里会直接忽略掉
//当然你可以利用response给用户返回一些提示信息,告诉他没登陆
// return false;
// } else {
//如果session里有user,表示该用户已经登陆,放行,用户即可继续调用自己需要的接口
return true;
// }
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
}
@Component
public class RequestAuthInterceptor implements HandlerInterceptor {
private static Logger logger = LoggerFactory.getLogger(RequestAuthInterceptor.class);
@Resource
private ServicerMapper servicerMapper;
/**
* 统一使用开放平台的鉴权逻辑进行消息鉴权
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUrl = request.getRequestURI();
logger.info(" 请求地址 : " + requestUrl);
response.setCharacterEncoding("utf-8");
if(requestUrl.contains("error")){
responseMessage(response, response.getWriter(), Result.error(CommonError.E_FF000000));
return false;
}
// 1.首先检查消息头中参数是否完整 以及计算签名摘要
String reqBody = new BodyReaderHttpServletRequestWrapper(request).getBodyString();
//判断 openAccountId 是否存在
// if (!partialCheck(request, response, reqBody)) {
// Result commonResult = Result.error(CommonError.E_FF000001);
// try {
// responseMessage(response, response.getWriter(), commonResult);
// } catch (IOException e) {
// e.printStackTrace();
// }
// return false;
// }
return true;
}
private boolean partialCheck(HttpServletRequest request, HttpServletResponse response, String bodyStr) {
JSONObject paramsObject = JSONObject.parseObject(getParamRequestStr(request, bodyStr));
logger.info("请求参数 :" + paramsObject);
String openAccountId = paramsObject.getString("openAccountId");
if (StringUtils.isBlank(openAccountId)) {
return false;
}
//该url放行
if (request.getRequestURI().contains("servicer/register")) {
return true;
}
ServicerModel model = servicerMapper.getServicerByOpenAccountId(openAccountId);
if (null == model) {
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
//请求不通过,返回错误信息给客户端
private static void responseMessage(HttpServletResponse response, PrintWriter out,
Result commonResult) {
response.setContentType("application/json; charset=utf-8");
String json = JSON.toJSONString(commonResult);
out.print(json);
out.flush();
out.close();
}
private static String getParamRequestStr(HttpServletRequest request, String bodyStr) {
try {
String result = bodyStr;
//获取application/json类型的头请求内容的参数
if (StringUtils.isBlank(bodyStr)) {
Map<String, String> parmMap = new HashMap<String, String>();
//方式二:
Enumeration<String> a = request.getParameterNames();
String parm = null;
String val = "";
while (a.hasMoreElements()) {
//参数名
parm = a.nextElement();
//值
val = request.getParameter(parm);
parmMap.put(parm, val);
}
return JSON.toJSONString(parmMap);
}
return result;
} catch (Exception e) {
logger.error("获取request参数错误", e);
}
return null;
}
3、自定义HttpServletRequestWrapper
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String sessionStream = getBodyString(request);
body = sessionStream.getBytes(Charset.forName("UTF-8"));
}
public String getBodyString() {
return new String(body, Charset.forName("UTF-8"));
}
/**
* 获取请求Body
*
* @param request
* @return
*/
public String getBodyString(final ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = cloneInputStream(request.getInputStream());
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
/**
* Description: 复制输入流</br>
*
* @param inputStream
* @return</br>
*/
public InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return byteArrayInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
原因:
这个问题其实就是一个流读取的问题,众所周知在Java中input流只能读取一次,主要原因是通标记的方法来判断流是否读取完毕(读取位 -1就是流读取完毕)解决这个问题我能想到两种方式
1、通过修改标记的方式 ( inputstream.markSupported() 方法可以判断这个流是否支持 mark 和 reset 方法。他们分别是标记 和 重新定位流。
再回到问题上来我们可以先使用第一种方法判断 requet 中的inputStream 是否支持标记和重新定位。因为这种方式实现起来比较简单。无需考虑太多。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
boolean b = request.getInputStream().markSupported();
System.out.println(b);
}
// 输出结果为 false
上述代码会返回一个 false 那么很明显,request 中的 input 流是不支持标记和重新定位的。
2、将流赋值给一个 byte[] 数组,或者其他变量保存起来。下载读取流时就调用这个数组就行。
我们再考虑第二种方法,我们需要一个变量保存这个流。并且还要保证再拦截器和controller中都要拿到这个变量。
通过上方这个图我们可以知道,在 Filter 和 Inteceptor 中间有一层Servlet。而Servlet就是提交request的地方。所以我们要重写HttpServletRequest方法只能在Servlet之前。也就是filter 中。下面就是直接上代码了
@WebFilter(filterName = "requestAuthFilter", urlPatterns = "/*")
public class RequestAuthFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("--------------过滤器初始化------------");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("--------------执行过滤操作------------");
// 防止流读取一次后就没有了, 所以需要将流继续写出去
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpServletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
@Override
public void destroy() {
System.out.println("--------------过滤器销毁------------");
}
}