JwtAuthorizationFilter implementtaion not working - spring

In summary JwtAuthorizationFilter is not working.
If I leave //filterChain.doFilter(request, response) commented out I correctly get a 200 but the body is empty, which means two things:
1) The Controller/Response from the controller is executed but not the logic on it.
2) The function getAuthentication() correctly reads the Claims/token
The issue happens if I uncomment the line //filterChain.doFilter(request, response) because I get a 405. That line should be uncommented for the filter chain to exeute completely and get a response body with content.
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain)
throws IOException, ServletException {
UsernamePasswordAuthenticationToken authentication = getAuthentication(request);
if (authentication == null) {
filterChain.doFilter(request, response);
return;
}
SecurityContextHolder.getContext().setAuthentication(authentication);
**//filterChain.doFilter(request, response);**
}
Function in the controller:
#GetMapping("/foo")
#ResponseStatus(code = HttpStatus.OK)
public MyObject retrieve(#RequestBody MyModel obj) {
//code here is never called
}
For reference, this is my security config:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, "/bar").permitAll()
.anyRequest().authenticated().and().addFilter(new AnotherFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
What Am I doing wrong?

Just to say that I clean up the build, restart the IDE and worked.
Maybe there was some cache not allowing the JWT filter to work properly.
Thanks,

Related

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

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?
When a request comes to a protected endpoint and it is not authenticated, Spring Security executes its AuthenticationEntryPoints interface to commence an authentication scheme.
You could create your own AuthenticationEntryPoint that adds the header and delegates to the LoginUrlAuthenticationEntryPoint (or other implementation that you are using).
#Bean
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
http
//...
.exceptionHandling(exception -> exception
.authenticationEntryPoint(new HxRefreshHeaderAuthenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login")))
);
return http.build();
}
public class HxRefreshHeaderAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final AuthenticationEntryPoint delegate;
public HxRefreshHeaderAuthenticationEntryPoint(AuthenticationEntryPoint delegate) {
this.delegate = delegate;
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
// Add the header
this.delegate.commence(request, response, authException);
}
}
You need to make sure that your filter runs before any Spring Security filter. See at SecurityProperties.DEFAULT_FILTER_ORDER or HttpSecurity#addFilterBefore

Why doesn't SpringBoot Security return any response to REST client although the authentication is done

I'm trying to implement JWT auth with a REST API in SpringBoot. When I debug my code, I see that the JWT Authenticator works correctly but I can't see that the JWT Authorization code is called by the Spring Security framework and there's no response sent to my REST client. Below are some parts of my code that I think are related to my problem.
I think my request is getting lost somewhere in the Spring Security flow...
WebSecurityConfig:
#EnableWebSecurity(debug = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
JWTAuthenticationFilter:
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
setAuthenticationManager(authenticationManager);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (!HttpMethod.POST.matches(request.getMethod())) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
try {
JsonAuthenticationParser auth =
new ObjectMapper().readValue(request.getInputStream(), JsonAuthenticationParser.class);
System.out.println(auth.username);
System.out.println(auth.password);
UsernamePasswordAuthenticationToken authRequest =
new UsernamePasswordAuthenticationToken(auth.username, auth.password);
return this.getAuthenticationManager().authenticate(authRequest);
} catch (Exception e) {
log.warn("Auth failed!!!!!!!!!!!!");
throw new InternalAuthenticationServiceException("Could not parse authentication payload");
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,
FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = Jwts.builder().setSubject(((User) auth.getPrincipal()).getUsername())
.claim("roles", ((User) auth.getPrincipal()).getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET.getBytes()).compact();
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
System.out.println("Token:"+token);
}
JWTAuthorizationFilter
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(
HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
System.out.println("++++++++++++++++++++++++++++AUTHERIZATION doFilterInternal++++++++++++++++++++++");
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
System.out.println("++++++++++++++++++++++++++++AUTHERIZATION getAuthentication++++++++++++++++++++++");
}
Background
When you add a filter to the filter chain without specifying the order (http.addFilter(...)), the comparator HttpSecurity uses to determine its order in the chain looks at the filter's parent class. UsernamePasswordAuthenticationFilter comes before BasicAuthenticationFilter (see FilterComparator).
The request comes in, reaches JWTAuthenticationFilter, and "ends" in the successfulAuthentication() method.
Solution
Continue the filter chain in JWTAuthenticationFilter:
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,
FilterChain chain, Authentication auth)
throws IOException, ServletException {
// ...
chain.doFilter(req, res);
}

