package com.project.shiro.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * 自定义表单认证过滤器
 *
 * @author wyy
 * @date 2019/07/26
 */
public class AuthenticationFilter extends FormAuthenticationFilter {
    private static final Logger log = LoggerFactory.getLogger(AuthenticationFilter.class);

    //加密密码参数
    private static final String DEFAULT_EN_PASSWORD_PARAM = "enPassword";

    //默认的登录名称
    private static final String DEFAULT_USERNAME_PARAM = "loginName";

    //默认验证码ID参数
    private static final String DEFAULT_CAPTCHA_ID_PARAM = "captchaId";

    //默认验证码参数
    private static final String DEFAULT_CAPTCHA_PARAM = "captcha";

    private String captchaIdParam = DEFAULT_CAPTCHA_ID_PARAM;

    private String captchaParam = DEFAULT_CAPTCHA_PARAM;

    private String usernameParam = DEFAULT_USERNAME_PARAM;

    private String enPasswordParam = DEFAULT_EN_PASSWORD_PARAM;

    /**
     * 创建token
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
        String loginName = getUsername(request);
        String password = getPassword(request);
        boolean isRemeberMe = isRememberMe(request);
        String ip = getHost(request);
        return new com.project.shiro.util.AuthenticationToken(loginName, password, isRemeberMe, ip, "", "");
    }

    /**
     * 登录拒绝；增加Ajax异步处理
     *
     * @param servletRequest  请求对象
     * @param servletResponse 响应对象
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        // 判断是否为ajax异步请求
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 判断是否为登录请求
        if (this.isLoginRequest(servletRequest, response)) {
            if (this.isLoginSubmission(servletRequest, response)) {

                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                boolean b = executeLogin(servletRequest, response);
                return b;
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the Authentication url [" + this.getLoginUrl() + "]");
            }

            // 异步请求报错
            if (isAjaxReq(request, response)) {
                response.setContentType("application/json");
                response.setCharacterEncoding("UTF-8");
                PrintWriter out = response.getWriter();
                JSONObject json = new JSONObject();
                json.put("result", "fail");
                json.put("msg", "未登录");
                out.println(json);
                out.flush();
                out.close();
                return false;
            }

//             如果同步请求继续执行基类方法（当为同步方法的时候，基类会直接跳转登录页面）
            return super.onAccessDenied(request, response);
        }

    }

    /**
     * 重写登录成功的方法；如果为异步请求，直接返回成功响应
     *
     * @param token
     * @param subject
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        // 如果为异步请求，登录成功后，直接返回数据，前台跳转登录后的页面处理
        if (isAjaxReq(servletRequest, servletResponse)) {
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            PrintWriter out = response.getWriter();
            JSONObject json = new JSONObject();
            json.put("result", "success");
            json.put("msg", "登录成功");
            out.write(json.toJSONString());
            out.flush();
            out.close();
            return true;
        }
        return super.onLoginSuccess(token, subject, servletRequest, servletResponse);
    }

    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //Always return true if the request's method is OPTIONSif (request instanceof HttpServletRequest) {
        if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
            return true;
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }

    /**
     * 重写登录失败的方法；如果为异步请求，直接返回失败响应
     *
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        // 如果为异步登录，直接返回错误结果
        if (isAjaxReq(request, response)) {
            PrintWriter out = null;
            try {
                response = (HttpServletResponse) response;
                response.setContentType("application/json");
                response.setCharacterEncoding("UTF-8");
                out = response.getWriter();
                JSONObject json = new JSONObject();
                if (e.equals("org.apache.shiro.authc.pam.UnsupportedTokenException")) {
                    String message = "验证码错误!";
                    json.put("result", "fail");
                    json.put("msg", message);
                } else if (e.equals("org.apache.shiro.authc.UnknownAccountException")) {
                    String message = "此账号不存在!";
                    json.put("result", "fail");
                    json.put("msg", message);
                } else if (e.equals("org.apache.shiro.authc.DisabledAccountException")) {
                    String message = "此账号已被禁用!";
                    json.put("result", "fail");
                    json.put("msg", message);
                } else if (e.equals("org.apache.shiro.authc.LockedAccountException")) {
                    String message = "此账号已被锁定";
                    json.put("result", "fail");
                    json.put("msg", message);
                } else if (e.equals("org.apache.shiro.authc.IncorrectCredentialsException")) {
                    String message = "密码错误";
                    json.put("result", "fail");
                    json.put("msg", message);
                } else if (e.equals("org.apache.shiro.authc.AuthenticationException")) {
                    String message = "账号认证失败!";
                    json.put("result", "fail");
                    json.put("msg", message);
                }
                out.write(json.toJSONString());
                out.flush();
                out.close();
                return false;
            } catch (IOException ex) {
                ex.printStackTrace();
                log.error("shiro认证失败");
            }

        }

        // 同步请求走基类
        return super.onLoginFailure(token, e, request, response);
    }


    /**
     * 获取密码
     *
     * @param servletRequest
     * @return
     */
    @Override
    protected String getPassword(ServletRequest servletRequest) {
        String parameter = servletRequest.getParameter(enPasswordParam);
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String enPasswor = request.getParameter(enPasswordParam);
        String password = enPasswor;
        return password;
    }

    /**
     * 判断是否为Ajax请求
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     */
    public boolean isAjaxReq(ServletRequest servletRequest, ServletResponse servletResponse) {
        boolean isAjaxReq = false;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String requestType = request.getHeader("X-Requested-With");
        if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) {
            isAjaxReq = true;
        }
        return isAjaxReq;
    }

    public String getEnPasswordParam() {
        return enPasswordParam;
    }

    public void setEnPasswordParam(String enPasswordParam) {
        this.enPasswordParam = enPasswordParam;
    }

    public String getUsernameParam() {
        return usernameParam;
    }

    public String getCaptchaIdParam() {
        return captchaIdParam;
    }

    public void setCaptchaIdParam(String captchaIdParam) {
        this.captchaIdParam = captchaIdParam;
    }

    public String getCaptchaParam() {
        return captchaParam;
    }

    public void setCaptchaParam(String captchaParam) {
        this.captchaParam = captchaParam;
    }

}
