I have the problem that the correlation-id is not propagated from my first to the my second microservice. I started to implement a servlet filter, a context and a context-holder as follows:
#Component
// Do not name bean "RequestContextFilter", otherwise filter will not work!
public class CallContextFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;CallContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader(CallContext.CORRELATION_ID));
filterChain.doFilter(httpServletRequest, servletResponse);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
}
#Component
#Getter
#Setter
public class CallContext {
public static final String CORRELATION_ID = "correlation-id";
private String correlationId = new String();
}
public class CallContextHolder {
private static final ThreadLocal<CallContext> userContext = new ThreadLocal<>();
public static final CallContext getContext() {
CallContext context = userContext.get();
if (context == null) {
context = new CallContext();
userContext.set(context);
}
return userContext.get();
}
}
Then, I implemented a RestTemplate bean as follows:
#Bean
public RestTemplate getRestTemplate() {
RestTemplate template = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = template.getInterceptors();
interceptors.add(new CallContextInterceptor());
return template;
}
and the interceptor looks as follows:
public class CallContextInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add(CallContext.CORRELATION_ID, CallContextHolder.getContext().getCorrelationId());
return execution.execute(request, body);
}
}
When I call my endpoint, the servlet filter is executed and the correlation-id is stored in the CallContextHolder. So far, so good. However, the CallContextInterceptor seems to be called in an other thread and my ThreadLocal variable in the CallContextHolder is null. What I have to do to make this working?
#GetMapping("/ping")
public String ping() {
return pongRestTemplateClient.getPong();
}
Why not use Spring Cloud Sleuth and just let the libary do the work for you? http://cloud.spring.io/spring-cloud-sleuth/spring-cloud-sleuth.html
The problem was that I'm using Hysterix. Hystrix spawns a new thread to execute the code, completely unaware of the "outer" thread context. So, the executing thread losses access to the ThreadLocal dependant functionality when using Hysterix commands.
I found an answer to my problem here: https://github.com/jmnarloch/hystrix-context-spring-boot-starter
Related
I'm adding an admin filter to a specific URL like this
#Bean
public FilterRegistrationBean<AdminFilter> adminFilterRegistrationBean() {
FilterRegistrationBean<AdminFilter> registrationBean = new FilterRegistrationBean<>();
AdminFilter adminFilter = new AdminFilter();
registrationBean.setFilter(adminFilter);
registrationBean.addUrlPatterns("/api/user/activate");
registrationBean.addUrlPatterns("/api/user/deactivate");
registrationBean.setOrder(Integer.MAX_VALUE);
return registrationBean;
}
While I'm testing it with postman or in browser, the filter is applied correctly, only applied to those URL pattern.
But, when I write test for it, somehow the filter is applied to another URL too.
this.mockMvc.perform(
get("/api/issue/").header("Authorization", defaultToken)
).andDo(print()).andExpect(status().isOk())
.andExpect(content().json("{}"));
This code return an error with code "403", on the log it says because the user is not an admin, which means the admin filter applied to "/api/issue/" URL on the mock mvc request.
I'm using #AutoConfigureMockMvc with #Autowired to instantiate the mockMVC.
anyone know why it's happening?
Full code of the admin filter:
#Component
public class AdminFilter extends GenericFilterBean {
UserService userService;
#Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain
) throws IOException, ServletException {
if (userService == null){
ServletContext servletContext = servletRequest.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
userService = webApplicationContext.getBean(UserService.class);
}
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
UUID userId = UUID.fromString((String)httpRequest.getAttribute("userId"));
User user = userService.fetchUserById(userId);
if (!user.getIsAdmin()) {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "User is not an admin");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
Full code of the test file:
#SpringBootTest()
#AutoConfigureMockMvc
#Transactional
public class RepositoryIntegrationTests {
#Autowired
private MockMvc mockMvc;
#Autowired
private RepositoryRepository repositoryRepository;
#Autowired
private UserRepository userRepository;
private String defaultToken;
private String otherToken;
#BeforeEach
void init() {
User defaultUser = userRepository.save(new User("username", "email#mail.com", "password"));
System.out.println(defaultUser);
User otherUser = userRepository.save(new User("other", "other#mail.com", "password"));
defaultToken = "Bearer " + generateJWTToken(defaultUser);
otherToken = "Bearer " + generateJWTToken(otherUser);
}
private String generateJWTToken(User user) {
long timestamp = System.currentTimeMillis();
return Jwts.builder().signWith(SignatureAlgorithm.HS256, Constants.API_SECRET_KEY)
.setIssuedAt(new Date(timestamp))
.setExpiration(new Date(timestamp + Constants.TOKEN_VALIDITY))
.claim("userId", user.getId())
.compact();
}
#Test
public void shouldReturnAllRepositoriesAvailableToUser() throws Exception {
this.mockMvc.perform(
get("/api/issue/").header("Authorization", defaultToken)
).andExpect(status().isOk())
.andExpect(content().json("{}"));
}
}
Your AdminFilter is being registered twice. Once through the FilterRegistrationBean and once due to the fact that it is an #Component and thus detected by component scanning.
To fix do one of 2 things
Remove #Component
Re-use the automatically created instance for the FilterRegistrationBean.
Removing #Component is easy enough, just remove it from the class.
For option 2 you can inject the automatically configured filter into the FilterRegistrationBean configuration method, instead of creating it yourself.
#Bean
public FilterRegistrationBean<AdminFilter> adminFilterRegistrationBean(AdminFilter adminFilter) {
FilterRegistrationBean<AdminFilter> registrationBean = new FilterRegistrationBean<>(adminFilter);
registrationBean.addUrlPatterns("/api/user/activate");
registrationBean.addUrlPatterns("/api/user/deactivate");
registrationBean.setOrder(Integer.MAX_VALUE);
return registrationBean;
}
An added advantage of this is that you can use autowiring to set up dependencies instead of doing lookups in the init method. I would also suggest using the OncePerRequestFilter. This would clean up your filter considerably.
#Component
public class AdminFilter extends OncePerRequestFilter {
private final UserService userService;
public AdminFilter(UserService userService) {
this.userService=userService;
}
#Override
protected void doFilter(HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain filterChain) throws IOException, ServletException {
UUID userId = UUID.fromString((String)httpRequest.getAttribute("userId"));
User user = userService.fetchUserById(userId);
if (!user.getIsAdmin()) {
httpResponse.sendError(HttpStatus.FORBIDDEN.value(), "User is not an admin");
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
I am developing a SpringBoot Project in which there is #RestController and FilterRegistrationBean.The filter I added works, but the url configured in #RestController and #RequestMapping does not work.When I request the url, the response code is 200, but no content showed.While I removed the filter, the RequestMapping worked fine.why? (The url I visited is http://localhost:8080/simple)
Here is my code.
SessionTokenFilter:
public class SessionTokenFilter implements Filter {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
Map<String, String[]> param_map = servletRequest.getParameterMap();
for(String param_key: param_map.keySet()){
System.out.println("Parameter name: "+param_key);
}
System.out.println("Get a request from the browser!");
}
}
AppConfiguration.java
#Configuration
public class AppConfiguration {
#Bean
public FilterRegistrationBean<SessionTokenFilter> registerFilter(){
FilterRegistrationBean filterBean = new FilterRegistrationBean();
filterBean.setFilter(new SessionTokenFilter());
filterBean.setUrlPatterns(Arrays.asList("/*"));
return filterBean;
}
}
ApiRouters.java
#RestController
public class ApiRouters {
#RequestMapping(value="/simple", method= RequestMethod.GET)
public ResponseEntity simple(){
System.out.println("Simple url matched!");
return ResponseEntity.ok().body("Ok");
}
}
and the main entry class:
#SpringBootApplication
public class ApiApplication {
public static void main(String[] args){
SpringApplication.run(ApiApplication.class);
}
}
The reason is that the filter chain should be called.
when i added the code in the filter class, all things worked fine.
filterChain.doFilter(servletRequest, servletResponse);
i am trying to send session id in response header in rest controller but excludepathpattern() seems not working
** the configuration class is not triggering **
i have tried changing the sevlet version but it didnt work
ContextListener
#Override
public void contextInitialized(ServletContextEvent sce) {
ServletContext context = sce.getServletContext();
Map<String, HttpSession> map = new HashMap<>();
context.setAttribute("activeUsers", map);
HttpSessionListener
ServletContext context = session.getServletContext();
Map<String, HttpSession> activeUsers = (Map<String, HttpSession>) context.getAttribute("activeUsers");
activeUsers.put(session.getId(), session);
HandlerInterceptor
ServletContext context = request.getServletContext();
Map<String, HttpSession> activeUsers = (Map<String, HttpSession>) context.getAttribute("activeUsers");
String sessionId = request.getHeader("sessionId");
String requestUrl = request.getRequestURL().toString();
if (requestUrl.contains("/getOtp") || requestUrl.contains("/validateOtp")) {
return true;
} else {
if (activeUsers.containsKey(sessionId)) {
return true;
} else {
response.setStatus(401);
return false;
}
}
interceptorconfigurartion by extendig websecurityconfigure
#Configuration
#EnableAutoConfiguration
public class SessionInterceptorConfig implements WebMvcConfigurer {
#Autowired
private SessionHanlderInterceptor sessionHandlerIntercepto;
#Override
public void addInterceptors(InterceptorRegistry registry) {
// List<String> paths = new ArrayList<String>();
// paths.add("/auth/*");
registry.addInterceptor(sessionHandlerIntercepto).excludePathPatterns("/auth/**");
}
#Bean
public ServletListenerRegistrationBean<CustomSessionListener> filterRegistrationBean() {
ServletListenerRegistrationBean<CustomSessionListener> registrationBean = new ServletListenerRegistrationBean<CustomSessionListener>();
CustomSessionListener customURLFilter = new CustomSessionListener();
registrationBean.setListener(customURLFilter);
registrationBean.setOrder(1); // set precedence
return registrationBean;
}
#Bean
public ServletListenerRegistrationBean<CustomServletContextListener> filterContextRregistration() {
ServletListenerRegistrationBean<CustomServletContextListener> registrationBean = new ServletListenerRegistrationBean<CustomServletContextListener>();
CustomServletContextListener customURLFilter = new CustomServletContextListener();
registrationBean.setListener(customURLFilter);
registrationBean.setOrder(1); // set precedence
return registrationBean;
}
Sprinboot main class
#SpringBootApplication
public class CustomerApplication extends SpringBootServletInitializer {
i expect to add the session id to header in response and to check for the sessionid in request
You can use spring web component "OncePerRequestFilter". You need to inject a bean which extends OncePerRequestFilter. Example:
public class CustomHeaderFilter extends OncePerRequestFilter {
#Override
public void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
response.setHeader(customHeaderName, customHeaderValue);
chain.doFilter(request, response);
}
}
I have a Springboot Application running with a Jersey config line property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, "/mywebpage/.*") to enable rendering static html/js/css/image... content for that specific path prefix. All is fine and mywebpage is loading perfectly on /mywebpage/index.html.
Now after running mywebpage for a few months, we want to redirect a percentage of users (those enabled for a beta) to a new webpage (say https://stackoverflow.com/). So I'm trying to write a Filter for the path /mywebpage/index.html to redirect the user to the new page if they are enabled for the beta. Now the part that stumped me, is that for some reason, no filters are being invoked for any calls to /mywebpage/.* (calls made by the browser to get the html/js/css/... content) (checked with breakpoints and dummy logs). I assume the suspect would be the property ServletProperties.FILTER_STATIC_CONTENT_REGEX.
I already have the bit to compute whether the user is beta enabled, and just need to fit it in the Filter.
Here's what I was trying:
#Provider
public class RedirectionFilter implements ContainerRequestFilter {
#Autowired
private BetaAudienceService betaAudienceService;
#Context
private UriInfo info;
private static final Logger log = LogManager.getLogger(getClass());
#Override
public void filter(ContainerRequestContext request) throws IOException {
log.info("Test Log :: Path=" + request.getUriInfo().getAbsolutePath().getPath());
if (request.getUriInfo().getAbsolutePath().getPath().equals("/mywebpage/index.html") && isBetaEnabled(request)) {
try {
request.abortWith(Response.temporaryRedirect(new URI("https://stackoverflow.com/")).build());
} catch (URISyntaxException e) {
throw new IOException("Failed to build or respond with Redirection URI", e);
}
}
}
private boolean isBetaEnabled(ContainerRequestContext request) { ... }
}
and
public class JerseyConfig extends ResourceConfig {
#Autowired
private ApplicationContext appCtx;
#PostConstruct
public void setup() {
register(A.class);
register(B.class);
...
}
public JerseyConfig() {
property(ServletProperties.FILTER_STATIC_CONTENT_REGEX, Constants.STATIC_CONTENT_PATH);
}
}
Any suggestions on how I can get around this? Or probably is my approach wrong altogether?
I was able to figure this out.
In this case, Jersey is set to run as a Filter (since we need to use ServletProperties.FILTER_STATIC_CONTENT_REGEX) and Jersey handles all the service requests except for the specific paths /mywebpage/* where Jersey is configured to ignore the request due to the static content filter property. Hence those ignored/filtered requests are handled by Springboot directly, which means we can just use a Springboot filter.
Here's the code for the Filter:
public class RedirectionFilter extends OncePerRequestFilter /* can also just `implements Filter` instead */ {
public static final String REDIRECT_PATH = "/mywebpage/index.html";
private static final Logger log = LogManager.getLogger(RedirectionFilter.class);
private final MyConfig myConfig;
public UnifiedShellRedirectionFilter(MyConfig myConfig) {
this.myConfig = myConfig;
}
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("Test Log :: Path: " + request.getPathInfo());
if (isBetaEnabled(request)) {
response.sendRedirect(myConfig.getRedirectionEndpoint() /* "https://stackoverflow.com/" */);
} else {
chain.doFilter(request, response);
}
}
#Override
public void destroy() {
log.info("Destructing Filter: " + this.getClass().getSimpleName());
}
private boolean isBetaEnabled(HttpServletRequest request) { ... }
}
And put the following in your #Configuration class:
#Bean
// #Autowired
public FilterRegistrationBean registerRedirectionFilter(MyConfig myConfig) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setName(RedirectionFilter.class.getSimpleName());
registrationBean.setFilter(new UnifiedShellRedirectionFilter(myConfig));
registrationBean.addUrlPatterns(RedirectionFilter.REDIRECT_PATH);
return registrationBean;
}
I've a simple filter just to check if a request contains a special header with static key - no user auth - just to protect endpoints. The idea is to throw an AccessForbiddenException if the key does not match which then will be mapped to response with a class annotated with #ControllerAdvice. However I can't make it work. My #ExceptionHandler isn't called.
ClientKeyFilter
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Controller
import javax.servlet.*
import javax.servlet.http.HttpServletRequest
#Controller //I know that #Component might be here
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
req = (HttpServletRequest) req
def reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
chain.doFilter(req, res)
}
public void destroy() {}
}
AccessForbiddenException
public class AccessForbiddenException extends RuntimeException {
AccessForbiddenException(String message) {
super(message)
}
}
ExceptionController
#ControllerAdvice
class ExceptionController {
static final Logger logger = LoggerFactory.getLogger(ExceptionController)
#ExceptionHandler(AccessForbiddenException)
public ResponseEntity handleException(HttpServletRequest request, AccessForbiddenException e) {
logger.error('Caught exception.', e)
return new ResponseEntity<>(e.getMessage(), I_AM_A_TEAPOT)
}
}
Where I'm wrong? Can simple servlet filter work with spring-boot's exception mapping?
As specified by the java servlet specification Filters execute always before a Servlet is invoked. Now a #ControllerAdvice is only useful for controller which are executed inside the DispatcherServlet. So using a Filter and expecting a #ControllerAdvice or in this case the #ExceptionHandler, to be invoked isn't going to happen.
You need to either put the same logic in the filter (for writing a JSON response) or instead of a filter use a HandlerInterceptor which does this check. The easiest way is to extend the HandlerInterceptorAdapter and just override and implement the preHandle method and put the logic from the filter into that method.
public class ClientKeyInterceptor extends HandlerInterceptorAdapter {
#Value('${CLIENT_KEY}')
String clientKey
#Override
public boolean preHandle(ServletRequest req, ServletResponse res, Object handler) {
String reqClientKey = req.getHeader('Client-Key')
if (!clientKey.equals(reqClientKey)) {
throw new AccessForbiddenException('Invalid API key')
}
return true;
}
}
You can't use #ControllerAdvice, because it gets called in case of an exception in some controller, but your ClientKeyFilter is not a #Controller.
You should replace the #Controller annotation with the #Component and just set response body and status like this:
#Component
public class ClientKeyFilter implements Filter {
#Value('${CLIENT_KEY}')
String clientKey
public void init(FilterConfig filterConfig) {
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String reqClientKey = request.getHeader("Client-Key");
if (!clientKey.equals(reqClientKey)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid API key");
return;
}
chain.doFilter(req, res);
}
public void destroy() {
}
}
Servlet Filters in Java classes are used for the following purposes:
To check requests from client before they access resources at backend.
To check responses from server before sent back to the client.
Exception throw from Filter may not be catch by #ControllerAdvice because in may not reach DispatcherServlet. I am handling in my project as below:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
String token = null;
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && (bearerToken.contains("Bearer "))) {
if (bearerToken.startsWith("Bearer "))
token = bearerToken.substring(7, bearerToken.length());
try {
AuthenticationInfo authInfo = TokenHandler.validateToken(token);
logger.debug("Found id:{}", authInfo.getId());
authInfo.uri = request.getRequestURI();
AuthPersistenceBean persistentBean = new AuthPersistenceBean(authInfo);
SecurityContextHolder.getContext().setAuthentication(persistentBean);
logger.debug("Found id:'{}', added into SecurityContextHolder", authInfo.getId());
} catch (AuthenticationException authException) {
logger.error("User Unauthorized: Invalid token provided");
raiseException(request, response);
return;
} catch (Exception e) {
raiseException(request, response);
return;
}
// Wrapping the error response
private void raiseException(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ApiError apiError = new ApiError(HttpStatus.UNAUTHORIZED);
apiError.setMessage("User Unauthorized: Invalid token provided");
apiError.setPath(request.getRequestURI());
byte[] body = new ObjectMapper().writeValueAsBytes(apiError);
response.getOutputStream().write(body);
}
// ApiError class
public class ApiError {
// 4xx and 5xx
private HttpStatus status;
// holds a user-friendly message about the error.
private String message;
// holds a system message describing the error in more detail.
private String debugMessage;
// returns the part of this request's URL
private String path;
public ApiError(HttpStatus status) {
this();
this.status = status;
}
//setter and getters