Spring cloud config failed using filter - java

I'm using Spring Cloud Config to help build Rest Services and I choose GitHub to maintain my config files. There is a need when I change some config on GitHub its WebHooks will call API which Spring Cloud Config provided to monitor configuration change and notify Config Service.
In order to verify whether the request is a GitHub request. I will add a filter to check the signature, which does not work:
The request even doesn't through the filter and get a http 200 ok response. I also test the way to extends WebMvcConfigurerAdapter. But still got the same. I wonder is spring cloud config do sth before my filter can handle the request?
public class WebhookSignatureFilter implements Filter
{
#Autowired private WebhooksAuthService webhooksAuthService;
#Override public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException,
ServletException
{
HttpServletRequest httpServletRequest=(HttpServletRequest)servletRequest;
String githubSignature=httpServletRequest.getHeader("HTTP_X_HUB_SIGNATURE");
String payload=getPayLoad(httpServletRequest);
try
{
if(!webhooksAuthService.isValidWebhookSignature(githubSignature, payload))
{
throw new GeneralSecurityException();
}
}
catch(GeneralSecurityException e)
{
throw new ServletException("verify signature failed!");
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String getPayLoad(HttpServletRequest httpServletRequest) throws UnsupportedEncodingException
{
httpServletRequest.setCharacterEncoding("UTF-8");
BufferedReader bufferedReader=null;
StringBuffer payload=new StringBuffer();
String line;
try
{
InputStream inputStream=httpServletRequest.getInputStream();
bufferedReader=new BufferedReader(new InputStreamReader(inputStream));
while((line=bufferedReader.readLine()) != null)
{
payload.append(line);
}
}
catch(IOException e)
{
e.printStackTrace();
}
finally
{
if(bufferedReader!=null)
{
try
{
bufferedReader.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
return payload.toString();
}
#Override public void destroy()
{
}
#Override public void init(FilterConfig filterConfig) throws ServletException
{
}
}
here is my config file:
#Configuration public class FilterConfig
{
#Bean public FilterRegistrationBean webhooksFilter()
{
FilterRegistrationBean registration=new FilterRegistrationBean();
WebhookSignatureFilter webhookSignatureFilter=new WebhookSignatureFilter();
registration.setFilter(webhookSignatureFilter);
registration.addUrlPatterns("/monitor");
registration.setOrder(0);
return registration;
}
}
What am I doing wrong?

Problem solved. I found that when I checked the application.yml. I originally config rabbitmq ,github config mixed in the same yml file . So it may caused by the confuse yml file which may lead some config not loaded.

Related

Set interceptor only shutdown endpoint

In Spring Boot version 2.2 I have an implementation of HandlerInterceptor and expose info, health, shutdown endpoints of Actuator.
I'd like to set that interceptor only for the shutdown endpoint, but not sure how to do it.
Already know that the code on the following page is useful for setting an interceptor for all endpoints.
https://github.com/spring-projects/spring-boot/issues/11234
#Configuration
public class ActuatorConfig extends WebMvcEndpointManagementContextConfiguration {
#Autowired
private AuthenticationInterceptor authenticationInterceptor;
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebAnnotationEndpointDiscoverer endpointDiscoverer,
EndpointMediaTypes endpointMediaTypes,
CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
WebMvcEndpointHandlerMapping mapping = super.webEndpointServletHandlerMapping(
endpointDiscoverer,
endpointMediaTypes,
corsProperties,
webEndpointProperties);
mapping.setInterceptors(authenticationInterceptor);
return mapping;
}
}
Also I checked this page
Spring Boot can't intercept actuator access
The comments from spring team on github issue link suggest to create a filter for intercepting actuator endpoint.
You can create a servlet filter and add authentication logic.
Here is a example:
#Component
public class ActuatorFilter implements Filter {
private final BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor();
private String encryptedPassword;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
try {
File file = ResourceUtils.getFile("classpath:password.txt");
try (FileReader reader = new FileReader(file)) {
char[] chars = new char[(int) file.length()];
reader.read(chars);
encryptedPassword = new String(chars);
} catch (IOException e) {
e.printStackTrace();
}
}catch (FileNotFoundException e){
e.printStackTrace();
}
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String url = request.getRequestURI().toString();
String rawPassword = request.getHeader("Authorization");
if(url.equals("/actuator/shutdown") && !passwordEncryptor.checkPassword(rawPassword,encryptedPassword)){
response.setStatus(HttpStatus.UNAUTHORIZED.value());
PrintWriter out = response.getWriter();
out.print("Invalid password");
out.flush();
return;
}
chain.doFilter(req, res);
}
#Override
public void destroy() { }
}
In the above code i have used jasypt for encrypting password and matching it in filter.
You can use new BasicPasswordEncryptor().encryptPassword(rawPassword); to get a encrypted password and save it in either DB or file. In the above code i have saved the encrypted password in password.txt in resources folder

How to pass GenericFilterBean data to WebSecurityConfigurerAdapter in Spring Boot?

I am trying to redirect http to https in my spring boot application using:
http.requiresChannel().anyRequest().requiresSecure();
But I am getting ERR_TOO_MANY_REDIRECTS. The reason for this is that the load balancer converts all the https to http and directs the http to port 8082, therefore the app never seems to see the https.
I tried to fix this by adding isSecure before the http to https redirection, like this in my configuration:
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
//variables
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/css/**", "/js/**", "/admin/**")
.permitAll().anyRequest().authenticated().and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.formLogin().loginPage("/login").permitAll().and()
.logout().logoutSuccessUrl("/");
//hsts
http.headers().httpStrictTransportSecurity()
.includeSubDomains(true).maxAgeInSeconds(31536000);
http.addFilterBefore(new IsSecureFilter(), ChannelProcessingFilter.class);
//https compulsion
if(!isSecureFilter.isSecure()) {
http.requiresChannel().anyRequest().requiresSecure();
}
}
//rest of the code
}
I am trying to use HttpServletRequestWrapper so that I can repeatedly use isSecure in WebSecurityConfiguration above through the IsSecureFilter I have created below, to prevent infinite redirects:
public class RequestWrapper extends HttpServletRequestWrapper {
private boolean isSecure;
public RequestWrapper(HttpServletRequest request) throws IOException
{
//So that other request method behave just like before
super(request);
this.isSecure = request.isSecure();
}
//Use this method to read the request isSecure N times
public boolean isSecure() {
return this.isSecure;
}
}
Below is the filter that I am trying to inject in WebSecurityConfiguration, to use it's isSecure value above :
#Component
public class IsSecureFilter extends GenericFilterBean {
private boolean isSecure;
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = new RequestWrapper((HttpServletRequest) request);
this.isSecure = req.isSecure();
chain.doFilter(req, response);
}
public boolean isSecure() {
return this.isSecure;
}
}
So running the above code and putting example.com/login in the browser does redirect to https://example.com/login, but i am still getting ERR_TOO_MANY_REDIRECTS.
I can't understand what I am doing wrong?
My first thoughts are:
Can I inject the IsSecureFilter in WebSecurityConfiguration to retrieve isSecure?
Am I adding the IsSecureFilter filter in a correct way to the configuration.
Is the wrapper filter relationship defined correctly?
EDIT
1) I changed http.addFilterAfter(new isSecureFilter(), ChannelProcessingFilter.class); to http.addFilterAfter(isSecureFilter, ChannelProcessingFilter.class);, still no effect.
2) I tried changing http.addFilterBefore(isSecureFilter, ChannelProcessingFilter.class); to http.addFilterAfter(isSecureFilter, ChannelProcessingFilter.class); but that still did not change anything.
Here is the solution to resolve this issue. Based on investigation, since 8080 and 8082 are used to identify HTTP traffic and HTTPS traffic, some code are added to check the port number instead "isSecure" to decide whether redirect HTTP request or not. The code is like following:
public class IsSecureFilter extends GenericFilterBean {
private boolean isSecure;
private int port;
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = new RequestWrapper((HttpServletRequest) request);
HttpServletResponse res = (HttpServletResponse) response;
this.isSecure = req.isSecure();
this.port = req.getLocalPort();
System.out.println("[DEBUG] : isSecure FILTER :: " + isSecure);
System.out.println("[DEBUG] : port FILTER :: " + port);
System.out.println("[DEBUG] : URL :: " + req.getRequestURL());
String url = req.getRequestURL().toString().toLowerCase();
if(url.endsWith("/login") && url.startsWith("http:") && port == 8080){
url = url.replace("http:", "https:");
String queries = req.getQueryString();
if (queries == null) {
queries = "";
} else {
queries = "?" + queries;
}
url += queries;
res.sendRedirect(url);
}
else {
chain.doFilter(req, response);
}
}
public boolean isSecure() {
return this.isSecure;
}
public boolean setIsSecure(boolean isSecure) {
return this.isSecure = isSecure;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
and remove http.requiresChannel().anyRequest().requiresSecure() in WebSecurityConfiguration class.

Logging filter response with body for REST

I'm trying to log all request and response with the body for my REST service.
So far for logging request, I'm using the Spring built-in solution to log payloads RequestLoggingFilterConfig and it works perfectly.
Now I'm looking for a similar solution for logs Response.
The question is how can I logs the whole responses with the body from REST and can it be done only by the configuration file?
My configuration for the request
#Configuration
public class RequestLoggingFilterConfig {
#Bean
public CommonsRequestLoggingFilter logFilter() {
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
filter.setIncludeQueryString(true);
filter.setIncludePayload(true);
filter.setMaxPayloadLength(10000);
filter.setIncludeHeaders(true);
filter.setAfterMessagePrefix("REQUEST DATA: ");
return filter;
}
}
and the application.properties
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
application.properties logging.level.* help just configured your logging level, For handling the logging response,
You need some kind of middleware which can intercept your response
1: Custom Filter Filter is one of the best approach for handle this problem
eg:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class WebFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(WebFilter.class);
private static final boolean CONDITION = true;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("Initiating WebFilter >> ");
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(requestWrapper, response);
// log your response here
}
#Override
public void destroy() {
logger.debug("Destroying WebFilter >> ");
}
}
register your filter
#Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(someFilter());
registration.addUrlPatterns("/api/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("someFilter");
registration.setOrder(1);
return registration;
}
public Filter someFilter() {
return new WebFilter();
}
2:InterceptorAdapter
You can extend HandlerInterceptorAdapter and override the afterCompletion method
public void afterCompletion,
#Component
public class LogginInterceptor
extends HandlerInterceptorAdapter {
#Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// log your response here
}
}
3:AOP Also you can use magical AOP for handle the response using #After
impl

Spring: How to make a filter throw a custom exception?

I created a filter which authenticate each request header for JWT token:
public class JWTAuthenticationFilter extends GenericFilterBean {
private UserDetailsService customUserDetailsService;
private static Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
private final static UrlPathHelper urlPathHelper = new UrlPathHelper();
public JWTAuthenticationFilter(UserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (authentication == null) {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
}
filterChain.doFilter(request, response);
}
}
I want to throw a custom exception, and that exception returns a response:
#ResponseStatus(value=HttpStatus.SOMECODE, reason="There was an issue with the provided authentacion information") // 409
public class CustomAuthenticationException extends RuntimeException {
private static final long serialVersionUID = 6699623945573914987L;
}
How should I do this ? What is the best design to catch such exception thrown by filters ?
Is there any kind of exception handling mechanism provided by the Spring security that I can use and catch everythin in one point ?
Is there any other way to throw custom exceptions in a filter ?
Note: there is another question here which its accepted answer doesn't answer my question. I want to return a response before getting to any controller.
Error cases I want to handle:
1. Client sends an empty value for the Authorization header.
2. Client sends a malformed token
In both cases I get a response with 500 HTTP status code. I want to get 4XX code back.
Take a look at #ControllerAdvice
Here's an example from my project.
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
private final Logger log = Logger.getLogger(this.getClass().getSimpleName());
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = RuntimeException.class)
public Response handleBaseException(RuntimeException e) {
log.error("Error", e);
Error error = new Error(HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.name());
return Response.status(HttpStatus.BAD_REQUEST.value()).error(error, null).build();
}
#ResponseStatus(HttpStatus.NOT_FOUND)
#ExceptionHandler(value = NoHandlerFoundException.class)
public Response handleNoHandlerFoundException(Exception e) {
log.error("Error", e);
Error error = new Error(HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.name());
return Response.status(HttpStatus.NOT_FOUND.value()).error(error, null).build();
}
#ExceptionHandler(value = AuthenticationCredentialsNotFoundException.class)
public Response handleException(AuthenticationCredentialsNotFoundException e) {
log.error("Error", e);
Error error = new Error(ErrorCodes.INVALID_CREDENTIALS_CODE, ErrorCodes.INVALID_CREDENTIALS_MSG);
return Response.status(ErrorCodes.INVALID_CREDENTIALS_CODE).error(error, null).build();
}
#ResponseStatus(HttpStatus.UNAUTHORIZED)
#ExceptionHandler(value = UnauthorisedException.class)
public Response handleNotAuthorizedExceptionException(UnauthorisedException e) {
// log.error("Error", e);
return Response.unauthorized().build();
}
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e) {
log.error("Error", e);
return e.getClass().getName() + " 14" + e.getMessage();
}
}
Edit
I believe you can response.sendError inside do Filter method.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = AuthenticationService.getAuthentication((HttpServletRequest) request, customUserDetailsService);
SecurityContextHolder.getContext().setAuthentication(authentication);
if (authentication == null) {
logger.debug("failed authentication while attempting to access " + urlPathHelper.getPathWithinApplication((HttpServletRequest) request));
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authentication.");
setUnauthorizedResponse(response);
return;
}
filterChain.doFilter(request, response);
}
public void setUnauthorizedResponse(HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
Response unAuthorizedResponse = Response.unauthorized().build();
try {
PrintWriter out = response.getWriter();
out.println(unAuthorizedResponse.toJsonString());
} catch (IOException e) {
log.error("Error", e);
}
}
I had the same issue with JWT tokens and posted the solution on this question, since the issue there was similar (he had trouble with filter exceptions)
Disclaimer: This is not the answer to the question asked, but this is a followup answer to the problem which Arian was asking.
As commented above, please see how you can autowire in places which are launched before spring container gives us access to beans.
Here I am autowiring my BlacklistJwtRepo
if (blacklistJwtRepo == null) { //Lazy Load because filter
ServletContext servletContext = req.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
}
This is where I am getting hold of the req object -
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
Final code looks like -
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("blacklistJwtRepo : " + blacklistJwtRepo);
//till here the autowired repo (blacklistJwtRepo) is null
if (blacklistJwtRepo == null) { //Lazy Load because filter
ServletContext servletContext = req.getServletContext();
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
blacklistJwtRepo = webApplicationContext.getBean(BlacklistJwtRepo.class);
}

How to add a request interceptor to a feign client?

I want every time when I make a request through feign client, to set a specific header with my authenticated user.
This is my filter from which I get the authentication and set it to the spring security context:
#EnableEurekaClient
#SpringBootApplication
#EnableFeignClients
public class PerformanceApplication {
#Bean
public Filter requestDetailsFilter() {
return new RequestDetailsFilter();
}
public static void main(String[] args) {
SpringApplication.run(PerformanceApplication.class, args);
}
private class RequestDetailsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String userName = ((HttpServletRequest)servletRequest).getHeader("Z-User-Details");
String pass = ((HttpServletRequest)servletRequest).getHeader("X-User-Details");
if (pass != null)
pass = decrypt(pass);
SecurityContext secure = new SecurityContextImpl();
org.springframework.security.core.Authentication token = new UsernamePasswordAuthenticationToken(userName, pass);
secure. setAuthentication(token);
SecurityContextHolder.setContext(secure);
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy() {
}
}
private String decrypt(String str) {
try {
Cipher dcipher = new NullCipher();
// Decode base64 to get bytes
byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
// Decrypt
byte[] utf8 = dcipher.doFinal(dec);
// Decode using utf-8
return new String(utf8, "UTF8");
} catch (javax.crypto.BadPaddingException e) {
} catch (IllegalBlockSizeException e) {
} catch (UnsupportedEncodingException e) {
} catch (java.io.IOException e) {
}
return null;
}
}
This is my feign client:
#FeignClient("holiday-client")
public interface EmailClient {
#RequestMapping(value = "/api/email/send", method = RequestMethod.POST)
void sendEmail(#RequestBody Email email);
}
And here I have a request interceptor:
#Component
public class FeignRequestInterceptor implements RequestInterceptor {
private String headerValue;
public FeignRequestInterceptor() {
}
public FeignRequestInterceptor(String username, String password) {
this(username, password, ISO_8859_1);
}
public FeignRequestInterceptor(String username, String password, Charset charset) {
checkNotNull(username, "username");
checkNotNull(password, "password");
this.headerValue = "Basic " + base64encode((username + ":" + password).getBytes(charset));
}
private static String base64encode(byte[] bytes) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(bytes);
}
#Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("Authorization", headerValue);
}
}
I don't know how to configure this interceptor to my client and how to set the header with the username and password. How can I accomplish that ?
You don't really need your own implementation of the FeignRequestInterceptor as there is already BasicAuthRequestInterceptor in the feign.auth package that does exactly the same.
With this said, you basically have almost everything set up already. All is left to do is to define the basicAuthRequestInterceptor bean with specific username and password:
#Bean
public RequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("username", "password");
}
I know the thread is a bit old but wanted to give some explanation on what's happening here.
If you'd like to customize your Feign requests, you can use a RequestInterceptor. This can be a custom implementation or you can reuse what's available in the Feign library, e.g. BasicAuthRequestInterceptor.
How to register it? Well, there 2 ways to do it depending on how you use Feign.
If you're using plain Feign without Spring, then you gotta set the interceptor to the Feign builder. An example is here.
Feign.builder()
.requestInterceptor(new MyCustomInterceptor())
.target(MyClient.class, "http://localhost:8081");
If you're using Spring Cloud OpenFeign and you use the #FeignClient annotation to construct your clients, then you have to create a bean from your RequestInterceptor by either defining it as a #Component or as a #Bean in one of your #Configuration classes. Example here.
#Component
public class MyCustomInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
// do something
}
}
Also, you can check out one of my articles in this topic, maybe that clears it up better: Customizing each request with Spring Cloud Feign

Categories

Resources