shiro与spring security用自定义异常处理401错误

  • Post category:http

Shiro 与 Spring Security 自定义异常处理 401 错误

前言

在 Web 应用中,访问某些需要授权的资源时,若用户未登录或无权限,通常会得到 401 Unauthorized 的响应码。这是常见的情况,也很容易处理。但当使用 Shiro 或 Spring Security 这样的安全框架时,由于框架自身对 401 状态码的处理机制,我们需要进行自定义的异常处理才能对其进行精细化控制。

本文将分别讲解在 Shiro 和 Spring Security 中如何自定义异常处理 401 错误,以及具体的实现方法和示例说明。

Shiro 自定义异常处理

1. 异常处理方法

Shiro 中处理 API 接口访问时,常用的方式是实现一个 AuthenticationFilter,并将其添加到 Shiro 的 FilterChain 中。在 AuthenticationFilter 中,我们可以重写 onAccessDenied 方法,并在该方法中手动抛出 AuthenticationException 异常(也就是 401 的异常类型),以触发自定义异常处理。

(1)实现 AuthenticationFilter
public class ApiAuthenticationFilter extends AuthenticatedFilter {
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        return false;
    }

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
            throws Exception {
        throw new AuthenticationException("认证失败");
    }
}

上述代码中,我们新建了一个 ApiAuthenticationFilter 类,继承了 Shiro 的 AuthenticatedFilter。重写了 isAccessAllowed 和 onAccessDenied 两个方法。其中,isAccessAllowed 方法用于判断用户是否已经登录和是否有权限访问请求的资源。这里我们直接返回 false,使得 onAccessDenied 方法会被调用。

onAccessDenied 方法中,我们手动抛出了一个 AuthenticationException 异常,以触发自定义异常处理。这里直接抛出的异常信息为 “认证失败”,当然也可以根据具体业务需求进行定制。

(2)异常处理方法

接下来,我们需要在自己的项目中实现 ExceptionHandler 类。在该类中,我们需要添加一个 @ExceptionHandler(AuthenticationException.class) 注解,用来处理 AuthenticationException 异常类型的抛出。

@ControllerAdvice
public class ExceptionHandler {
    @org.springframework.web.bind.annotation.ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
        return ResponseEntity.status(401).body(e.getMessage());
    }
}

上述代码中,我们新建了一个 ExceptionHandler 类,并添加了 @ControllerAdvice 注解。然后,在该类中定义了一个 handleAuthenticationException 方法,并添加了 @ExceptionHandler(AuthenticationException.class) 注解,用来指定处理 AuthenticationException 异常类型。该方法中,我们调用了 ResponseEntity.status 方法来设置响应状态码为 401,并使用 e.getMessage() 方法获取即将返回的异常信息作为返回体。

2. 示例说明

// ShiroConfig.java 配置文件中添加该 filter
@Bean
public FilterRegistrationBean registration(ApiAuthenticationFilter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}

对于上述代码,我们通过 ShiroConfig.java 配置文件将 ApiAuthenticationFilter 过滤器添加到 FilterChain 中。这里需要注意:registration.setEnabled(false) 中的 false 参数用来防止 Spring Boot 通过 “servlet container initializer” 将 Filter 加载成 Servlet 容器,导致过滤器失效。

Spring Security 自定义异常处理

1. 异常处理方法

Spring Security 中也存在着和 Shiro 相似的问题。当我们想要通过自定义的异常处理来对 401 状态码进行处理时,我们需要实现一个 AuthenticationEntryPoint,并在该中手动抛出一个 AuthenticationException 异常,以触发自定义异常处理。

(1)实现 AuthenticationEntryPoint
public class ApiAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        throw new AuthenticationException("认证失败");
    }
}

上述代码中,我们新建了一个 ApiAuthenticationEntryPoint 类,实现了 Spring Security 的 AuthenticationEntryPoint 接口。这个接口中只有一种方法commence,该方法中,我们同样手动抛出了一个 AuthenticationException 异常,以触发自定义异常处理。代码中的异常信息同样为 “认证失败”,当然也可以根据具体业务需求进行定制。

(2)异常处理方法

接下来,我们需要在自己的项目中实现 ExceptionHandler 类。在该类中,我们需要添加一个 @ExceptionHandler(AuthenticationException.class) 注解,用来处理 AuthenticationException 异常类型的抛出。

@ControllerAdvice
public class ExceptionHandler {
    @org.springframework.web.bind.annotation.ExceptionHandler(AuthenticationException.class)
    public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) {
        return ResponseEntity.status(401).body(e.getMessage());
    }
}

上述代码中,我们新建了一个 ExceptionHandler 类,并添加了 @ControllerAdvice 注解。然后,在该类中定义了一个 handleAuthenticationException 方法,并添加了 @ExceptionHandler(AuthenticationException.class) 注解,用来指定处理 AuthenticationException 异常类型。该方法中,我们调用了 ResponseEntity.status 方法来设置响应状态码为 401,并使用 e.getMessage() 方法获取即将返回的异常信息作为返回体。

2. 示例说明

// SecurityConfig.java 配置文件中添加该 entry point
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .exceptionHandling()
            .authenticationEntryPoint(apiAuthenticationEntryPoint())
            .and()
            // ... 其他配置
}

@Bean
public AuthenticationEntryPoint apiAuthenticationEntryPoint() {
    return new ApiAuthenticationEntryPoint();
}

对于上述代码,我们通过 SecurityConfig.java 配置文件将 ApiAuthenticationEntryPoint 添加到 exceptionHandling 中,具体的添加方法是通过调用 authenticationEntryPoint 方法,并将该方法返回的 Bean 对象作为参数传入。这里需要注意:apiAuthenticationEntryPoint 需要通过 @Bean 注册为 Bean。

总结

在本文中,我们分别讲解了在 Shiro 和 Spring Security 中如何自定义异常处理 401 错误,并且给出了相应的实现方法和示例说明。对于我们想要进行精细化控制的 Web 应用,自定义异常处理可以起到十分重要的作用。