AuthenticationException thrown in AuthenticationProvider is mapped to AccessDeniedException in ExceptionTranslationFilter

Having written a custom AuthenticationProvider (which calls a service, after which that one calls an external URL to authenticate the given credentials), I wanted to customize the error message people get when their authentication fails, based on the message I pass to the instance of AuthenticationException (so the e.getMessage() passed to BadCredentialsExceptoin in the code below).
#Component
public class TapasAuthenticationProvider implements AuthenticationProvider {
#Override
public TapasAuthentication authenticate(Authentication authentication) throws AuthenticationException {
String userName = authentication.getName();
String password = authentication.getCredentials().toString();
try {
AuthenticationResponse authResponse = authenticationService.authenticate(userName, password);
return new TapasAuthentication(mapToAuthenticatedUser(authResponse, userName, password), password);
} catch (AuthenticationFailedException e) {
// Note that AuthenticationFailedException is a self-written exception, thrown by the authenticationService.
log.error("Authentication failed: ", e);
throw new BadCredentialsException(e.getMessage());
}
}
}
Now I looked up how to map AuthenticationExceptions and found that an AuthenticationEntryPoint should be used for this. So I created one and added it to my SecuritySetup:
#Component
public class TapasAuthenticationEntryPoint implements AuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
System.out.println(authException.getMessage());
// More code to be added once exception is what I expect.
}
}
#Autowired
private TapasAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf().disable();
}
This successfully triggers the AuthenticationEntryPoint, but instead of a BadCredentialsException, I get an InsufficientAuthenticationException. I checked the origin of this exception and it comes from the ExceptionTranslationFilter's handleSpringSecurityException. Here the exception turns out to be an AccessDeniedException instead of an AuthenticationException.
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
// I would except to enter this if-statement, but exception is not what I expect
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
....
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
// Instead code comes here, and creates an InsufficientAuthenticationException.
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
...
}
}
Why does the exception not match my exception thrown in the AuthenticationProvider? And how would I be able to pass data from the AuthenticationProvider back to the user?
Turns out the HttpBasicConfigurer returned by the .basicSecurity() call also allows to register an AuthenticationEntryPoint. When registering it that way, the exception thrown by the Provider does end up in the entry point.
Security config looks like this:
#Autowired
private TapasAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf().disable();
}

Filter for Url Pattern without authentification?

Help me please a little with setting up Spring Security.
I found something similar, but it somehow does not work very well for me ..
https://stackoverflow.com/a/36875726/1590594
The configuration specifies that each request must be authenticated.
It is necessary to do the following, that on the specified URL ("/ push") worked only one filter. The filter does the appropriate checking and skips the request further or rejecting. Without authentication.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().
authorizeRequests()
.anyRequest().authenticated().
and().
anonymous().disable().
exceptionHandling().authenticationEntryPoint(unauthorizedEntryPoint());
http.addFilterBefore(new UserAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class);
http.authorizeRequests().antMatchers(HttpMethod.POST, "/push").authenticated().and().addFilterBefore(new RPushFilter(),BasicAuthenticationFilter.class);
}
and filter
public class RPushFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
//IF NOT httpResponse.sendError(HttpStatus.BAD_REQUEST.value(), "Access denied");
chain.doFilter(request, response);
}
}

Security filters are overlapping

