Change the Bad credentials error response spring security oauth2 - spring

I have a AuthorizationServer which uses password grant_type using spring security. I am using this for mobile application, when a user enter username password to log in, the app calls the token endpoint and generate a token if he/she is an authenticated user. This is all handled by password grant_type itself. For a unsuccessful log in it returns below general error with 400 HTTP status code.
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
But for my scenario I need customize this error message. Is their a way to change this error message ?
Note that i tried the suggested duplicate question -
Customize authentication failure response in Spring Security using AuthenticationFailureHandler
but it uses the formLogin and it's not working with my implementation.
Thank you,
Rajith

I couldn't find an answer to this problem for many days. Finally, I got help from one of my colleagues. He asked me to follow this tutorial and it worked for me. Now I could transform the default spring framework response to my response template as follows.
{
"status": 400,
"message": "Invalid username or password",
"timestamp": "2020-06-19T10:58:29.973+00:00",
"payload": null
}
But still, we don't know, why authenticationFailure handler is not working. Hope this helps.

If you want to change only the message text in the response, than it will be enough to add the messages.properties file to the classpath of your application with the following content:
AbstractUserDetailsAuthenticationProvider.badCredentials=Invalid username or password
This will lead to the response below:
{
"error": "invalid_grant",
"error_description": "Invalid username or password"
}

