SC_UNAUTHORIZED returns 403 in Spring Boot - spring

I have the following JWT filter
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 {
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);
}
...
chain.doFilter(request, response);
}
But for some reason, I still get a 403 response instead of 401.
{
"timestamp": "2020-07-08T15:59:50.696+0000",
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/ping"
}
Any idea what might be the issue? I tried different returns but they are all either 500 or 403.

Since you are handling the response directly, you will have to do something like this
StringBuilder sb = new StringBuilder();
sb.append("{ ");
sb.append("\"error\": \"Unauthorized\" ");
sb.append("\"message\": \"Unauthorized\"");
sb.append("\"path\": \"")
.append(request.getRequestURL())
.append("\"");
sb.append("} ");
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write(sb.toString());
return;

Related

Springboot + Axios JWT HTTPonly cookie is null

I cant get this cookie verification filter to work in springboot. I can also see the cookie in postman but not browser.
I generate the cookie in the following:
#PostMapping("/signin")
public ResponseEntity<?> authenticateUser(#RequestBody User loginRequest) {
Authentication authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword())); // gets error here
SecurityContextHolder.getContext().setAuthentication(authentication);
MyUserDetails userDetails = (MyUserDetails) authentication.getPrincipal();
ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails);
List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority()).collect(Collectors.toList());
return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString())
.body(userService.findUserProfileUserByEmail(userDetails.getEmail()));
}
When a request is sent to a restricted access endpoint, it will be run through this filter in the SecurityConfiguration
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
This is the class:
public class AuthTokenFilter extends OncePerRequestFilter {
#Autowired
private JwtUtils jwtUtils;
#Autowired
private MyUserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
String email = jwtUtils.getEmailFromJwtToken(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
System.out.println(SecurityContextHolder.getContext());
SecurityContextHolder.getContext().setAuthentication(authentication); // throws error here
System.out.println("a");
}
}
catch (Exception e) { logger.error("Cannot set user authentication: {}", e);
System.out.println(e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) { return jwtUtils.getJwtFromCookies(request); }
}
Here on line String jwt = parseJwt(request), it will always equal null.
I was told this may be an issue with the actual request itself, that it should contain {withCredentials: true} in Axios, though doing this raises other issues, and does not explain why this cookie exists and is visible in Postman.

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());

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.

Spring Boot JWT filter logic

I inherited a half-written Spring Boot REST service that is using Spring Sec to implement JWT-based API authentication. Gradle security-related dependencies are:
'org.springframework.security:spring-security-jwt:1.0.9.RELEASE'
'org.springframework.security.oauth:spring-security-oauth2:2.2.1.RELEASE'
'io.jsonwebtoken:jjwt:0.9.0'
'org.springframework.boot:spring-boot-starter-security'
This app uses Spring Sec filters to implement the entire auth solution, and I'm trying to wrap my head around how it works, and for the life of me can't make sense of a few critical things :-/
Here's the code:
public class MyAppAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public MyAppAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
ApplicationUser creds = new ObjectMapper()
.readValue(req.getInputStream(), ApplicationUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
new ArrayList<>())
);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
#Override
protected void successfulAuthentication(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain,
Authentication auth) throws IOException, ServletException {
String token = Jwts.builder()
.setSubject(((User) auth.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET.getBytes())
.compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
}
}
public class MyAppAuthorizationFilter extends BasicAuthenticationFilter {
public MyAppAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#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) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SECRET.getBytes())
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}
#Component
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private AccountDAO accountDAO;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountDAO.findByUsername(username);
if(account == null) {
throw new UsernameNotFoundException(username);
}
return new User(account.username, account.password, []);
}
}
What I'm not understanding is:
Can I assume that Spring Security automagically positions these filters in the correct sequence? That is: the MyAppAuthenticationFilter always gets called before the MyAppAuthorizationFilter?
I'm really confused by the authenticationManager.authenticate(...) call inside MyAppAuthenticationFilter#attemptAuthentication. How are creds.getUsename() and cred.getPassword() compared to user information stored in a database (or LDAP or anywhere else)? How does this mechanism relate to UserDetailsServiceImpl#loadByUsername(String)?
All of the logic in MyAppAuthorizationFilter#doFilterInternal doesn't make sense to me. To me, I read it as: check to see if there is a JWT token header on the request. If there isn't, then go ahead and make the request any way (!!!!). If there is, then go ahead and check that the JWT has a valid user as its subject. Shouldn't we be blocking the request if there's no JWT header on the request?

Resources