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

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

Related

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
}

Backend Create user with signup JWT

I'm trying to create a restful web application with Springboot and Gradle. I use JWT for authentication. When a user logged the backend creates a user token for the user. this token he uses whenever he opens another page to retrieve his data.
What I'm trying to do is making a signup page, but the problem is that I cant send information to my backend without a Bearer token. How do I send a post method to create a user without authentication on this one single POST operation?
To generate a token for a user that exists
#PostMapping(value = "${jwt.get.token.uri}")
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtTokenRequest authenticationRequest)
throws AuthenticationException {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtTokenResponse(token));
}
What I want to call to create a user in my database:
(want to do this POST without authorization)
#PostMapping(value = "/signup")
public ResponseEntity<User> createUser(#RequestBody User user){
HBMUserService HBMuserService = new HBMUserService();
User createdUser = HBMuserService.saveUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.OK);
}
My application properties:
jwt.signing.key.secret=SecretKey
jwt.get.token.uri=/authenticate
jwt.refresh.token.uri=/refresh
jwt.http.request.header=Authorization
jwt.token.expiration.in.seconds=604800
I Found out where my JWT Ignores my Auth path for login and added my signup path there as well.
.antMatchers(
HttpMethod.POST,
authenticationPath,
"/signup"
)

Change the Bad credentials error response spring security oauth2

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

Spring Oauth with multiple users tables

I am creating an application using Spring with Oauth2 as a backend for two apps (provider app and a consumer app). I have two different types of users; Providers, and consumers, each with its own db table.
The problem I am facing is that I cannot find a way to know if the request is coming from a provider or a customer, as each one will be in a different db table.
The username is Not unique between the two tables. So, a provider and a consumer can have the same username (and password).
I think any of the following solutions will suffice, however, I can’t find any way to implement any of them.
Having two different endpoints for each user class. e.g. “/provider/oauth/token” and “/consumer/oauth/token”. Each with its custom authentication manager.
Or: Having two authorization servers in the same Spring application, and then mapping their “/oauth/token” to different endpoints.
Or: Sending custom data in the oauth request to know where the request is coming from, and then dynamically selecting an authentication manager.
Or: Associating different authentication manager to different OAuth clients, and then ensuring that each app will have its respective client ID.
If any of these solutions is possible, or if there is another way to accomplish this, please let me know.
Any help is appreciated.
Edit - Solution
Following the answer below, I added another client with a different client ID, check the id in the UserDetailsService and then decide which db to use. Here is the code:
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
String username = user.getUsername();
if (username.equals(OAuth2Configuration.provider_app))
// Load from provider db
else if (username.equals(OAuth2Configuration.consumer_app))
// Load from consumer db
else
throw new UsernameNotFoundException("ClientID " + username + " not found.");
}
};
}
UsernamePasswordAuthenticationToken is used as /oauth/token is protected with Basic Oauth using the client id and secret.
I think you should be able to look inside SecurityContextHolder.getContext().getAuthentication.
This should be an instance of OAuth2Authentication, from which you can (after you cast) call getOAuth2Request() to get the original Oauth2Request details.
With this information you can have a single UserDetailsService that can delegate lookups to the correct db tables. You could use scopes or resourceIds to help determine what db table to use.
You could use the third option. but this is not a good principal to follow. you can send a custom param in the oauth/token end point. it can be accessed by AutoWiring HttpServletRequest in the userDetailsService.
UserDetailsService
#Autowired
private HttpServletRequest httpServletRequest;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
String userType = httpServletRequest.getParameter("user_type");
LOGGER.info("Load user method \n Username : " + username + "\nuser_type : " + userType);
if (userType == null) {
throw new CustomOauthException("User type is required !");
}
if (userType.equals(String.valueOf(MOBILE_USER))) {
//get user..
} else if (userType.equals(String.valueOf(DRIVER))) {
//get driver..
} else if (userType.equals(String.valueOf(ADMIN))) {
//get admin
}
throw new CustomOauthException("User type is not valid !");
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("Exception : " + e.getMessage());
throw new CustomOauthException(e.getMessage());
}
}

Spring Security Context Set Authentication object not working