Sabin answer is works, but i need to throw the exception using BadCredentialsException,
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final static Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
#Autowired
private UserService userService;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException{
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
try {
/* CHECKING USER CREDENTIAL */
/* check account */
User userDetail = userService.findByUsername(username);
if (userDetail == null){
throw new Exception("User not found!");
}
/* check password */
String origPass = Utilities.getEncrypted(new String(Base64.decodeBase64(password)), username);
if(!userDetail.getPassword().equals(origPass)){
throw new Exception("Wrong username or password!");
}
/* check is active */
if(!userDetail.getIsActive()){
throw new Exception("User is not active!");
}
/* check allowance in web type */
if(Access.isWeb()){
if(!userDetail.getIsWeb())
throw new Exception("Web access prohibited!");
}
/* check allowance in mobile type */
if(Access.isMobile()){
if(!userDetail.getIsMobile())
throw new Exception("Mobile access prohibited!");
}
/* do some logs */
userService.login(userDetail);
return new UsernamePasswordAuthenticationToken(userDetail, "{noop}".concat(origPass), userDetail.getAuthorities());
} catch (Exception e) {
LOGGER.error("[OAUTH] Error : " + e.getLocalizedMessage());
throw new BadCredentialsException(e.getLocalizedMessage(), e);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

Related

Error message is not displayed when when validating a user using spring framework

So I am trying to authenticate a user using a POST login API but the issue is when the user does not exist (meaning username (unique) not in the database) the thrown error message is not displayed on the client side (POSTMAN). I tried debugging and the error is thrown but not displayed all I see is Status: 401 Unauthorized from POSTMAN
But when the user exists but the password doesn't match, it displays the correct thrown error message. NOTE: I am using spring's OAuth 2.0 Resource Server JWT
Controller method
#PostMapping(path = "/login", consumes = "application/json")
public ResponseEntity<?> login(#Valid #RequestBody UserDTO userDTO) throws UsernameNotFoundException {
LOGGER.info("Authenticating {}", userDTO.getUsername());
userDTOService.confirmUser(userDTO); // Where the issue occurs
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(userDTO.getUsername(), userDTO.getPassword()));
return ResponseEntity.ok()
.header(
HttpHeaders.AUTHORIZATION,
tokenService.generateToken(authentication)
)
.build();
}
Service method (confirm user method)
public void confirmUser(UserDTO userDTO) throws UsernameNotFoundException {
/*
* Check if username exist in the database
* Check if the password provided equals password in database
* */
String username = userDTO.getUsername();
String password = userDTO.getPassword();
Optional<User> user = userRepository.findUserByUserName(username);
// This error is not displayed
if (user.isEmpty()) {
LOGGER.error("User {} does not exist", username);
throw new UsernameNotFoundException(username + " does not exist");
}
boolean checkingCredentials = user
.stream()
.anyMatch(
param ->
param.getUsername().equals(username)
&&
passwordEncoder.matches(password, param.getPassword())
);
if (!checkingCredentials) {
LOGGER.error("Bad user credentials");
throw new RuntimeException("Please check username or password");
}
}
The reason I was getting a 401 instead of the correct error message is because my approach was wrong. I had 2 solutions to this but I am not sure if the 2nd is the industry standard.
The first approach:
Pass the user credentials to a UsernamePasswordToken to generate a token.
Then I the token into the authentication manager to be authenticated
Surround the auth manager in a try catch block to return an exception. The thrown error message will be of your chosen.
The second approach:
I want to check if user exists in the database or else throw Not found exception
If step 1 passed then I want to check the user password trying to log in and the hashed password in the database. If they do not match, I want to throw an invalid password exception
If no error is thrown, then I want to pass the users name, password and authorities into UsernamePasswordAuthenticationToken().

Spring-security - invalid login always return 403 instead of appropriate errors

I am trying to input some more "accurate" error handling for invalid logins.
The three main objectives: invalid password, account disabled, invalid email.
The current calling hierarchy is the following:
Attempted login requests
#Override // THIS OVERRIDES THE DEFAULT SPRING SECURITY IMPLEMENTATION
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String email = request.getParameter("email");
String password = request.getParameter("password");
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, password);
return authManager.authenticate(authToken);
}
This calls another override method where I tried to insert error handling because it has access to the userRepo and object. The issue here is if the AccountLockedException or fails on email finding or password verification, it will always reutrn a 403 and no indication of the thrown exception.
#SneakyThrows
#Override // THIS OVERWRITES THE DEFAULT SPRING SECURITY ONE
public UserDetails loadUserByUsername(String email){
User user = findUserByEmail(email);
if ( user != null){
if (user.isEnabled()){
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> { authorities.add(new SimpleGrantedAuthority(role.getName()));});
sucessfulLogin(user);
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
else { throw new AccountLockedException("Account disabled"); }
}
}
However, what I have found this previous method on throwing will call this additional override method (in the same class as the attempted authentication)
#Override // DO SOMETHING WITH THIS TO PREVENT BRUTE FORCE ATTACKS WITH LIMITED NUMBER OF ATTEMPTS IN A TIME-FRAME
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
System.out.println("aaa");
super.unsuccessfulAuthentication(request, response, failed);
}
Though, at this point it will display the following:
this option gets shown when the password is incorrect.
this option gets shown when the account is disabeld.
this option gets shown when the email is incorrect.
My question is. Firstly how do I appropriately distinguish between these errors and secondly send appropriate http responses based on these errors?
if (failed != null) {
if (failed.getMessage() == "AccountLockedException") {
response.setStatus(403);
} // if account is disabled
else if (failed.getMessage() == "EntityNotFoundException") {
response.setStatus(400);
} // if email incorrect
else if (failed.getMessage() == "Bad credentials") {
response.setStatus(400);
} // if password incorrect
else {
System.out.println("some kind of other authentication error");
response.setStatus(418); // some random error incase this ever happens
}

Spring security 403 with disabled csrf

Using spring security, I've looked at similar questions but they say to try disable cors & csrf.
I am using it on the browser so I will need csrf. But just testing briefly doesn't change the outcome.
On login I get an access token and refresh token.
Using this token gives me a 403 forbidden response code.
My configuration is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/login").permitAll();
http.authorizeRequests().antMatchers(GET, "/**").hasAnyAuthority("STUDENT");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
I think maybe its something to do with this filter but if I change forbidden.value to something else the result is still forbidden.value
public class CustomAuthorizationFilter extends OncePerRequestFilter { // INTERCEPTS EVERY REQUEST
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/login")){ filterChain.doFilter(request,response); } // DO NOTHING IF LOGGING IN
else{
String authorizationHeader = request.getHeader(AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
try {
String token = authorizationHeader.substring("Bearer ".length()); // TAKES TOKEN STRING AND REMOVES BEARER
// THIS NEEDS MAKING SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); // <<<<<<<<<<<<<<<<<<<<<<<
JWTVerifier verifier = JWT.require(algorithm).build(); // USING AUTH0
DecodedJWT decodedJWT = verifier.verify(token);
String email = decodedJWT.getSubject(); // GETS EMAIL
String[] roles = decodedJWT.getClaim("roles").asArray(String.class); // GETS ROLES
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); }); // CONVERTS ALL USERS ROLE INTO AN AUTHORITY
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null); // PASSWORD IS NULL AT THIS POINT
SecurityContextHolder.getContext().setAuthentication(authToken); // INSERTS TOKEN INTO CONTEXT // THIS SHOWS AUTHENTICATED FALSE, DETIALS FALSE AND GRANTED AUTHORITIES EMPTY
filterChain.doFilter(request, response); // GETS TO THIS LINE HERE
}
catch (Exception e){
response.setHeader("error" , e.getMessage() );
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error); // THEN SKIPS RIGHT TO THIS LINE HERE EVEN IF BREAKPOINTING BEFORE
}
}
else{ filterChain.doFilter(request, response); }
}
}
}
debugging shows it hits filterChain.doFilter(request, response) then jumps straight to the exception catch objectMapper line
The user submitting is also of the Student role.
this line
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null);
is missing authorities:
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null, authorities);
Hope that my answer can help,
you can drop a breakpoint to the line change the response status, and then check who and why it returns 403, it can finally help you get the solution
Drop a breakpoint on the line set the 403 status, to see how this happen from the stackframes.
Guess that it returns 403 without much other information, but it must need to set the status to the response, right? So drop a breakpoint to the setStatus method, I don't know where it should locate, in tomcat lib, spring lib, or servlet lib. Check the HttpResponse, they're several implementation, set the breakpoints for those setStatus/setCode methods. (Next you can see it acutally happens at HttpResponseWrapper::setStatus)
Analyze the stackframes to see what's going on there
please check https://stackoverflow.com/a/73577697/4033979

How can I refresh tokens in Spring security

