1. 程式人生 > >五.shiro,表單攔截器FormAuthenticationFilter

五.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;
    }