Summary

In Spring Security 4.2.4 or earlier, the HeaderWriterFilter writes headers before filter chin was processed. However, commit https://github.com/spring-projects/spring-security/commit/f81b58112b372c48b94e9b0260ecee0853d94243#diff-57c0f670220b7f4e45a0d1252a99b482 in 4.2.5 changed the timing of header writing to response.onResponseCommitted phase. And this will break existing code which writes custom headers other than those defined in HeaderWriters. For example:

I have some URL intend to be embed in frames. In 4.2.4 or earlier, I can overwrite the default value from XFrameOptionsHeaderWriter as following :

    public ModelAndView relogin(HttpServletResponse response) {
        ModelAndView mav = new ModelAndView();
        mav.setViewName("security/relogin");

        response.setHeader("X-Frame-Options", "SAMEORIGIN"); // overwrite `DENY` in XFrameOptionsHeaderWriter
        return mav;
    }

Now I have no easy way to set X-Frame-Options to SAMEORIGIN in dedicate URLs while applying DENY to rest or the system.

Call sequence illustrated for above code snippet in 4.2.5.RELEASE:

HeaderWriterFilter.doFilterInternal
  filterChain.doFilter
    response.setHeader in contorller     <- manual header writing here (X-Frame-Options=SAMEORIGIN)
  response.onResponseCommitted           <- HeaderWriters writes header here (X-Frame-Options=DENY)

Resulting X-Frame-Options=DENY.

Call sequence illustrated for 4.2.4.RELEASE and earlier:

HeaderWriterFilter.doFilterInternal      <- HeaderWriters writes header here (X-Frame-Options=DENY)
  filterChain.doFilter
    response.setHeader in contorller     <- manual header writing here (X-Frame-Options=SAMEORIGIN)
  response.onResponseCommitted

Resulting X-Frame-Options=SAMEORIGIN as expected.

Actual Behavior

response.setHeader in controller code will not take effect as before.

Expected Behavior

Manual response.setHeader in controller code overwrites headers wrote by HeaderWriters.

Configuration

N/A

Version

4.2.5.RELEASE

Sample

N/A

Comment From: tan9

Hi @rwinch , seems this issue is related to #5004, #5005, #4307, #3975, #2953, it really wired and confusing.

If the behavior is by design, should we document it well for programmer to follow

Comment From: jazdw

@tan9 I agree with what you are saying, it definitely seems more obvious to me that the security headers should be set earlier and then the controller should be able to override them.

Another issue with it is that the OnCommittedResponseWrapper just doesn't seem to do its job reliably as I posted here - https://github.com/spring-projects/spring-security/issues/3975#issuecomment-394878337

Comment From: rwinch

This does appear to be an issue. I think we need to modify it to check to see if the header is already set and if so not to override it. If anyone is interested in submitting a PR that would help get this issue resolved faster as we are focused on some other tasks at the moment.

Comment From: tan9

@rwinch thanks for your response, I am willing to contribute. Would you please provide some direction how to do it right. Use another responseWrapper within HeaderWriterResponse to prevent headers wrote in filterChain been overwritten by headerWriters?

Comment From: rwinch

Thanks for the fast response! I think we should just update the HeaderWriter implementations to only write if the value isn't present.

Comment From: koju

Workarounds are mentioned in #2953. JavaConfig: https://github.com/spring-projects/spring-security/issues/2953#issue-131777949 XmlConfig: https://github.com/spring-projects/spring-security/issues/2953#issuecomment-335443527

Comment From: rwinch

Closing this as a duplicate of gh-2953