This line:
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
Throws an error like this when my jwt token expires:
JWT expired at 2020-05-13T07:50:39Z. Current time:
2020-05-16T21:29:41Z.
More specifically, it is this function that throws the "ExpiredJwtException" exception :
How do I go about handling these exceptions? Should I catch them and send back to the client an error message and force them to re-login?
How can I implement a refresh tokens feature? I'm using Spring and mysql in the backend and vuejs in the front end.
I generate the initial token like this:
#Override
public JSONObject login(AuthenticationRequest authreq) {
JSONObject json = new JSONObject();
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authreq.getUsername(), authreq.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
.collect(Collectors.toList());
if (userDetails != null) {
final String jwt = jwtTokenUtil.generateToken(userDetails);
JwtResponse jwtres = new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(),
userDetails.getEmail(), roles, jwtTokenUtil.extractExpiration(jwt).toString());
return json.put("jwtresponse", jwtres);
}
} catch (BadCredentialsException ex) {
json.put("status", "badcredentials");
} catch (LockedException ex) {
json.put("status", "LockedException");
} catch (DisabledException ex) {
json.put("status", "DisabledException");
}
return json;
}
And then in the JwtUtil class:
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRESIN))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
For more info, here is my doFilterInternal function that filters every request:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {
try {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
boolean correct = jwtUtil.validateToken(jwt, userDetails);
if (correct) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException ex) {
resolver.resolveException(request, response, null, ex);
}
}
There are 2 main approaches to deal with such situations:
Manage access and refresh tokens
In this case, the flow is the following one:
User logins into the application (including username and password)
Your backend application returns any required credentials information and:
2.1 Access JWT token with an expired time usually "low" (15, 30 minutes, etc).
2.2 Refresh JWT token with an expired time greater than access one.
From now, your frontend application will use access token in the Authorization header for every request.
When backend returns 401, the frontend application will try to use refresh token (using an specific endpoint) to get new credentials, without forcing the user to login again.
Refresh token flow
(This is only an example, usually only the refresh token is sent)
If there is no problem, then the user will be able to continue using the application. If backend returns a new 401 => frontend should redirect to login page.
Manage only one Jwt token
In this case, the flow is similar to the previous one and you can create your own endpoint to deal with such situations: /auth/token/extend (for example), including the expired Jwt as parameter of the request.
Now it's up to you manage:
How much time an expired Jwt token will be "valid" to extend it?
The new endpoint will have a similar behaviour of refresh one in the previous section, I mean, will return a new Jwt token or 401 so, from the point of view of frontend the flow will be the same.
One important thing, independently of the approach you want to follow, the "new endpoint" should be excluded from the required Spring authenticated endpoints, because you will manage the security by yourself:
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
..
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
..
.authorizeRequests()
// List of services do not require authentication
.antMatchers(Rest Operator, "MyEndpointToRefreshOrExtendToken").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
..
}
}
You can call the API for getting the refresh token as below
POST https://yourdomain.com/oauth/token
Header
"Authorization": "Basic [base64encode(clientId:clientSecret)]"
Parameters
"grant_type": "refresh_token"
"refresh_token": "[yourRefreshToken]"
Please be noticed that, the
base64encode is the method to encrypt the client authorization. You can use online at https://www.base64encode.org/
the refresh_token is the String value of the grant_type
yourRefreshToken is the refresh token received with JWT access token
The result can be seen as
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Good luck.

Spring Boot Security OAuth - handle InternalAuthenticationServiceException

My Spring Boot OAuth REST application returns "401 Unauthorized" status when the database connection failure(Spring Security throws InternalAuthenticationServiceException ).
It's strange, and I need to change status to "500 Internal server error" that client can provide some adequate description, like "service is not available".
If I use WebResponseExceptionTranslator then I can catch response, but if I change HTTP status, it works only when the database active. If the database is shutdown, then I get "401 Unauthorized" again.
How can I solve this problem most gracefully?
Depends on which level the exception is thrown, you might want to add exception handler to your login controller:
#ExceptionHandler(InternalAuthenticationServiceException.class)
public ModelAndView handleError(HttpServletRequest req, Exception ex) {
// convert exception to 500, add logging and
}
Learn more about exception handling here:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
I fix this by adding "try catch" around jdbcTemplate request in my custom UserDetailService.
protected List<UserDetails> loadUsersByUsername(String username) {
try {
userDetailsList = this.getJdbcTemplate().query( USERS_BY_USERNAME, new String[]{username},
new RowMapper() {
public UserDetails mapRow( ResultSet rs, int rowNum ) throws SQLException {
String username = rs.getString( 1 );
/* etc. map user fields */
return new SecurityUser( username, /* other params... */ );
}
});
} catch (CannotGetJdbcConnectionException e){
logger.error( "UserDetailService SQL error: " + e.getMessage(), e );
}
return userDetailsList;
}
And then I check InternalAuthenticationServiceException
by WebResponseExceptionTranslator and change response status.
It seems that when I catch CannotGetJdbcConnectionException then something ruins in chain. It works, but I will leave my question open, maybe someone can offer a more clear solution.

Resources