OncePerRequestFilter I can not return HttpServletResponse custom error - spring

I have OncePerRequestFilter:
public class ApiAuthenticationFilter extends OncePerRequestFilter {
...
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("X-Authorization");
if (StringUtils.isEmpty(token)) {
//return custom exception
return;
}
User user = userService.findUserByToken(token);
if (user == null) {
//return custom exception
return;
}
AuthUser authUser = new AuthUser(token, user);
SecurityContextHolder.getContext().setAuthentication(authUser);
filterChain.doFilter(request, response);
}
How can I return to client my custom exception?
I tried response.sendError(777, "Authorization token is invalid");
but client received error with status 500. I can send onli CONSTANT errors from HttpServletResponse loke this:
response.sendError(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED, "Authorization token required");
But this HttpServletResponse not have all codes. For example I want send 666 error.

Related

Onboarding filter in Spring Boot and Spring Security

I'm trying to create a filter in order to redirect logged users to the onboarding page in case they haven't completed the process before.
This is my filter so far:
#Component
#Order(110)
public class OnboardingFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// cast the request and response to HTTP
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
SecurityContextImpl securityContext = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
// if there's a logged user
if (securityContext != null) {
UserPrincipal principal = (UserPrincipal) securityContext.getAuthentication().getPrincipal();
if (!principal.hasOnboarded()) {
httpResponse.sendRedirect("/onboarding");
}
}
// continue with the filter chain
chain.doFilter(httpRequest, httpResponse);
}
I've tried different values for #Order but in every case the http response generates a downloadable content instead of showing the actual requested URL or sending the redirect. Any ideas?
I'm getting this exception:
Caused by: java.lang.IllegalStateException: Committed
at org.eclipse.jetty.server.HttpChannel.resetBuffer(HttpChannel.java:917)
HttpChannel.java:917
at org.eclipse.jetty.server.HttpOutput.resetBuffer(HttpOutput.java:1423)
HttpOutput.java:1423
at org.eclipse.jetty.server.Response.resetBuffer(Response.java:1182)
Response.java:1182
at org.eclipse.jetty.server.Response.sendRedirect(Response.java:534)
Response.java:534
at org.eclipse.jetty.server.Response.sendRedirect(Response.java:543)
Response.java:543
at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
at org.springframework.security.web.firewall.FirewalledResponse.sendRedirect(FirewalledResponse.java:43)
FirewalledResponse.java:43
at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
at org.springframework.security.web.util.OnCommittedResponseWrapper.sendRedirect(OnCommittedResponseWrapper.java:135)
OnCommittedResponseWrapper.java:135
at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
at org.springframework.security.web.util.OnCommittedResponseWrapper.sendRedirect(OnCommittedResponseWrapper.java:135)
OnCommittedResponseWrapper.java:135
at javax.servlet.http.HttpServletResponseWrapper.sendRedirect(HttpServletResponseWrapper.java:130)
HttpServletResponseWrapper.java:130
at org.springframework.web.servlet.view.RedirectView.sendRedirect(RedirectView.java:627)
RedirectView.java:627
at org.springframework.web.servlet.view.RedirectView.renderMergedOutputModel(RedirectView.java:314)
RedirectView.java:314
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:316)
AbstractView.java:316
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1373)
DispatcherServlet.java:1373
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1118)
DispatcherServlet.java:1118
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1057)
DispatcherServlet.java:1057
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
DispatcherServlet.java:943
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
FrameworkServlet.java:1006
... 94 common frames omitted
What about adding a return in:
httpResponse.sendRedirect("/onboarding");
return;
Otherwise you are continuing with the filter chain:
chain.doFilter(httpRequest, httpResponse);
Not 100% sure if this is already solving your issue, but might be also a problem.
I finally solved this by implementing an Spring context-aware GenericFilterBean as follows:
public class OnboardingFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (SecurityContextHolder.getContext().getAuthentication() != null) {
// some business logic
httpResponse.sendRedirect("/onboarding");
}
else {
chain.doFilter(httpRequest, httpResponse);
}
}
}
Don't forget to add it to the security filter chain:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new OnboardingFilter(), UsernamePasswordAuthenticationFilter.class)...

Catching CharConversionException in Spring Boot

I have a typical JWT authentication process on my app. I am able to catch most of the token errors, but when the user sends a bad bearer token (ex. ads.asd.d), CharConversionException is being thrown.
java.io.CharConversionException: Not an ISO 8859-1 character: [�]
The issue is that I can't catch it. I am getting this error.
exception java.io.CharConversionException is never thrown in body of corresponding try statement
Filter class:
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException, MalformedJwtException, CharConversionException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
try {
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
} catch (MalformedJwtException e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
} catch (CharConversionException e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
...
chain.doFilter(request, response);
}
Any idea on how I can catch that exception?

Spring security BasicAuthenticationFilter returns 403 instead of 401

I have implemented the JWT authentication and authorization. Everything is working fine, besides the unauthorized scenario
Unauthorized scenario: making a http call to a route without providing a authorization token.
Result: 403 forbidden instead of unauthorized
Here is my code:
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
After the
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
Executes, the response is 403
Here is my full class:
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// setting the user in the security context
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if(user != null){
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
Remark:
I had the same problem with UsernamePasswordAuthenticationFilter, and I solved it by overriding the default authenticationFailureHandler:
setAuthenticationFailureHandler(new JWTAuthenticationFailureHandler());
How can I get the correct 401 response code and body?
Thanks!
If you look at what BasicAuthenticationFilter which you are overriding with JWTAuthorizationFilter does when authentication fails, it calls authenticationEntryPoint.commence(request, response, failed) which sends 401
BasicAuthenticationEntryPoint
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\"");
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
But you have behaviour that overridden and returning null. So instead of that try one of the following:
Throw BadCredentialsException where you are returning null
Do response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());