I have a scenario where I have to force the users to reset password on first login. For
this I am using a custom successAuthenticationHandler.
All this handler trying to do is see if logged in user requires to reset password. If yes create a new UsernamePasswordAuthenticationToken and set it onto SecurityContext. And then redirect to resetPasswordUrl.
Here is my onAuthenticationSuccess method:
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws ServletException, IOException {
final AugmentedUser aUser = (AugmentedUser) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
System.out.println("In password reset handler.");
if (authorizationService.isPasswordResetRequired(aUser.getUsername(), aUser.getUsertype())) {
LOG.debug("Password reset is required.");
System.out.println("Password reset is required");
final UsernamePasswordAuthenticationToken authRequest = reAssignUserWithOnlyResetPasswordRole(aUser,
request);
SecurityContextHolder.getContext().setAuthentication(authRequest);
SecurityContextHolder.getContext().getAuthentication();
System.out.println("User reassinged with only RESET_PASSWORD Authority, redirecting to resetPasswordPage");
response.sendRedirect(resetPasswordUrl);
//super.onAuthenticationSuccess(request, response, newAuthentication);
} else {
super.onAuthenticationSuccess(request, response, authentication);
}
}
If yes create another UsernamePasswordAuthenticationToken with same credentials as logged in user, but just assign him a single role "RESET_PASSWORD", so that he cannot access anything alse by hitting any other link/url.
private UsernamePasswordAuthenticationToken reAssignUserWithOnlyResetPasswordRole(final AugmentedUser aUser,
final HttpServletRequest request) {
final String username = aUser.getUsername();
final String password = aUser.getPassword();
final boolean isEnabled = aUser.isEnabled();
final boolean isAccountNonExpired = aUser.isAccountNonExpired();
final boolean isCredentialsNonExpired = aUser.isCredentialsNonExpired();
final boolean isAccountNonLocked = aUser.isAccountNonLocked();
LOG.debug("Re-assigning the user: " + username + " with only RESET PASSWORD AUTHORITY");
System.out.println("Re-assigning the user: " + username + "with only RESET PASSWORD AUTHORITY");
final Map<String, String> userAttributesMap = new HashMap<String, String>();
final AugmentedUser userWithResetPasswordRole = new AugmentedUser(username, password, aUser.getUsertype(),
isEnabled, isAccountNonExpired, isCredentialsNonExpired, isAccountNonLocked,
createResetPasswordGrantedAuhtority(), userAttributesMap);
final UsernamePasswordAuthenticationToken authenticationRequest = new UsernamePasswordAuthenticationToken(
userWithResetPasswordRole, userWithResetPasswordRole.getAuthorities());
//WebAuthenticationDetails are required for sessionId and ipAddress
final WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(request);
authenticationRequest.setDetails(webAuthenticationDetails);
return authenticationRequest;
}
Now I do see the new UsernamePasswordAuthenticationToken being created with just a RESET password role.
But looks like while doing redirect to the resetPasswordURl the spring filter do some checks and user is getting unauthenticated after I set my new UsernamePasswordAuthenticationToken .
Heres is the root cause that I see in the logs:
doAuthentication - Authentication attempt using
org.springframework.security.authentication.dao.DaoAuthenticationProvider
2012-02-15 22:47:20,931 [http-8081-6] DEBUG org.springframework.security.web.access.ExceptionTranslationFilter org.springframework.security.web.access.ExceptionTranslationFilter handleException - Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.BadCredentialsException: Bad credentials
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:67)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:139)
at org.springframework.security.authentication.ProviderManager.doAuthentication(ProviderManager.java:120)
Any comments where I am going wrong ?
It's difficult to say without more context from the log (the rest of the stacktrace plus the preceding log messages), but my best guess is that you're using the wrong constructor for UsernamePasswordAuthenticationToken. For historical reasons it takes Object arguments which doesn't help.
The two-arg version is supposed to take the username and credentials, and creates an unauthenticated token (for a request) which is not valid from the security interceptor's perspective. So I'm guessing the interceptor is trying to reauthenticate the token (should be obvious from the stacktrace where the call is coming from) and it is failing because the credentials parameter is actually a list of authorities rather than a password.
So use:
new UsernamePasswordAuthenticationToken(
userWithResetPasswordRole, null, userWithResetPasswordRole.getAuthorities());
instead.
Also, you will need to have a custom voter which handles RESET_PASSWORD as it will not be recognised by a default configuration. Alternatively use the ROLE_ prefix, i.e. ROLE_RESET_PASSWORD.

Resources