I'm in a Spring project, and I need of filters.
Now I have already read about "Interceptor Vs Filter" and for now I choose filter.
So I have this class as filter
public class LogFilter implements Filter {
private final Logger log = LoggerFactory.getLogger(this.getClass());
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String ipAddress = request.getRemoteAddr();
this.log.info("IP " + ipAddress + ", Time " + new Date().toString());
chain.doFilter(req, res);
}
public void init(FilterConfig config) throws ServletException {
// Get init parameter
String testParam = config.getInitParameter("test-param");
this.log.info("Test Param:" + testParam);
}
public void destroy() {
// Add code to release any resource
}
}
And this method for registration of filter's bean in java config ( no xml configuration )
#Bean
public LogFilter filter() {
LogFilter filter = new LogFilter();
this.beanFactory.autowireBean(filter);
return filter;
}
Now this filter works for every url of my app, how can I choose which url should be "under filter" ?
EDIT
I solved this in this way
#Bean
public FilterRegistrationBean regFilter() {
FilterRegistrationBean regFilter = new FilterRegistrationBean();
regFilter.setFilter(new LogFilter());
regFilter.addUrlPatterns("/test");
return regFilter;
}
Thanks to the hint in comment, I found this
If you're using annotation to configure your web #WebFilter to annotate your Filter as per http://docs.oracle.com/javaee/6/tutorial/doc/bnagb.html.
Note that those annotation should be put in the LogFilter itself. And that it has no relation to spring bean or spring context. Those configuration are scanned and processed by the application server, e.g Tomcat, but Spring.
Related
Spring boot interceptor, intercept the problem of returning Chinese garbled characters.
Code structure as shown
code show as below
demoFilter
public class demoFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.getWriter().write("您好");
return;
}
}
FilterConfig
#Configuration
public class FilterConfig {
#Bean
public FilterRegistrationBean jwtFilter() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new demoFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
controller
#RestController
public class Demo {
#RequestMapping(value = "demo", method = RequestMethod.GET)
public String getStr(HttpServletRequest request, HttpServletResponse response) {
return "demo";
}
}
SpringApplication
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Project start, visit http://localhost:8080/demo
Actual return:??
Expected return:您好
I added the following configuration to application.properties:
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
Browser access, return:
Postman visits and returns correct results.
I would like to ask, how to solve this problem perfectly, so that the browser returns the correct Chinese.
It has been solved, and the following code is added to solve the problem:
response.setContentType("text/html;charset=UTF-8");
I'm currently implementing audit trail in my project, I tried using HandlerInterceptor and it seems it won't work in my project, so i looked for another way and I discovered that it's possible with OncePerRequestFilter.
Here's the code of my OncePerRequestFilter class:
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
// Log the info you need
// ...
filterChain.doFilter(request, response);
}
}
The only problem so far that I see with my current configuration of OncePerRequestFilter is it also includes the resources such as css / javascripts.
example these links will be also go to the filter:
http://localhost:8000/project/css/style.css
http://localhost:8000/project/3277a64fcca0dbde907d8684aed8f170.png
http://localhost:8000/project/js/script.js.map
What i want is to filter only the controller request mappings, and ignore the resources
example:
http://localhost:8000/project/accounts/client-users
http://localhost:8000/project/accounts
This code is a workaround to ignore resource file. not sure if it's the best practice tho.
#Component
#Order
public class LogFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String method = request.getMethod();
String username = SecurityContextHolder.getContext().getAuthentication().getName();
String url = request.getRequestURL().toString();
filterChain.doFilter(request, response);
}
protected boolean shouldNotFilter(HttpServletRequest request)
throws ServletException {
String url = request.getRequestURL().toString();
return isResourceUrl(url);
}
private boolean isResourceUrl(String url) {
boolean isResourceUrl = false;
List<String> resourceRequests = Arrays.asList(
"/css/", "/js/", "/scss/", "/fonts/", "/emails/",
".css", ".js", ".scss", ".eot", ".svg", ".ttf", ".woff", ".otf", ".ico", ".png");
for (String resourceRequest : resourceRequests) {
if (url.contains(resourceRequest)) {
isResourceUrl = true;
}
}
return isResourceUrl;
}
}
Use something like this:
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/example/docs",
"/swagger-resources/**",
"/swagger-ui.html");
}
I am developing a web service with JWT in spring security and spring session. My intention is to add a filter that validates the JWT, extract its JTI and add it in the header service as "x-auth-token". The Json Web Token JTI matches the "Session_id" that generates spring session when a new user is authenticated (it can see with RequestContextHolder.currentRequestAttributes().GetSessionId()). This was put in the Json Web Token JTI when the user was authenticated.
I already have the filter that validates the JWT, however, I will not put it here now for simplicity. What I will put will be only the filter class with the written method doFilter.
What I'm trying to do is add a value to the header, something like this:
public class CustomFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/*
* In this part I validate the token and extract the JTI, which is equal to the session_id of spring session.
* Suppose that JTI = 71b0b8c1-1eac-46ce-80b6-f14c2e08c0de
*/
//I want to do something like this:
request.addHeader("x-auth-token", "71b0b8c1-1eac-46ce-80b6-f14c2e08c0de");
chain.doFilter(request, response);
}
}
This way the spring session token will not be inserted by the user, but by the filter once it is extracted from the JWT.
I have tried to do it through a class that extends from HttpServletRequestWrapper, like this:
public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
/**
* construct a wrapper for this request
*
* #param request
*/
public HeaderMapRequestWrapper(HttpServletRequest request) {
super(request);
}
private Map<String, String> headerMap = new HashMap<String, String>();
/**
* add a header with given name and value
*
* #param name
* #param value
*/
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
#Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
/**
* get the Header names
*/
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
for (String name : headerMap.keySet()) {
names.add(name);
}
return Collections.enumeration(names);
}
#Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values.add(headerMap.get(name));
}
return Collections.enumeration(values);
}
}
and defining the doFilter method like this:
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
/*
* In this part I validate the token and extract the JTI, which is equal to the session_id of spring session.
* Suppose that JTI = 71b0b8c1-1eac-46ce-80b6-f14c2e08c0de
*/
HttpServletRequest r = (HttpServletRequest) request;
HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(r);
requestWrapper.addHeader("x-auth-token", "71b0b8c1-1eac-46ce-80b6-f14c2e08c0de");
chain.doFilter(requestWrapper, response);
}
However it does not work, I do not know if I have missed something or is not the way to do it.
Edit 02/10/2017:
When I run the service spring recognizes that there is no token (x-auth-token) in the header and automatically sends me the filter to authenticate the new user, which causes a Forbidden error because there is no user and password.
If I send the token (x-auth-token) from the beginning in the header everything works fine.
Edit 05/10/2017:
I have created an second filter to check the value that was added by the first filer in the header. The first filter does not receive the value "x-auth-token" from the ServletRequest, it adds it with "requestWrapper".
The second filter was added to configuration class like this:
.addFilterAfter (getCustomFilter (),
UsernamePasswordAuthenticationFilter.class)
.addFilterAfter
(getCustomFilter2 (), UsernamePasswordAuthenticationFilter.class)
where getCustomFilter () and getCustomFilter2 () were created using a bean like this:
#Bean
public CustomFilter getCustomFilter(){
return new CustomFilter();
}
#Bean
public CustomFilter2 getCustomFilter2(){
return new CustomFilter2();
}
The second filter is defined as follows:
public class CustomFilter2 implements Filter {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
System.out.println("Result: " + req.getHeader("x-auth-token"));
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
// TODO Auto-generated method stub
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
When I run the service spring recognizes that there is no token (x-auth-token) in the original header and automatically sends me the filter to authenticate the new user.
I think the problem is the order in which spring session is executed.
How can I call spring session after our filters?
The configuration class is the follows:
#Configuration
#EnableWebSecurity
public class SeguridadConfiguracion extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("seguridadServicio")
private UserDetailsService objSeguridadServicio;
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(objSeguridadServicio);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").hasAnyAuthority("ComisionadoSI","GerenciaSI")
.anyRequest().authenticated()
.and()
.logout().clearAuthentication(true)
.invalidateHttpSession(true)
.and()
.formLogin()
.and()
.httpBasic()
.and()
.addFilterAfter(getCustomFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(getCustomFilter2(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable();
}
#Bean
public CustomFilter getCustomFilter(){
return new CustomFilter();
}
#Bean
public CustomFilter2 getCustomFilter2(){
return new CustomFilter2();
}
#Bean
public HttpSessionStrategy httpSessionStrategy() {
return new HeaderHttpSessionStrategy();
}
}
We can do this via addingAttribute in the filter
httpServletRequest.setAttribute("key name","value");
And in Controller we can access them via #RequestAttribute
Have you verified the order of your filters to make sure this filter is executed in the correct order?
Update to add more info:
Looking at the spring docs: https://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-rest
You enable spring session by adding the annotation #EnableRedisHttpSession.
The #EnableRedisHttpSession annotation creates a Spring Bean with the
name of springSessionRepositoryFilter that implements Filter. The
filter is what is in charge of replacing the HttpSession
implementation to be backed by Spring Session. In this instance Spring
Session is backed by Redis.
According to this the springSessionRepositoryFilter is an instance of springSessionRepositoryFilter: https://docs.spring.io/spring-session/docs/current/api/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.html
RedisHttpSessionConfiguration exposes the SessionRepositoryFilter as a bean named
"springSessionRepositoryFilter". In order to use this a single
RedisConnectionFactory must be exposed as a Bean.
Based on that, I think you need to add your filter before the SessionRepositoryFilter like:
.addFilterAfter(getCustomFilter(), SessionRepositoryFilter.class)
.addFilterAfter(getCustomFilter2(), SessionRepositoryFilter.class)
Following BalusC's instructions on that answer:
How to stream audio/video files such as MP3, MP4, AVI, etc using a Servlet
I added the following Context element to my Tomcat server.xml to make my media files available to Tomcat's own DefaultServlet.
<Context docBase="/home/jwi/media" path="/service/media" />
This works like charm and the media is available at:
http://localhost:8080/service/media/example.mp4
The ApplicationPath from my application (build on Jersey 2.x) is set to: #ApplicationPath("service").
Within that application I have a request filter that checks every incoming request for a valid user session.
#Provider
#PreMatching
#Priority(1)
public class SessionFilter implements ContainerRequestFilter {
#Context
private ServletContext _context;
#Context
private HttpServletRequest _request;
#Context
private HttpServletResponse _response;
public void filter(ContainerRequestContext requestContext) throws IOException {
HttpSession session = _request.getSession(false);
boolean isLoggedIn = session != null && session.getAttribute("username") != null;
boolean isLoginRequest = _request.getRequestURI().contains("login");
if (isLoggedIn || isLoginRequest) {
// Since filter chain is invoked by #Priority annotation here's nothing to do.
} else {
URI indexPage = UriBuilder.fromUri("/index.html").build();
requestContext.abortWith(Response.temporaryRedirect(indexPage).build());
}
}
}
My problem is, that filter is never called on the media elements. So when I open http://localhost:8080/service/media/example.mp4 the filter isn't called at all.
How do I add Tomcat's DefaultServlet to my request filter?
Have you considered a Servlet Filter instead?
#WebFilter("/service/media/*")
public class SessionFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
...
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
}
To force https in web.xml i was using this code snippet:
<security-constraint>
<web-resource-collection>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
Is there an equivalent for this in Spring Java Config? I already figured out that i need a ServletSecurityElement. But how do i connect it to the rest?
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
container.addListener(new ContextLoaderListener(context));
context.register(PersistenceJPAConfig.class);
FilterRegistration filter = container.addFilter("wicket.myproject", WicketFilter.class);
filter.setInitParameter("applicationClassName", WicketApplication.class.getName());
filter.setInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/*");
filter.addMappingForUrlPatterns(null, false, "/*");
HttpConstraintElement forceHttpsConstraint = new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL, "");
ServletSecurityElement securityElement = new ServletSecurityElement(forceHttpsConstraint);
}
}
As John Thompson pointed out you were right there. You just needed to add the security element you defined to the servlet. On another note I noticed you had "" as the roleNames parameter to the HttpConstraintElement. This would actually cause everyone who didn't have the role "" to be denied. If you want this to work like normal (force https) don't give any roles. In the end this worked for me:
public class ApplicationInitializer implements WebApplicationInitializer {
private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
private static final String DISPATCHER_SERVLET_MAPPING = "/";
#Override
public void onStartup(ServletContext container) throws ServletException {
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(ApplicationConfiguration.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher = container.addServlet(DISPATCHER_SERVLET_NAME, new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
// Force HTTPS, and don't specify any roles for this constraint
HttpConstraintElement forceHttpsConstraint = new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL);
ServletSecurityElement securityElement = new ServletSecurityElement(forceHttpsConstraint);
// Add the security element to the servlet
dispatcher.setServletSecurity(securityElement);
}
}
I think you need to get a handle on the servlet registration, then register the security element. Try something like this:
ServletRegistration.Dynamic registration
= container.addServlet("dispatcher", new DispatcherServlet());
registration.setLoadOnStartup(1);
registration.setServletSecurity(securityElement); //your prev defined securityElement
In the case if you use Spring Security 3.2 you could do this as follows.
<security:intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
with http to https port mappings as well.
<security:port-mappings>
<security:port-mapping http="${port.mapping.http.port}"
https="${port.mapping.https.port}" />
private static final String DISPATCHER_SERVLET_NAME = "dispatcher";
private static final String DISPATCHER_SERVLET_MAPPING = "/";
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationContext.class);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet(DISPATCHER_SERVLET_NAME,
new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(DISPATCHER_SERVLET_MAPPING);
HttpConstraintElement forceHttpsConstraint = new HttpConstraintElement(TransportGuarantee.CONFIDENTIAL);
ServletSecurityElement securityElement = new ServletSecurityElement(forceHttpsConstraint);
dispatcher.setServletSecurity(securityElement);
}
What do you mean, connect it to the rest? Looks like you should be set. Spring will auto-detect the configuration of the Java configured WebApplicationInitializer.
Remember that WebApplicationInitializer implementations are detected
automatically -- so you are free to package them within your
application as you see fit.
See:
http://docs.spring.io/spring-framework/docs/3.1.x/javadoc-api/org/springframework/web/WebApplicationInitializer.html#onStartup(javax.servlet.ServletContext)
One way of do this is creating an HTTP filter inside your application:
#Component
#ConfigurationProperties("security.http")
public class ForceHTTPSFilter implements Filter {
public static final String X_FORWARDED_PROTO_HEADER = "x-forwarded-proto";
private boolean forceHttps = false;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if(forceHttps && !request.getProtocol().toUpperCase().contains("HTTPS") && request instanceof HttpServletRequest) {
Optional<String> protocol = Optional.ofNullable(((HttpServletRequest)request).getHeader(X_FORWARDED_PROTO_HEADER));
if(!protocol.orElse("http").equals("https")){
((HttpServletResponse)response).sendError(HttpStatus.FORBIDDEN.value(), "Please use HTTPS when submitting data to this server.");
return;
}
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}
public boolean isForceHttps() {
return forceHttps;
}
public void setForceHttps(boolean forceHttps) {
this.forceHttps = forceHttps;
}
}
You can switch on/off the filter with a property by using #ConfigurationProperties.
Moreover, you should inspect the header x-forwarded-proto because some proxies (like Heroku) remove the protocol from the URL and store it into this header.
And, of course here's a unit test of this filter:
public class ForceHTTPSFilterTest {
#Rule
public MockitoRule rule = MockitoJUnit.rule();
#InjectMocks
private ForceHTTPSFilter filter;
#Test
public void testAcceptHTTPRequestWhenFlagIsDisabled() throws Exception{
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getProtocol()).thenReturn("HTTP/1.1");
HttpServletResponse response = mock(HttpServletResponse.class);
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(chain, times(1)).doFilter(any(), any());
verify(response, never()).sendError(eq(403), anyString());
}
#Test
public void testAcceptHTTPRequestWhenFlagIsEnableAndItHasForwardedProtoHeader() throws Exception{
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getProtocol()).thenReturn("HTTP/1.1");
when(request.getHeader(ForceHTTPSFilter.X_FORWARDED_PROTO_HEADER)).thenReturn("https");
HttpServletResponse response = mock(HttpServletResponse.class);
filter.setForceHttps(true);
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(chain, times(1)).doFilter(any(), any());
verify(response, never()).sendError(eq(403), anyString());
}
#Test
public void testAcceptHTTPSRequest() throws Exception{
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getProtocol()).thenReturn("HTTPS/1.1");
HttpServletResponse response = mock(HttpServletResponse.class);
filter.setForceHttps(true);
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(chain, times(1)).doFilter(any(), any());
verify(response, never()).sendError(eq(403), anyString());
}
#Test
public void testRejectHTTPRequestWhenFlagIsEnableAndItDoesntHaveForwardedProtoHeader() throws Exception{
HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getProtocol()).thenReturn("HTTP/1.1");
HttpServletResponse response = mock(HttpServletResponse.class);
filter.setForceHttps(true);
FilterChain chain = mock(FilterChain.class);
filter.doFilter(request, response, chain);
verify(chain, never()).doFilter(any(), any());
verify(response, times(1)).sendError(eq(403), anyString());
}
}
None of the above answer are good enough when using Spring Boot and External Tomcat. Here the correct configuration:
super() must be called and the existing dispatcher servlet must be taken from the existing container.
private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
#Override
public void onStartup(ServletContext container) throws ServletException {
super.onStartup(container);
// Get the existing dispatcher servlet
ServletRegistration.Dynamic dispatcher = (ServletRegistration.Dynamic)
container.getServletRegistration(DISPATCHER_SERVLET_NAME);
// Force HTTPS, and don't specify any roles for this constraint
HttpConstraintElement forceHttpsConstraint =
new HttpConstraintElement(ServletSecurity.TransportGuarantee.CONFIDENTIAL);
ServletSecurityElement securityElement =
new ServletSecurityElement(forceHttpsConstraint);
// Add the security element to the servlet
dispatcher.setServletSecurity(securityElement);
}