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
Related
I am trying to enable the CORS support in Spring Boot app but I am not getting successful. I looked into a lot of solutions but none seems to be working for me.
When I try to make a call from the Angular app to Java backend I see the error in chrome:
Access to XMLHttpRequest at 'http://localhost:8080/..' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
I have enabled CORS in controller method level by adding the following annotation but still I get the preflight request error.
#CrossOrigin(origins = "http://localhost:4200")
My Spring Security configuration:
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
}
My custom filter:
#Configuration
public class AuthFilter implements Filter {
#Autowired
private Environment env;
private static final ApplicationLogger logger = ApplicationLogger.getInstance();
#Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.debug("Initializing authentication filter.");
}
public boolean checkHeader(HttpServletRequest httpRequest) {
boolean flag = false;
String applicationName = httpRequest.getHeader("bar");
if (applicationName != null && applicationName.equalsIgnoreCase("foo")) {
flag = true;
}
return flag;
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// HttpSession httpSession = httpRequest.getSession();
List<String> excludedUrls = null;
String excludePattern = env.getProperty("excludedUrls");
excludedUrls = Arrays.asList(excludePattern.split(","));
String path = ((HttpServletRequest) request).getServletPath();
String loginPathURL = env.getProperty("loginPathURL");
if (excludedUrls.contains(path)
|| path.contains("/file/..")
|| path.contains("/file/...")
|| path.contains("/file/....")) {
chain.doFilter(request, response);
} else if (checkHeader(httpRequest)) {
// Authenticate the request through LDAP
logger.info("Authenticating the request ...");
chain.doFilter(request, response);
} else {
logger.debug("User is not authenticated");
httpResponse.sendRedirect(loginPathURL);
}
/*
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = httpRequest.getSession();
List<String> excludedUrls = null;
String excludePattern = env.getProperty("excludedUrls");
excludedUrls = Arrays.asList(excludePattern.split(","));
String path = ((HttpServletRequest) request).getServletPath();
if (excludedUrls.contains(path)) {
// Authenticate the request through LDAP
logger.info("Authenticating the request ...");
chain.doFilter(request, response);
}
else if(checkHeader(httpRequest)) {
else if (httpSession != null && httpSession.getAttribute(WorkpermitConstants.CLIENT_AUTH_TOKEN_KEY) != null) {
List<Map<String,Object>> res = (List<Map<String,Object>>) jdbcTemplate.queryForList("some select query") ;
if(!AppUtil.isObjectEmpty(res.size())) {
for (Map<String, Object> row : res) {
//currentUserEmail
//empType
//userId
//username
}
}
chain.doFilter(request, response);
} else {
logger.debug("User is not authenticated.");
HttpServletResponse httpResponse = (HttpServletResponse) response;
//httpResponse.sendRedirect(httpRequest.getContextPath() + "/");
httpResponse.sendRedirect("http://..");
}
*/
// comment below code
// chain.doFilter(request, response);
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
I added the following code in my class after looking into few solutions but it did not work for me either.
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","OPTIONS"));
// NOTE: setAllowCredentials(true) is important,
// otherwise, the value of the 'Access-Control-Allow-Origin' header in the response
// must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true);
// NOTE: setAllowedHeaders is important!
// Without it, OPTIONS preflight request will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(Arrays.asList(
"Authorization",
"Accept",
"Cache-Control",
"Content-Type",
"Origin",
"ajax",
"x-csrf-token",
"x-requested-with"
));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Spring Boot Version:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
add #CrossOrigin("http://localhost:4200") on main method, if you want it for specific controller then add annotation on controller.
Add a #CrossOrigin annotation to any of the following:
Controller Method level - This restricts / enables cross-origin resource sharing only for this specific method.
#CrossOrigin(origins = "http://localhost:4200")
Global CORS
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
}
};
}
Note: Its important to share the complete URL (with http://) in origin
For more refer: https://spring.io/guides/gs/rest-service-cors/
I have custom Filter and I want to log body from request.
But when I use ContentCachingRequestWrapper and try to call getContentAsByteArray() I always get an empty array.
#Component
public class CustomFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(CustomFilter.class);
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest requestToCache = new ContentCachingRequestWrapper(request);
chain.doFilter(req, res);
log.info(getRequestData(requestToCache));
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
String payload = null;
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (wrapper != null) {
byte[] buf = wrapper.getContentAsByteArray();
if (buf.length > 0) {
payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
}
}
return payload;
}
}
I also tried create Interceptor, but had the same problem.
What am I doing wrong?
Thanks for help.
You can use the existing spring implementation by just registering this bean in a #Configuration annotated class:
#Bean
public static Filter requestLoggingFilter() {
final CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
loggingFilter.setIncludePayload(true);
loggingFilter.setMaxPayloadLength(512);
return loggingFilter;
}
Whilst I'd recommend going with NiVer's answer, I've been looking into why this issue occurs and I can finally give you an answer.
When you create a new ContentCachingRequestWrapper, the internal ByteArrayOutputStream is initialized but no data is copied to it. The body is only written to the ByteArrayOutputStream when you call getParameter, getParameterMap(), getParameterNames() or getParameterValues(String name) methods, and even then the data is only copied if the content type contains application/x-www-form-urlencoded.
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);
}
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
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.