shiro表单拦截器FormAuthenticationFilter如何认证,登录成功后如何继续访问原请求

shiro有几种默认的拦截器,authc,anno,roles,user等 authc就是FormAuthenticationFilter的实例

ShiroFilterFactoryBean的配置:

private Map<String, Filter> filters;  <取名,拦截器地址>,可以自定义拦截器放在这 

private Map<String, String> filterChainDefinitionMap; <url,拦截器名>哪些路径会被此拦截器拦截到

public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager); 
        
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/index");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// private Map<String, Filter> filters;  shiro有一些默认的拦截器 比如auth,它就是FormAuthenticationFilter表单拦截器  <取名,拦截器地址>,可以自定义拦截器放在这 
      //private Map<String, String> filterChainDefinitionMap; <url,拦截器名>哪些路径会被此拦截器拦截到
        
        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/ajaxLogin", "anon");
        filterChainDefinitionMap.put("/focus/userlogin", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html#", "anon");
        
        // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问;user:remember me的可以访问-->
        filterChainDefinitionMap.put("/fine", "user");  
        filterChainDefinitionMap.put("/focus/**", "authc");   
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
    }

  
一。请求被authc拦截,如果状态未登录,就会被跳到登录页面,登录成功后,会继续原请求页面,除非原请求就是successurl,才去successurl

 

PathMatchingFilter是开涛讲过得,匹配某url 拦截进行处理的拦截器,里面有匹配url方法,preHandle,onPreHandle等方法。

AccessControlFilter重写了onPreHandle

pathsMatch:该方法用于path与请求路径进行匹配的方法;如果匹配返回true;

onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
    }


isAccessAllowed:请求是否被允许访问,此方法在AccessControlFilter是抽象方法,被AuthenticatingFilter重写

@Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return super.isAccessAllowed(request, response, mappedValue) ||
                (!isLoginRequest(request, response) && isPermissive(mappedValue));
    }


分别点进去看,调用父类AuthenticationFilter的方法判断 当前用户是否已认证过 

 protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        Subject subject = getSubject(request, response);
        return subject.isAuthenticated();
    }


并且判断请求url是不是配置的loginurl

 protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
        return pathsMatch(getLoginUrl(), request);
    }


回头再看 onPrehandle,如果是一个没认证过的请求,isAccessAlowed肯定是false,执行onAccessDefined方法,开涛也说过,此方法是请求未通过认证时执行的方法,按逻辑推理,请求未认证就跳转到loginurl在这里实现

这个方法在authc里,onAccessDenied(用到这点:子类继承父类,重写了A方法,父类有个B方法,子类对象调用B方法,执行的是子类的A方法)

如果请求是登录请求,发起登录,如果不是就保存原请求,并重定向到登录url ,saveRequestAndRedirectToLogin

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                return executeLogin(request, response);
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Login page view.");
                }
                //allow them to see the login page ;)
                return true;
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                        "Authentication url [" + getLoginUrl() + "]");
            }
 
            saveRequestAndRedirectToLogin(request, response);
            return false;
        }
    }


看看saveRequestAndRedirectToLogin

 protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        redirectToLogin(request, response);
    }


saveRequest

 protected void saveRequest(ServletRequest request) {
        WebUtils.saveRequest(request);
    }

webUtils.saveRequest:其实是放到了session里,key是SAVED_REQUEST_KEY

public static void saveRequest(ServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        HttpServletRequest httpRequest = toHttp(request);
        SavedRequest savedRequest = new SavedRequest(httpRequest);
        session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
    }


那登录成功后怎么跳转到原请求页面的?肯定是从session取出原请求,还是看authc的onAcessDefined方法

如果请求是loginurl并允许,就发起登录

if (isLoginRequest(request, response)) {
            if (isLoginSubmission(request, response)) {
                if (log.isTraceEnabled()) {
                    log.trace("Login submission detected.  Attempting to execute login.");
                }
                return executeLogin(request, response);
            }


executeLogin:同样是 subject.login(token)

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

onLoginSuccess:authc重写此方法

protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
        issueSuccessRedirect(request, response);
        //we handled the success redirect directly, prevent the chain from continuing:
        return false;
    }
 protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
        WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
    }

找到session的的原请求,发起请求

public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
            throws IOException {
        String successUrl = null;
        boolean contextRelative = true;
        SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
        if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
            successUrl = savedRequest.getRequestUrl();
            contextRelative = false;
        }
 
        if (successUrl == null) {
            successUrl = fallbackUrl;
        }
 
        if (successUrl == null) {
            throw new IllegalStateException("Success URL not available via saved request or via the " +
                    "successUrlFallback method parameter. One of these must be non-null for " +
                    "issueSuccessRedirect() to work.");
        }
 
        WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
    }

从session取出了那个key 并清空

public static SavedRequest getAndClearSavedRequest(ServletRequest request) {
        SavedRequest savedRequest = getSavedRequest(request);
        if (savedRequest != null) {
            Subject subject = SecurityUtils.getSubject();
            Session session = subject.getSession();
            session.removeAttribute(SAVED_REQUEST_KEY);
        }
        return savedRequest;
    }


到这就大致明白,authc 完成 认证通过后 转发到原请求页面的流程

二。SecurityUtils.getSubject

前面的AuthenticationFilter或其他过滤器也有不少是直接通过这个方法得到subject,然后通过subject.isAuthenticated 包括check权限或角色,

就会想到是不是subject也保存到session里,登录成功一次后,以后的请求是如何判断 用户已认证通过呢?确实是这样,shiro会在浏览器写下coolie JSESSIONID

通过线程副本,

 public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值