Home > Net >  How to add a header on an auth redirect response with Spring?
How to add a header on an auth redirect response with Spring?

Time:09-22

For integration of Spring Boot with htmx, I need a way to add a header if an incoming request is done by htmx and the user is no longer logged on.

In the normal flow, the user gets redirected to the login page. However, when there is a request done by htmx, this is an AJAX request and the redirect is not happening.

The recommended solution is that if there is an HX-Request header on the request, that the server should put an HX-Refresh: true header on the response. This will make htmx do a full page refresh.

My security config looks like this:

@Configuration
public class WebSecurityConfiguration {
    private final ClientRegistrationRepository clientRegistrationRepository;

    public WebSecurityConfiguration(ClientRegistrationRepository clientRegistrationRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(registry -> {
            registry.mvcMatchers("/actuator/info", "/actuator/health").permitAll();
            registry.mvcMatchers("/**").hasAuthority(Roles.ADMIN);
            registry.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
            registry.anyRequest().authenticated();
        });
        http.oauth2Client();
        http.oauth2Login();
        http.logout(logout -> logout.logoutSuccessHandler(oidcLogoutSuccessHandler()));

        return http.build();
    }

    private LogoutSuccessHandler oidcLogoutSuccessHandler() {
        OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);

        // Sets the location that the End-User's User Agent will be redirected to
        // after the logout has been performed at the Provider
        logoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}");

        return logoutSuccessHandler;
    }
}

I tried with a Filter:

public Filter htmxFilter() {
        return new Filter() {
            @Override
            public void doFilter(ServletRequest servletRequest,
                                 ServletResponse servletResponse,
                                 FilterChain filterChain) throws IOException, ServletException {
                HttpServletRequest request = (HttpServletRequest) servletRequest;
                HttpServletResponse response = (HttpServletResponse) servletResponse;

                filterChain.doFilter(servletRequest, servletResponse);
                String htmxRequestHeader = request.getHeader("HX-Request");
                System.out.println("htmxRequestHeader = "   htmxRequestHeader);
                System.out.println(response.getStatus());
                if (htmxRequestHeader != null
                        && response.getStatus() == 302) {
                    System.out.println("XXXXXXXXXXX");
                    response.setHeader("HX-Refresh", "true");
                }
            }
        };
    }

But response.getStatus() is never 302 (altough I can see the 302 response status in Chrome).

I also tried with an Interceptor:

    @Bean
    public HandlerInterceptor htmxHandlerInterceptor() {
        return new HandlerInterceptor() {

            @Override
            public void postHandle(HttpServletRequest request,
                                   HttpServletResponse response,
                                   Object handler,
                                   ModelAndView modelAndView) throws Exception {
                boolean htmxRequest = request.getHeader("HX-Request") != null;
                String htmxRequestHeader = request.getHeader("HX-Request");
                System.out.println("htmxRequestHeader = "   htmxRequestHeader);
                System.out.println(response.getStatus());

                if( htmxRequest && response.getStatus() == 302) {
                    response.setHeader("HX-Refresh", "true");
                }
            }
        };
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeInterceptor());
        registry.addInterceptor(htmxHandlerInterceptor());//.order(Ordered.HIGHEST_PRECEDENCE);
    }
    

Which also does not work, there is no 302 response status.

I also tried with the commented out order(Ordered.HIGHEST_PRECEDENCE), but that did not make any difference.

Are there other options?

CodePudding user response:

You need to make sure that your filter runs before any Spring Security filter. See at SecurityProperties.DEFAULT_FILTER_ORDER or HttpSecurity#addFilterBefore

CodePudding user response:

I think that you should use the LogoutSuccessHandler as you did with OidcClientInitiatedLogoutSuccessHandler. You should create another implementation of that with your business logic and create a CompositeLogoutSuccessHandler that would call those multiple LogoutSuccessHandlers that you've created.

Something like this:

@Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
    http
        // ...
        .logout((logout) -> logout.logoutSuccessHandler(new CompositeLogoutSuccessHandler(new HxRefreshHeaderLogoutSuccessHandler(), new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository))));
    return http.build();
}

static class HxRefreshHeaderLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // your business logic
        response.setHeader("HX-Refresh", "true");
    }
}

static class CompositeLogoutSuccessHandler implements LogoutSuccessHandler {

    private final List<LogoutSuccessHandler> handlers;

    CompositeLogoutSuccessHandler(LogoutSuccessHandler... handlers) {
        this.handlers = Arrays.asList(handlers);
    }

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        for (LogoutSuccessHandler handler : this.handlers) {
            handler.onLogoutSuccess(request, response, authentication);
        }
    }
}
  • Related