I get the following failure responses which the responses themselves are correct:
- An
HTTP 401 Unauthorized
with empty body when the Keycloak token is invalid or expired. - An
HTTP 403 Forbidden
with stack trace in body when access is denied.
But how can one generate custom responses instead?
CodePudding user response:
I managed to customize responses in the following way:
A class with arbitrary name which implements AuthenticationFailureHandler
and AccessDeniedHandler
interfaces:
import com.fasterxml.jackson.databind.ObjectMapper;
import org.keycloak.adapters.springsecurity.authentication.KeycloakCookieBasedRedirect;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
// ... other required imports
@Component
public class CustomSecurityFailureResponseHandler implements AuthenticationFailureHandler, AccessDeniedHandler {
private final ObjectMapper objectMapper = new ObjectMapper();
// The body of this method has been copied from its Keycloak counterpart, except for the marked line.
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
if (!response.isCommitted()) {
if (KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request) != null) {
response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl(null));
}
writeResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed"); // <--- marked line
} else {
if (200 <= response.getStatus() && response.getStatus() < 300) {
throw new RuntimeException("Success response was committed while authentication failed!", exception);
}
}
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
writeResponse(response, HttpServletResponse.SC_FORBIDDEN, "Access denied");
}
private void writeResponse(HttpServletResponse response, int status, String message) throws IOException {
// Generate your intended response here, e.g.:
response.setStatus(status);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("UTF-8");
response.getOutputStream().println(
objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(new MessageResponse(status, message)));
}
record ResponseMessage(int statusCode, String message) {}
}
in the keycloak security config:
@KeycloakConfiguration
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Import(KeycloakSpringBootConfigResolver.class)
@RequiredArgsConstructor
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// An object of preceding class is injected here
private final CustomSecurityFailureResponseHandler customSecurityFailureResponseHandler;
// Other required methods has been omitted for brevity
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(customSecurityFailureResponseHandler); // for 403 Forbidden
}
// This method is overridden to customize 401 Unauthorized response
@Override
public KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = super.keycloakAuthenticationProcessingFilter();
filter.setAuthenticationFailureHandler(customSecurityFailureResponseHandler);
return filter;
}
}
The method keycloakAuthenticationProcessingFilter()
is overridden to replace favorable AuthenticationFailureHandler
.
The Keycloak 19.0.3
and the Spring boot 2.7.4
have been used.
I hope this workaround helps someone. Any better solution is appreciated.
CodePudding user response:
You can check the error code you receive back using something like this:
int error = response.getStatusLine().getStatusCode();
Then you can check the error if it triggers and display your own error:
if (error == 404)
{
Toast.makeText(this, "custom message here", LENGTH_SHORT).show();
}