AbstractAuthenticationProcessingFilter chain in successfulAuthentication

Hello I have filter to get Autorization from JWT
public class JwtAuthorizationFilter extends AbstractAuthenticationProcessingFilter {
public JwtAuthorizationFilter() {
super("/**");
}
#Override
public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) {
super.setAuthenticationSuccessHandler(successHandler);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader("Authorization");
//code
return getAuthenticationManager().authenticate(getAuthentication(token));
}
private UsernamePasswordAuthenticationToken getAuthentication(String token) {
// code
}
Problem is that when I reach successfulAuthentication and doing chain.doFilter I getting exception
"servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: Cannot call sendError() after the response has been committed] with root cause"
And I can't get my endpoint. I also notice that authResult generate many (Objects?) with same data even if I login only once
#Edit I noticed now that, after successful authorize, spring trying to reach my controller few time. First return value but other just throw eceptions, and I don't know why I have this loop
What is done inside super.successfulAuthentication? If you do any modification to HttpServletResponse object, e.g., by changing the Http Status Codes or ResponseEntity you won't be able to proceed to next filter in the chain by invoking chain.doFilter(request, response) anymore since the response is committed and returned to the client.

Spring Security UsernamePasswordAuthenticationFilter: How to access Request after a failed login

I am implementing a login page using Angular 7 and Spring Boot and I am with an issued processing a failed login. Basically I want to lock for a specific amount of time the login after X login attempt failures.
HttpSecurity configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
logger.info("#### Configuring Security ###");
JWTAuthenticationFilter jwtAuthenticationFilter = new JWTAuthenticationFilter(authenticationManager());
jwtAuthenticationFilter.setFilterProcessesUrl("/rest/users/authenticate");//this override the default relative url for login: /login
http
.httpBasic().disable()
.csrf().disable()
.authorizeRequests()
.antMatchers("/rest/", "/rest/helloworld/**").permitAll()
.anyRequest().authenticated()
.and().exceptionHandling().authenticationEntryPoint(new JwtAuthenticationEntryPoint()).and()
.addFilter(jwtAuthenticationFilter);
To process the login i created a Filter
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static Logger logger = Logger.getLogger(JWTAuthenticationFilter.class);
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserDto credentials = new ObjectMapper().readValue((request.getInputStream()), UserDto.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUserName(),
credentials.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//sucessfull authentication stuff
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
logger.info("Authentication failed");
ErrorMessage errorMessage = new ErrorMessage("access_denied", "Wrong email or password.");
String jsonObject = JSONUtil.toJson(errorMessage);
//processing authentication failed attempt
UserDto credentials = new ObjectMapper().readValue((request.getInputStream()), UserDto.class);
AuthenticationService authenticationService = Application.getApplicationContext().getBean(AuthenticationService.class);
int numFailedAttemptLogin = authenticationService.authenticationFailedAttempt(credentials.getUserName());
response.setStatus(403);
PrintWriter out = response.getWriter();
out.print(jsonObject);
out.flush();
out.close();
//super.unsuccessfulAuthentication(request, response, failed);
}
}
The login is working fine with no issues. My problem is with the unsuccessfulAuthentication method. When the user enters bad credentials, a BadCredentials exception is raised and unsuccessfulAuthenticationmethod is call. Here i need to access again to the request form to extract the username and process the authentication failed attempt and I am getting the following exception
java.io.IOException: Stream closed
This is because inside the attemptAuthentication method the request inputstream is read and obviously closed.
How can i access request body information inside the unsuccessfulAuthentication?
I tried SecurityContextHolder.getContext().getAuthentication() but it is null due the authentication failure.
Does anyone have any idea?
Best Regards
After following M.Deinum suggestion i was able to create a component that listens specific Exceptions:
#Component
public class AuthenticationEventListener implements ApplicationListener<ApplicationEvent> {
private static Logger logger = Logger.getLogger(AuthenticationEventListener.class);
#Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
logger.info(String.format("Event types: %s", applicationEvent.getClass()));
if (applicationEvent instanceof AbstractAuthenticationFailureEvent) {
String username = ((AbstractAuthenticationFailureEvent) applicationEvent).getAuthentication().getName();
if (applicationEvent instanceof AuthenticationFailureBadCredentialsEvent) {
logger.info(String.format("User %s failed to login", username));
//this.handleFailureEvent(username, event.getTimestamp());
}
}
}
}
This approach is using Exceptions to drive what to do in specific scenarios. I was able to achieve something similar keep using my JWTAuthenticationFilter like this
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
UserDto credentials = new ObjectMapper().readValue((request.getInputStream()), UserDto.class);
try {
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUserName(),
credentials.getPassword(),
new ArrayList<>())
);
} catch (BadCredentialsException bce) {
try {
handleBadCredentials(credentials, response);
throw bce;
} catch (LockedException le) {
handleUserLocked(credentials, response);
throw le;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
logger.info("Authentication failed");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.getWriter().print(authException.getLocalizedMessage());
response.getWriter().flush();
}
Thak you all for your time and help, much appreciated.

Resources