Spring Boot Security OAuth - handle InternalAuthenticationServiceException - spring

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.

Related

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

#ControllerAdvice does not catch MySQLIntegrityConstraintViolationException

I am making a Spring Boot app with MySQL database attached to it.
I have already marked username as unique in the user-table, and the DB throws a MySQLIntegrityConstraintViolationException when i try to add a new user with an existing username.
Is there a way to make the ControllerAdvice handle this exception ? I tried making the handler like this :
#ExceptionHandler(MySQLIntegrityConstraintViolationException.class)
#ResponseStatus(HttpStatus.CONFLICT)
#ResponseBody
public MyBadInputResponse databaseIntegrityViolationDuplicate(MySQLIntegrityConstraintViolationException ex) {
MyBadInputResponse bir = new MyBadInputResponse("The request value already exists in the database",
ex.getLocalizedMessage());
logger.error(bir.toString());
return bir;
}
But it doesn't work.

Spring http status code - java.lang.IllegalArgumentException: No matching constant

I'm using The spring rest-template for calling the rest URL , I get a response from the server but the http-status code is invalid and the Spring throws , java.lang.IllegalArgumentException: No matching constant . Due to this exception the application is failing , this looks like a bug in the Spring code . Since the http status code received is not in the list spring framework is looking forit failed . Is there a Spring way to handle it ?
Spring seems to use the standard status code in their enum. You can find the status codes here: org.springframework.http.HttpStatus.
Probably the API you're querying is not returning a standard HTTP Status code. Your best bet is to create a custom error handler, like this:
var r = new RestTemplate();
r.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getRawStatusCode() != 550;
}
#Override
public void handleError(ClientHttpResponse response) {
// Do nothing?
}
});
var response = r.exchange("https://httpbin.org/status/550", HttpMethod.GET, null, String.class);
System.out.println(response.getStatusCodeValue());
What we're saying is basically if the status code returned is 550 (not a standard code), we don't want to do anything about it.
Another option you have is, of course, to catch the exception and do something about it.
try {
// Call the API here
} catch (IllegalArgumentException e) {
// Do something about it here...
}

Spring controller, custom response via HttpServletResponse

I'm trying to write a custom response using HttpServletResponse.getOutputStream() and HttpServletResponse.setStatus(int).
But anything that is an status different from 200 doesn't consideres the response body that I wrote.
I have 2 web applications running on different ports, the application "A" must request data from application "B". For this I created a controller to tunnel all requests on application "A" to application "B".
Example:
#RequestMapping("/tunnel/**")
public void exchange(HttpServletRequest request, HttpServletResponse response) throws IOException {
// my service tunnel the request to another server
// and the response of the server must be replied
ResponseDescriptor tunnelResponse = tunnelService.request(request);
response.setStatus(tunnelResponse.getStatus());
// if the status was different them 200, the next line will not work
response.getOutputStream().write(tunnelResponse.getResponseBodyAsByteArray());
}
Note, I need to response from application A the exact response that come from application B.
You need to catch HttpStatusCodeException to get responses other than 200.
try {
ResponseDescriptor tunnelResponse = tunnelService.request(request);
response.setStatus(tunnelResponse.getStatus());
response.getOutputStream().write(tunnelResponse.getResponseBodyAsByteArray());
} catch (HttpStatusCodeException e) {
response.setStatus(e.getStatusCode().value());
response.getOutputStream().write(e.getResponseBodyAsByteArray());
}
Solved!
I created a #ExceptionHandler and a custom exception TunnelException extends RuntimeException.
So, on my exchange(...) method, I catch RestClientResponseException and throw my own exception encapsulating (in the exception) the Headers, HttpStatus and the ResponseBody byte array.
This is the exception handler:
#ExceptionHandler(TunnelException.class)
public ResponseEntity<?> handleTunnelException(TunnelException ex) throws IOException {
return ResponseEntity
.status(ex.getStatus())
.contentType(ex.getHeaders().getContentType())
.body(ex.getBody());
}

Session management in gwt

I am using GWT for my client side application. However, I am not sure how I can handle session management. The GWT application resides on one page, all server calls are done via AJAX. If a session expires on the server. let's assume the user didn't close the browser, and sending some request to server using RPC, how could my server notify the application that the session has expired and that the client side portion should show the login screen again?My sample code :
ContactDataServiceAsync contactDataService = GWT
.create(ContactDataService.class);
((ServiceDefTarget) contactDataService).setServiceEntryPoint(GWT
.getModuleBaseURL()
+ "contactDatas");
contactDataService.getContact(2,
new AsyncCallback<ContactData>() {
public void onFailure(Throwable caught) {
//code to show error if problem in connection or redirect to login page
}
public void onSuccess(ContactData result) {
displayContact(result);
}
});
If session expires only it has to show login screen, otherwise it wants to show some error using Window.alert().
How to do this and what are all the codes needed in server side and client side?
You could have the server throw an AuthenticationException to the client in case the user has been logged out.
This will be catched in the callbacks onFailure method, which then can redirect the user to the login-page.
Edit:
AuthenticationException is not a standard exception of course, i was just making an example. It might be best to stick with the standard exceptions.
To try if you caught an specific exception you could use the instanceof operator
public void onFailure(Throwable e) {
if(e instanceof AuthenticationException) {
redirecttoLogin();
}
else {
showError(),
}
}
This does not directly apply to those using RPC, but for those of you who are not using RPC, you should send a HTTP 401 from the server. Then you can check that status code in your RequestBuilder callback.
Client: All Callbacks extend a Abstract Callback where you implement the onFailur()
public abstract class AbstrCallback<T> implements AsyncCallback<T> {
#Override
public void onFailure(Throwable caught) {
//SessionData Expired Redirect
if (caught.getMessage().equals("500 " + YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN)) {
Window.Location.assign(ConfigStatic.LOGIN_PAGE);
}
// else{}: Other Error, if you want you could log it on the client
}
}
Server: All your ServiceImplementations extend AbstractServicesImpl where you have access to your SessionData. Override onBeforeRequestDeserialized(String serializedRequest) and check the SessionData there. If the SessionData has expire then write a spacific error message to the client. This error message is getting checkt in your AbstrCallback and redirect to the Login Page.
public abstract class AbstractServicesImpl extends RemoteServiceServlet {
protected ServerSessionData sessionData;
#Override
protected void onBeforeRequestDeserialized(String serializedRequest) {
sessionData = getYourSessionDataHere()
if (this.sessionData == null){
// Write error to the client, just copy paste
this.getThreadLocalResponse().reset();
ServletContext servletContext = this.getServletContext();
HttpServletResponse response = this.getThreadLocalResponse();
try {
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
try {
response.getOutputStream().write(
ConfigStatic.ERROR_MESSAGE_NOT_LOGGED_IN.getBytes("UTF-8"));
response.flushBuffer();
} catch (IllegalStateException e) {
// Handle the (unexpected) case where getWriter() was previously used
response.getWriter().write(YourConfig.ERROR_MESSAGE_NOT_LOGGED_IN);
response.flushBuffer();
}
} catch (IOException ex) {
servletContext.log(
"respondWithUnexpectedFailure failed while sending the previous failure to the client",
ex);
}
//Throw Exception to stop the execution of the Servlet
throw new NullPointerException();
}
}
}
In Addition you can also Override doUnexpectedFailure(Throwable t) to avoid logging the thrown NullPointerException.
#Override
protected void doUnexpectedFailure(Throwable t) {
if (this.sessionData != null) {
super.doUnexpectedFailure(t);
}
}

Resources