grails spring security rest status 401重定向到控制器的动作以抛出自定义错误消息

Posted

技术标签:

【中文标题】grails spring security rest status 401重定向到控制器的动作以抛出自定义错误消息【英文标题】:grails spring security rest status 401 redirect to controller's action to throw custom error message 【发布时间】:2014-12-26 10:46:41 【问题描述】:

我们在 grails 2.4.2 中使用spring-security-core:2.0-RC4、spring-security-rest:1.4.0 plugin。他们俩都工作正常。当用户输入无效凭据时,spring-security-rest:1.4.0 插件会给出 401,这是在 Config.groovy 中配置的

grails.plugin.springsecurity.rest.login.failureStatusCode =  401

这里是控制台输出的小sn-p

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

现在没有错误消息或响应,只是将状态 401 发送给客户端。现在我尝试在出现 401 状态时发送错误响应。

在 UrlMappings.groovy 中添加以下行

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

创建了 UnauthorizedController.groovy 并添加了 sendErrorResponse() 如下

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

我的想法是在 401 上会调用控制器并且方法会呈现错误响应,但它不起作用。

我的方法对吗?

任何其他最佳实践或想法来实现这一点?

感谢任何正确方向的指针。

非常感谢。

【问题讨论】:

【参考方案1】:

您需要用您自己的自定义版本覆盖grails.plugin.springsecurity.rest.RestAuthenticationFailureHandler bean。

可能是这样的:

@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();
    

在你的 resources.groovy 中你应该有

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

【讨论】:

以上是关于grails spring security rest status 401重定向到控制器的动作以抛出自定义错误消息的主要内容,如果未能解决你的问题,请参考以下文章

Grails - grails-spring-security-rest - 无法从 application.yml 加载 jwt 机密

grails-spring-security-rest 插件和悲观锁定

Grails + spring-security-core:用户登录后如何分配角色?

Grails Spring Security注释问题

Grails Spring Security 插件网址

Grails spring-security-oauth-google:如何设置