When multiple filters are added to the HttpSecurity configure method, they seem to be overlapping because only one works at the time.
This is the configure method:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.logout().and().antMatcher("/**")
.addFilterBefore(ssoFilter(), RequestHeaderAuthenticationFilter.class)
.authenticationProvider(preauthAuthProvider())
.authorizeRequests()
.antMatchers("/index.html", "/home.html", "/", "/login").permitAll()
.anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
I've tried to specify the order but the issue still persists:
#Bean
public FilterRegistrationBean securityFilterChain(#Qualifier(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) Filter securityFilter) {
FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
registration.setOrder(Integer.MAX_VALUE - 2);
registration.setName(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME);
return registration;
}
#Bean
public FilterRegistrationBean ssoFilterRegistrationBean() throws Exception {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(ssoFilter());
registrationBean.setOrder(Integer.MAX_VALUE-1);
return registrationBean;
}
#Bean
public FilterRegistrationBean csrfFilterRegistrationBean() throws Exception {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(csrfHeaderFilter());
registrationBean.setOrder(Integer.MAX_VALUE);
return registrationBean;
}
I've followed the following thread with no success.
Filter order in spring-boot
https://github.com/spring-projects/spring-boot/issues/1640
https://github.com/spring-projects/spring-boot/issues/677
Any help will be appreciated!
UPDATE:
CSRF Filter definition
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request
.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null
|| token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
SSO Filter definition:
public class SSORequestHeaderAuthenticationFilter extends RequestHeaderAuthenticationFilter {
private boolean allowPreAuthenticatedPrincipals = true;
public SSORequestHeaderAuthenticationFilter() {
super();
//TODO Pull this value from a properties file (application.properties, or localstrings.properties)
//NOTE SM_USER is the default, but you can change it like this (your company may use some other header)
//this.setPrincipalRequestHeader("SM_USER");
}
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
}
/**
* This is called when a request is made, the returned object identifies the
* user and will either be {#literal null} or a String. This method will throw an exception if
* exceptionIfHeaderMissing is set to true (default) and the required header is missing.
*
* #param request {#link javax.servlet.http.HttpServletRequest}
*/
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
String userName = (String) (super.getPreAuthenticatedPrincipal(request));
if (userName == null || userName.trim().equals("")) {
return userName;
}
return userName;
}
public boolean isAllowPreAuthenticatedPrincipals() {
return allowPreAuthenticatedPrincipals;
}
}
My guess is that you are not always executing FilterChain.doFilter method inside both filters. Then the filter chain stops and only one of your custom filters is executed. In this simple example both filters executed:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(new Filter1(), RequestHeaderAuthenticationFilter.class)
.addFilterAfter(new Filter2(), CsrfFilter.class)
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
static class Filter1 extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("executed filter 1");
filterChain.doFilter(request, response);
}
}
static class Filter2 extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
System.out.println("executed filter 2");
filterChain.doFilter(request, response);
}
}
}
You are confusing container registration (with FilterRegistrationBean) and registration in a Security filter chain (with HttpSecurity) and also possibly with the order of the filter chains within Spring Security. If a given filter chain is selected by Spring Security all the filters in it are not even necessarily fired anyway (filters can always switch off other downstream filters).
I suggest you stop worrying about the order in your FilterRegistrationBeans and use them to disable the container registration (by setting their enabled flag to false). Then think about the order of your filter chains, as specified by the #Order on your WebSecurityConfigurers. And finally you can decide if the order of the filters in a given chain matters, and if it does use the addFilter{Before,After} methods.
A Filter which does what you want to do does already exist. Its the OAuth2AuthenticationProcessingFilter.
This filter is used if you annotate your application with #EnableResourceServer. If you do so this will cause, that only token based authentication will work now.
You have to set the stateless flag of this filter to false to allow other ways of authentication, too.
What i did is to create a class ApiTokenAccessFilter which extends OAuth2AuthenticationProcessingFilter. This filter takes a ResourceServerTokenServices constructor parameter and sets the stateless flag to false.
public class ApiTokenAccessFilter extends OAuth2AuthenticationProcessingFilter {
public ApiTokenAccessFilter(ResourceServerTokenServices resourceServerTokenServices) {
super();
setStateless(false);
setAuthenticationManager(oauthAuthenticationManager(resourceServerTokenServices));
}
private AuthenticationManager oauthAuthenticationManager(ResourceServerTokenServices tokenServices) {
OAuth2AuthenticationManager oauthAuthenticationManager = new OAuth2AuthenticationManager();
oauthAuthenticationManager.setResourceId("oauth2-resource");
oauthAuthenticationManager.setTokenServices(tokenServices);
oauthAuthenticationManager.setClientDetailsService(null);
return oauthAuthenticationManager;
}
}
In my security config i used this Filter as follows:
#Configuration
#EnableOAuth2Sso
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private ResourceServerTokenServices tokenServices;
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.addFilterBefore(new ApiTokenAccessFilter(tokenServices), AbstractPreAuthenticatedProcessingFilter.class);
}
}
I think this could be easier so i opened an issue on the spring-security-oauth Github repo. I'm not sure whether this solution is the way to go, but i didn't find another alternative.

Resources