package com.simuwang.shiro.core.jwt;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.simuwang.base.common.util.ServletUtils;
import com.simuwang.base.config.DaqProperties;
import com.smppw.common.pojo.ResultVo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.io.IOException;
import java.util.List;

/**
 * @author wangzaijun
 * @date 2024/9/9 16:33
 * @description 自定义的jwt token过滤器
 */
@Component
public class JwtFilter extends AccessControlFilter {
    private static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final DaqProperties properties;
    private final JwtContext jwtContext;

    public JwtFilter(DaqProperties properties, JwtContext jwtContext) {
        this.properties = properties;
        this.jwtContext = jwtContext;
    }

    /**
     * isAccessAllowed()判断是否携带了有效的JwtToken
     * onAccessDenied()是没有携带JwtToken的时候进行账号密码登录，登录成功允许访问，登录失败拒绝访问
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
        /*
         * 1. 返回true，shiro就直接允许访问url
         * 2. 返回false，shiro才会根据onAccessDenied的方法的返回值决定是否允许访问url
         *  这里先让它始终返回false来使用onAccessDenied()方法
         */
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        // 跟前端约定将jwtToken放在请求的Header的Authorization中，Authorization:token
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String requestURI = request.getRequestURI();
        List<String> whitelist = this.properties.getWhitelist();
        if (CollUtil.isNotEmpty(whitelist)) {
            for (String path : whitelist) {
                if (PATH_MATCHER.match(path, requestURI)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("接口{} 配置了白名单，不需要签名验证！", requestURI);
                    }
                    return true;
                }
            }
        }
        String token = request.getHeader(JwtContext.HEADER);
        if (StrUtil.isBlank(token)) {
            this.onLoginFail(servletResponse, requestURI, "请先登录后操作");
            return false;
        }
        Claims claims;
        try {
            claims = this.jwtContext.getClaimsByToken(token);
        } catch (ExpiredJwtException e) {
            this.onLoginFail(servletResponse, requestURI, "登录已过期，请重新登录");
            return false;
        }
        String username = claims.getSubject();
        String requestIp = ServletUtils.getIpAddr(request);
        String validToken = this.jwtContext.getUserCache(username, requestIp);
        if (!token.equals(validToken)) {
            this.onLoginFail(servletResponse, requestURI, "token非法");
            return false;
        }
        return true;
    }

    //登录失败要执行的方法
    private void onLoginFail(ServletResponse response, String url, String msg) throws IOException {
        this.logger.warn("接口{} 认证或授权错误：{}", url, msg);
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
        httpServletResponse.getWriter().print(JSONUtil.toJsonStr(ResultVo.fail(401, msg)));
    }
}
