Grails spring security break status 401 redirect to controller action to send error message
We are using spring-security-core: 2.0-RC4 , spring-security-rest: 1.4.0 plugin with grails 2.4.2. They both work fine. When the user enters invalid credentials the spring-security-rest: 1.4.0 plugin gives 401 which is configured in Config.groovy
grails.plugin.springsecurity.rest.login.failureStatusCode = 401
And here is a small snippet of the console output
rest.RestAuthenticationFilter - Actual URI is /api/login; endpoint URL is /api/login
rest.RestAuthenticationFilter - Applying authentication filter to this request
credentials.DefaultJsonPayloadCredentialsExtractor - Extracted credentials from JSON payload. Username: admin@asdasdmopi.com, password: [PROTECTED]
rest.RestAuthenticationFilter - Trying to authenticate the request
authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
dao.DaoAuthenticationProvider - User 'admin@something.com' not found
rest.RestAuthenticationFilter - Authentication failed: Bad credentials
rest.RestAuthenticationFailureHandler - Setting status code to 401
context.HttpSessionSecurityContextRepository - SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
context.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
Now there is no error message or response, just a 401 status being sent to the client. Now I am trying to submit a 401 error response.
Added the following line to UrlMappings.groovy
"401"(controller:'unauthorized',action:'sendErrorResponse')
Created by UnauthorizedController.groovy and added by sendErrorResponse () as follows
def sendErrorResponse() {
try{
int errorCode = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.errorCode
int status = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.status
String message = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.message
String extendedMessage = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.extendedMessage
String moreInfo = grailsApplication.config.customExceptions.account.fourZeroOne.loginNotAuthorized.moreInfo
throw new AccountException(status,errorCode,message,extendedMessage,moreInfo)
}catch(AccountException e){
log.error e.errorResponse()
response.setStatus(e.errorResponse().status)
render e.errorResponse()
}
}
My thinking was that on 401 the controller will be called and the method will give an error response, but it doesn't work.
Is my approach correct?
Any other best practice or idea for implementing this?
Any pointers in the right direction are appreciated.
Thanks a ton.
source to share
You need to override the grails.plugin.springsecurity.rest.RestAuthenticationFailureHandler
component grails.plugin.springsecurity.rest.RestAuthenticationFailureHandler
with your own customized version.
It could be something like this:
@Slf4j
@CompileStatic
class CustomRestAuthenticationFailureHandler implements AuthenticationFailureHandler {
/**
* Configurable status code, by default: conf.rest.login.failureStatusCode?:HttpServletResponse.SC_FORBIDDEN
*/
Integer statusCode
MessageSource messageSource
/**
* Called when an authentication attempt fails.
* @param request the request during which the authentication attempt occurred.
* @param response the response.
* @param exception the exception which was thrown to reject the authentication request.
*/
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(statusCode)
response.addHeader('WWW-Authenticate', Holders.config.get("grails.plugin.springsecurity.rest.token.validation.headerName").toString())
def errorMessage
if (exception instanceof AccountExpiredException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.expired", null as Object[], LocaleContextHolder.getLocale())
} else if (exception instanceof CredentialsExpiredException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.passwordExpired", null as Object[], LocaleContextHolder.getLocale())
} else if (exception instanceof DisabledException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.disabled", null as Object[], LocaleContextHolder.getLocale())
} else if (exception instanceof LockedException) {
errorMessage = messageSource.getMessage("springSecurity.errors.login.locked", null as Object[], LocaleContextHolder.getLocale())
} else {
errorMessage = messageSource.getMessage("springSecurity.errors.login.fail", null as Object[], LocaleContextHolder.getLocale())
}
PrintWriter out = response.getWriter()
response.setContentType("aplication/json")
response.setCharacterEncoding("UTF-8");
out.print(new JsonBuilder([message: errorMessage]).toString());
out.flush();
}
}
And in your resources you should have a game
restAuthenticationFailureHandler(CustomRestAuthenticationFailureHandler) {
statusCode = HttpServletResponse.SC_UNAUTHORIZED
messageSource = ref("messageSource")
}
source to share