Мне нужно сохранить всю информацию о запросах / ответах, которые отправляются для приложения, в виде статуса http, текущего времени, токена, URI запроса и т. д. Это API, и ресурсы:
POST локальный: 8080 / v1 / авторизация / логин с адресом электронной почты и паролем в запросе на аутентификацию. Ответ представляет собой токен JWT.
GET локальный: 8080 / v1 / auth / правила с токеном в заголовке запроса. Ответ - это тело с информацией о владельце токена, такой как адрес электронной почты и имя.
Для этого мой метод переопределяет метод 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 отлично работает для GET в локальный: 8080 / v1 / auth / правила.
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 / войти, перенаправляются для фильтра.
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 для сохранения того, что было запросом и каким будет ответ. Для /авторизоваться нет Контроллер, только 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
}
}
Но для /авторизоваться это не работает. Когда ApiLog пытается получить тело запроса в getRequestPayload, я получаю java.io.IOException: поток закрыт
Что я могу сделать, чтобы этого избежать? JWTLoginFilter должен знать тело запроса для аутентификации и LogDispatcherServlet, но request.getInputStream () вызывается в попытка аутентификации. Есть ли другое менее сложное решение?




Я не думаю, что вам нужно обновлять 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.