shiro学习系列:shiro自定义filter过滤器

shiro学习系列:shiro自定义filter过滤器

自定义JwtFilter的hierarchy(层次体系)

在这里插入图片描述

上代码

package com.finn.springboot.common.config.shiro.filters;

import com.alibaba.fastjson.JSON;
import com.finn.springboot.common.api.vo.Result;
import com.finn.springboot.common.config.shiro.JwtToken;
import com.finn.springboot.common.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义Jwt过滤器,对token进行处理
 */
@Slf4j
public class JwtFilter extends BasicHttpAuthenticationFilter {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 执行完executeLogin
     * 前置处理
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 是否允许发送Cookie,默认Cookie不包括在CORS请求之中。设为true时,表示服务器允许Cookie包含在请求中。
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * 判断访问是否允许
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //登录请求不拦截
        try {
            return executeLogin(request, response);//认证是否成功
        } catch (Exception e) {
            //无需捕获
            throw new AuthenticationException("isAccessAllowed ===》 Token已过期或失效,请重新登录!", e);
        }
    }

    /**
     *执行登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response){
        AuthenticationToken token = createToken(request,response);
        // 提交给realm进行验证,如果错误他会抛出异常并被捕获
        try {
            Subject subject = getSubject(request, response);
            subject.login(token); // 交给 realm 去进行登录验证
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            /**
             * AuthenticationException 异常,并不会被全局异常ExceptionHandler捕获。filter抛出的错误不会拦截
             * 解释(也不大确定对不对):
             * Spring MVC is based on servlet, and spring security is based on filter,
             * filter is before servlet,
             * so the exception handler in your controller will not be executed, because it's already failed in filter.
             */
            return false; //会进入onAccessDenied() 在此方法内部进行返回结果处理

        }
    }

    /**
     *  如果Shiro Login认证成功,会执行这个方法。目前每次请求都会刷新token
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        String newToken = null;
        if(token instanceof JwtToken){
            JwtToken jwtToken = (JwtToken)token;
            newToken = JwtUtils.refreshTokenExpired(jwtToken.getPrincipal(),JwtUtils.SECRET);
        }
        if(newToken != null)
            httpResponse.setHeader(JwtUtils.AUTH_HEADER, newToken);
        return true;
    }

    /**
     * 如果调用shiro的login认证失败,会回调这个方法,这里我们什么都不做,因为逻辑放到了onAccessDenied()中。
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        log.error("Validate token fail, token:{}, error:{}", token.toString(), e.getMessage());
        return false;
    }

    /**
     * isAccessAllowed 访问不允许时会进入该方法
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        httpResponse.setHeader("Content-type",  "application/json;charset=UTF-8");
        Result<?> result = new Result<>();
        result.setSuccess(false);
        result.setCode(500);
        result.setMessage("Token已过期或失效,请重新登录!");
        httpResponse.getWriter().append(JSON.toJSONString(result));
        return false;
    }

    /**
     * 从 Header 里提取 JWT token
     * 返回null会直接抛异常
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String authorization = httpServletRequest.getHeader(JwtUtils.AUTH_HEADER);
        JwtToken token = new JwtToken(authorization);
        return token;
    }

}

执行顺序

1. preHandle()

前置处理,最先执行。适合做一些跨域请求处理。

2. isAccessAllowed()

访问是否允许。具体判断,调用executeLogin(request, response)

3. executeLogin(request, response)

执行登录。获取token,调用subject.login(token)
返回结果boolean,无异常会执行onLoginSuccese()方法,代表登录成功。
有异常一般有两种处理方式:

1.向上抛异常,最终由isAccessAllowed()处理异常
2.返回false,按照父类规则执行onAccessDenied()方法

注意:最终抛出的AuthenticationException异常,是无法使用全局异常处理器捕获的。百度到的解释(如有错误,请指正):

AuthenticationException 异常,并不会被全局异常ExceptionHandler捕获。filter抛出的错误不会拦截
Spring MVC is based on servlet, and spring security is based on filter,filter is before servlet,so the exception handler in your controller will not be executed, because it’s already failed in filter.

所以与其返回springboot默认错误信息格式,不如自定义返回信息格式,好于全局返回格式一致。所以可以使用第二种方法,return false,执行onAccessDenied()方法

4. onAccessDenied()

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletResponse httpResponse = WebUtils.toHttp(response);
        httpResponse.setHeader("Content-type",  "application/json;charset=UTF-8");
        Result<?> result = new Result<>();
        result.setSuccess(false);
        result.setCode(500);
        result.setMessage("Token已过期或失效,请重新登录!");
        httpResponse.getWriter().append(JSON.toJSONString(result));
        return false;
    }

5. onLoginFailure()

错误已经由onAccessDenied()方法处理了,这里就没必要再处理。

总结

最好是先不要自定义过滤器,自己debug一遍,就会对整体的流程熟悉很多。
有错误的地方,麻烦指正,看到会立即更改,且十分感谢!

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 在 Spring Boot 中使用 shiro 配置自定义过滤器需要以下几个步骤: 1. 引入 shiro-spring-boot-starter 依赖: ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> ``` 2. 创建自定义过滤器: ``` public class CustomFilter extends AccessControlFilter { @Override protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception { // 在这里实现自定义的过滤逻辑,返回 true 表示通过过滤器,返回 false 表示未通过过滤器 return true; } @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { // 如果 isAccessAllowed 返回 false,则会进入到这里,可以在这里处理未通过过滤器的情况 return false; } } ``` 3. 配置 shiroFilterChainDefinition: ``` @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); // 添加自定义过滤器,其中 key 是过滤器名称,value 是该过滤器对应的路径 chainDefinition.addPathDefinition("/custom/**", "custom"); return chainDefinition; } ``` 4. 配置自定义过滤器: ``` @Bean("custom") public CustomFilter customFilter() { return new CustomFilter(); } ``` 5. 配置 shiro 的注解支持: ``` @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } ``` 完成以上步骤后,就可以在 Spring Boot 中使用 shiro 配置自定义过滤器了。 ### 回答2: 在 Spring Boot 中使用 Shiro 配置自定义过滤器分为三个步骤。 第一步,创建自定义过滤器类。可以通过实现 ShiroFilter 接口来创建自定义过滤器。在自定义过滤器中需要实现过滤规则,并对请求进行相应的处理。 第二步,配置 Shiro 过滤器链。在 Spring Boot 的配置类中,通过创建 ShiroFilterFactoryBean 对象来配置 Shiro过滤器链。可以使用 ShiroFilterChainDefinitionMap 对象来配置过滤器链,然后将该对象设置给 ShiroFilterFactoryBean。 第三步,启用 Shiro 过滤器。在 Spring Boot 的配置类中,通过创建 DefaultFilterChainManager 对象,并将该对象设置给 ShiroFilterFactoryBean,启用自定义过滤器。 有了以上三步,就可以在 Spring Boot 中使用 Shiro 配置自定义过滤器了。可以通过在自定义过滤器中实现过滤规则来对请求进行拦截或处理,然后在 Shiro 过滤器链中配置该过滤器,最后启用该过滤器。这样就可以实现对请求的自定义过滤器处理。 值得注意的是,在使用 Shiro 进行自定义过滤器配置时,需要保证 Shiro 的配置文件中已经进行了相应的配置,包括认证和授权等相关配置。只有在正确配置的前提下,才能正确使用 Shiro 进行自定义过滤器的配置。 ### 回答3: 在Spring Boot中使用Shiro配置自定义过滤器通常需要以下几个步骤: 1. 引入Shiro和Spring Boot依赖。在pom.xml文件中添加Shiro和Spring Boot Starter依赖: ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-starter</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 2. 创建自定义过滤器类。可以通过实现`javax.servlet.Filter`接口或者继承`org.apache.shiro.web.servlet.OncePerRequestFilter`类来创建自定义过滤器。例如,创建一个名为`CustomFilter`的自定义过滤器类: ``` public class CustomFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 过滤器逻辑处理 // ... filterChain.doFilter(request, response); } } ``` 3. 在Shiro配置类中注册自定义过滤器。创建一个Shiro配置类,并使用`@Configuration`注解标记为配置类。通过`@Bean`注解将自定义过滤器注册到Shiro过滤器链中。例如,在配置类`ShiroConfig`中注册`CustomFilter`: ``` @Configuration public class ShiroConfig { @Bean public FilterRegistrationBean<CustomFilter> customFilterRegistrationBean() { FilterRegistrationBean<CustomFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new CustomFilter()); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 过滤器执行顺序 registrationBean.addUrlPatterns("/*"); // 过滤器路径 return registrationBean; } } ``` 4. 配置Shiro的过滤规则。在Shiro配置文件中,可以设置自定义过滤器的拦截规则。例如,在`shiro.ini`配置文件中,设置自定义过滤器的拦截规则: ``` [urls] /** = customFilter // 对所有请求都使用自定义过滤器 ``` 通过以上步骤,在Spring Boot中使用Shiro配置自定义过滤器就可以实现对特定请求的拦截和处理。在`CustomFilter`类的`doFilterInternal`方法中编写自定义过滤器逻辑,例如鉴权、权限验证等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值