我需要持久保存有关发送给应用程序的请求/响应的所有信息,例如http状态,当前时间,令牌,请求URI等。这是一个API,资源是:
POST localhost:8080 / v1 / auth / login ,其中包含电子邮件和密码以请求身份验证。响应是一个JWT令牌。
获取 localhost:8080 / v1 / auth / rules ,并在请求标头中添加令牌。响应是包含有关令牌所有者的信息(例如电子邮件和姓名)的正文。
要实现此目的,我的方法将覆盖 doDispatch 方法:
LogDispatcherServlet
@Component
public class LogDispatcherServlet extends DispatcherServlet {
@Autowired
private LogRepository logRepository;
private static final Logger logger = LoggerFactory.getLogger(LogDispatcherServlet.class);
@Override
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (!(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper(request);
}
if (!(response instanceof ContentCachingResponseWrapper)) {
response = new ContentCachingResponseWrapper(response);
}
HandlerExecutionChain handler = getHandler(request);
try {
super.doDispatch(request, response);
} finally {
try {
ApiLog log = ApiLog.build(request, response, handler, null);
logRepository.save(log);
updateResponse(response);
} catch (UncheckedIOException e) {
logger.error("UncheckedIOException", e);
} catch (Exception e) {
logger.error("an error in auth", e);
}
}
}
private void updateResponse(HttpServletResponse response) throws IOException {
ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
responseWrapper.copyBodyToResponse();
}
}
ApiLog.build 负责获取有关请求的示例信息,并且 LogDispatcherServlet 对于 localhost:8080 / v1 / auth / rules 中的GET正常工作em>。
ApiLog
public static ApiLog build(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain handler, Authentication auth) {
ApiLog log = new ApiLog();
log.setHttpStatus(response.getStatus());
log.setHttpMethod(request.getMethod());
log.setPath(request.getRequestURI());
log.setClientIp(request.getRemoteAddr());
try {
if (request.getReader() != null) {
log.setBodyRequest(getRequestPayload(request));
}
} catch (IOException e) {
e.printStackTrace();
}
if (handler != null) {
log.setJavaMethod(handler.toString());
}
if (request.getHeader("Authorization") != null) {
log.setToken(request.getHeader("Authorization"));
} else if (response.getHeader("Authorization") != null) {
log.setToken(response.getHeader("Authorization"));
}
log.setResponse(getResponsePayload(response));
log.setCreated(Instant.now());
logger.debug(log.toString());
return log;
}
@NotNull
private static String getRequestPayload(HttpServletRequest request) {
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
try {
return wrapper
.getReader()
.lines()
.collect(Collectors.joining(System.lineSeparator()));
} catch (IOException e) {
e.printStackTrace();
}
return "{}";
}
@NotNull
private static String getResponsePayload(HttpServletResponse responseToCache) {
ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(responseToCache, ContentCachingResponseWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, 5120);
try {
return new String(buf, 0, length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException ex) {
logger.error("An error occurred when tried to logging request/response");
}
}
}
return "{}";
}
我最大的问题是:我正在使用Spring Security生成JWT令牌,因此所有发送到 / v1 / auth / login 的请求都将重定向到过滤器。
AppSecurity
@Configuration
@EnableWebSecurity
public class AppSecurity extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.and()
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
}
成功通过身份验证后,过滤器必须调用LogDispatcherServlet来持久保存请求和响应。没有 / login 的 Controller ,只有JWTLoginFilter。
JWTLoginFilter
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
AccountCredentials credentials = new ObjectMapper()
.readValue(request.getInputStream(), AccountCredentials.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
Md5.getHash(credentials.getPassword()),
Collections.emptyList()
)
);
}
@Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain,
Authentication auth) throws IOException, ServletException {
TokenAuthenticationService.addAuthentication(response, auth.getName());
//Must call LogDispatcherServlet
filterChain.doFilter(request, response);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
super.unsuccessfulAuthentication(request, response, failed);
//Must call LogDispatcherServlet
}
}
但是它不适用于 / login 。当ApiLog尝试在 getRequestPayload 中获取请求正文时,我得到一个 java.io.IOException:流已关闭
该如何避免这种情况? JWTLoginFilter也需要知道身份验证请求主体和LogDispatcherServlet,但是在 attemptAuthentication 中调用了 request.getInputStream()。还有没有那么简单的解决方案?
答案 0 :(得分:0)
我认为您不需要更新Spring的DispatcherServlet
。我只需要创建一个过滤器(位于链的第一个位置),即可将原始请求/响应包装在允许缓存的对象(例如ContentCachingRequestWrapper
/ ContentCachingResponseWrapper
)中。
在过滤器中,您只需要执行以下操作即可:
doFilter(chain, req, res) {
ServletRequest wrappedRequest = ...
ServletResponse wrappedResponse = ...
chain.doFilter(wrappedRequest, wrappedResponse);
}
您可以注册HandlerInterceptor
public class YourHandlerIntercepter extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod)
ApiLog log = ApiLog.build(request, response, handler, null);
logRepository.save(log);
}
return true;
}
}
您最终需要更改ApiLog方法,使其使用MethodHandler
而不是HandlerExecutionChain