学习 Shiro(二):REST API Filter

Shiro Web 的设计目标仍是解决单体 Web 服务的安全问题,如配置 authc.loginUrl 登录页面,用户被禁止访问则重定向到登录页面。

显然,authc 过滤器的默认行为显然并不适合提供 REST API 的 Web 服务。

Shiro 简单易扩展,可以对 Shiro Web 进行定制化,从而更好支持 REST API。

目标如下所示:

  • 未登录,返回 HTTP 401 Unauthorized
  • 未授权,返回 HTTP 403 Forbidden

Filter

Shiro Web 使用了 Filter 对 HTTP 请求和响应进行过滤。javax.servlet.Filter接口定义如下:

public interface Filter {

    public void init(FilterConfig filterConfig) throws ServletException;

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;

    public void destroy();

}

在 Shiro Web 中 Filter 的实现是 org.apache.shiro.web.servlet.ShiroFilter 类。在 Shiro Spring 中 Filter 的实现是 org.apache.shiro.spring.web.SpringShiroFilter 类。

如果定义了两个 Filter,关键步骤如下:

  1. 请求目标 Servlet,Servlet Container 检测到存在两个 Filter 并创建了 FilterChain;
  2. 执行 FilterChain 中第一个 Filter 的 doFilter() 方法;
  3. 第一个 Filter 完成处理,调用 FilterChain 的 doFilter() 方法,从而执行 Filter Chain 中第二个 Filter 的 doFilter() 方法;
  4. 第二个 Filter 完成处理,调用 FilterChain 的 doFilter() 方法,从而执行 Servlet 的 service() 方法;
  5. Servlet 完成,第二个 Filter 调用 FilterChain 的 doFilter() 方法返回;
  6. 第二个 Filter 完成,第一个 Filter 调用 FilterChain 的 doFilter() 方法返回;
  7. 第一个 Filter 完成。

Servlet Filter

在 Shiro Web 中,路径规则:

/user/** = authc
/** = anon

会转化为一个 FilterChain,而 authc 和 anon 就是 Shiro Web 默认提供的 Filter 实现。

默认 Filter 表格:

名称 实现类
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter

REST API Filter

以 Spring Boot 集成 org.apache.shiro:shiro-spring-boot-web-starter 为例,演示如何自定义 Filter。

public class RestApiFilter extends FormAuthenticationFilter { // ①

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        Subject subject = getSubject(request, response); 

        if (subject.isAuthenticated()) { // ②
            HttpServletResponse httpResponse = WebUtils.toHttp(response);
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        } else { // ③
            HttpServletResponse httpResponse = WebUtils.toHttp(response);
            httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
        }

        return false;
    }

}

① 修改 authc 在访问禁止的行为,选择继承 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 并覆盖其 onAccessDenied() 方法;

② 如果没有认证,返回 HTTP 401 Unauthorized;

③ 如果没有鉴权,返回 HTTP 403 Forbidden。

添加自定义 Filter:

@Configuration
public class ShiroWebFilterConfiguration extends AbstractShiroWebFilterConfiguration {

    @Bean
    @Override
    protected ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = super.shiroFilterFactoryBean();

        bean.getFilters().put("api", new RestApiFilter());

        return bean;
    }

}

使用自定义 Filter:

/api/** = api

参考