简体   繁体   中英

grails spring security rest status 401 redirect to controller's action to throw custom error message

We are using spring-security-core:2.0-RC4 , spring-security-rest:1.4.0 plugin with grails 2.4.2. Both of them are working fine. When user enters invalid credentials, 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 the small snippet of 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 status 401 is send to client. Now I am trying to send a error response when there is 401 status.

Added following line in UrlMappings.groovy

"401"(controller:'unauthorized',action:'sendErrorResponse')

Created UnauthorizedController.groovy and added 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 render error response, but It doesn't work.

Is my Approach right?

Any other Best practice or idea to implement this?

Any pointers in right direction are appreciated.

Thanks a ton.

You need to override grails.plugin.springsecurity.rest.RestAuthenticationFailureHandler bean with your own customized version.

It can 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.groovy you should have

restAuthenticationFailureHandler(CustomRestAuthenticationFailureHandler) {
    statusCode = HttpServletResponse.SC_UNAUTHORIZED
    messageSource = ref("messageSource")
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM