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

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

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

shiro学习系列:shiro自定义filter过滤器_第1张图片

上代码

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一遍,就会对整体的流程熟悉很多。
有错误的地方,麻烦指正,看到会立即更改,且十分感谢!

你可能感兴趣的:(shiro,shiro,filter,过滤器)