spring集成shiro登陸流程(上)


上一篇已經分析了shiro的入口filter是SpringShiroFilter, 那么它的doFilter在哪兒呢?

我們看到它的直接父類AbstractShrioFilter繼承了OncePerRequestFilter類,該類是shiro內置的大部分filter的父類(抽像公共部分),在該類中定義了doFilter方法

OncePerRequestFilte

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
    //當前過濾器的名字 String alreadyFilteredAttributeName
= getAlreadyFilteredAttributeName();
     //如果該過濾器執行過,那么將不執行同一個名字的過濾器 直接執行過濾鏈中的下一個過濾器
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { filterChain.doFilter(request, response);
}
else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
        //如果當前過濾器設置了enabled屬性為false,則不執行,直接執行過濾鏈中的下一個過濾器
filterChain.doFilter(request, response); } else { //標志當前過濾器已經執行過 request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try {
          //1、 核心方法 doFilterInternal(request, response, filterChain); }
finally { //過濾鏈執行完畢后,清空request中的過濾鏈執行記錄 request.removeAttribute(alreadyFilteredAttributeName); } } }

由於我們是通過SpringShiroFilter攔截進來的那么會調用AbstractShrioFilter中的doFilterInternal

AbstractShrioFilter

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
        throws ServletException, IOException {
            
     //封裝容器的request和response為shiro自己的 其中在request中標識了當前不為servlet容器的session (在創建session時會用到servlet容器調用getSession()時 )
    final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
    final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
    //2、 創建subject(可以看出每次請求都會創建一個Subject對象)
    final Subject subject = createSubject(request, response);

    //執行過濾鏈
    subject.execute(new Callable() {
        public Object call() throws Exception {
            updateSessionLastAccessTime(request, response);  //修改session的最后活動時間
            executeChain(request, response, chain);  //執行過濾鏈
            return null;
        }
    });
}
//創建subject對象
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
    return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
//7、執行過濾鏈
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
        throws IOException, ServletException {
    //獲取當前請求對應的過濾鏈
    FilterChain chain = getExecutionChain(request, response, origChain);
    chain.doFilter(request, response);
}

WebSubject

//3、
public
Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) { //每次都創建subject上下文(subjectContext), 並設置securityManager對象 super(securityManager); //將request和response添加到subject上下文(subjectContext), 即上面創建的對象 setRequest(request); setResponse(response); } //4、創建 public WebSubject buildWebSubject() { Subject subject = super.buildSubject(); return (WebSubject) subject; }

Subject

 
         
//5、調用DefaultSecurityManager的createSubject方法
public Subject buildSubject() {
  
return this.securityManager.createSubject(this.subjectContext); }

 DefaultSecurityManager

// 6
public
Subject createSubject(SubjectContext subjectContext) { //web的subjectContext時,會重新創建一個新的,其他的(ini等),只是copy SubjectContext context = copy(subjectContext); //驗證是否subject上下文中有securityMangary對象,如果沒有創建一個 context = ensureSecurityManager(context); //將session放入subjectContext 該session會從cookie或者rul上帶的JSESSIONID(默認) 注意:第一次訪問項目來到這兒沒有session context = resolveSession(context); //校驗用戶登陸信息, 並放入context, 如果subject,session,和授權認證AuthenticationInfo中都沒有,將會從rememberMeManager(cookie)中獲取
   //注意:第一次訪問項目來到這兒沒有這些信息
context = resolvePrincipals(context); //創建一個WebDelegatingSubject對象 Subject subject = doCreateSubject(context); //保存當前認證的用戶信息(一般是用戶名)在session里,並標記當前用戶已經被認證過 為了下次remember使用 save(subject); return subject; }
// 從context中獲取session protected SubjectContext resolveSession(SubjectContext context) { Session session = resolveContextSession(context); if (session != null) { context.setSession(session); } return context; } protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException { //調用的下面子類DefaultWebSecurityManager的方法 SessionKey key = getSessionKey(context); if (key != null) { //調用 SessionsSecurityManager#getSession return getSession(key); } return null; }
//登陸 public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info; try { info = authenticate(token); } catch (AuthenticationException ae) { onFailedLogin(token, ae, subject); } Subject loggedIn = createSubject(token, info, subject); //登陸成功后 根據配置的"記住我" 保存認證信息 onSuccessfulLogin(token, info, loggedIn); return loggedIn; } protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) { rememberMeSuccessfulLogin(token, info, subject); } protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) { //獲取 rememberMeManager管理器 RememberMeManager rmm = getRememberMeManager(); rmm.onSuccessfulLogin(subject, token, info); }

 DefaultWebSecurityManager 

//創建sessionKey
@Override
protected SessionKey getSessionKey(SubjectContext context) { //從context中獲取sessonId和request,response if (WebUtils.isWeb(context)) { Serializable sessionId = context.getSessionId(); ServletRequest request = WebUtils.getRequest(context); ServletResponse response = WebUtils.getResponse(context); return new WebSessionKey(sessionId, request, response); } else { ... } } // 第一次調用時 getSession方法最后會調用到這兒來 返回null protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException { Serializable sessionId = getSessionId(sessionKey); if (sessionId == null) { return null; } Session s = retrieveSessionFromDataSource(sessionId); if (s == null) { //session ID was provided, meaning one is expected to be found, but we couldn't find one: String msg = "Could not find session with ID [" + sessionId + "]"; throw new UnknownSessionException(msg); } return s; }

 

現在開始執行請求路徑對應的過濾器

 由於過濾鏈中的過濾器也是OncePerRequestFilte的子類,繼續走OncePerRequestFilte#doFilter方法 然后會調用第一步doFilterInternal方法

  我們自定義的方法一般也是繼承了AdviceFilter過濾器

AdviceFilter

public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws ServletException, IOException {

        Exception exception = null;
        try {
       //8、執行前置方法
boolean continueChain = preHandle(request, response);        if (continueChain) { executeChain(request, response, chain); } postHandle(request, response); if (log.isTraceEnabled()) { log.trace("Successfully invoked postHandle method"); } } catch (Exception e) { exception = e; } finally { cleanup(request, response, exception); } }

 

AccessControlFilter

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
    //9、是登陸的rul或者已經認證過 否則重定向到登陸頁面
    return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

 

UserFilter

(這里以user過濾器為例,如果沒有認證過,直接重定向到登陸url)

該過濾器重寫了這兩個方法

//10、判斷是否認證過
protected
boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  //判斷當前請求的路徑是否為當前過濾器配置的登陸路徑(登陸不需要任何權限,返回true)
if (isLoginRequest(request, response)) { return true; } else {
    //判斷是否已經認證過 Subject subject
= getSubject(request, response); return subject.getPrincipal() != null; } } //11、返回false protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  //重定向到登陸rul saveRequestAndRedirectToLogin(request, response);
return false; }
//12、
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException { //調用webUtils的方法,將當前請求失敗的的信息保存起來(便於下次認證成功后直接重定向到該路徑) saveRequest(request); //重定向的時候會生成session(shrio的) redirectToLogin(request, response); }

 

 WebUtils

//保存
public
static void saveRequest(ServletRequest request) { Subject subject = SecurityUtils.getSubject(); //13、這里會創建一個 StoppingAwareProxiedSession AbstractNativeSessionManager#start是創建simpleSession並調用session監聽器
  //會將sessinID存在cookie中和sessionDao中(默認時ehcache緩存,可以自己實現redis等)(在DefaultSessionManager#create(Session session)方法中)
Session session = subject.getSession(); HttpServletRequest httpRequest = toHttp(request); //將當前目標路徑的請求信息保存起來 SavedRequest savedRequest = new SavedRequest(httpRequest); //存到session中,跳到登陸頁面后登陸成功后會重定向到此次失敗的路徑 session.setAttribute(SAVED_REQUEST_KEY, savedRequest); } //重定向到savedRequet保存的路徑 如果是直接訪問的登陸url,則直接重定向到當前過濾器配置的登陸成功url //成功后的重定向可是不生成session的 public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl) throws IOException { String successUrl = null; boolean contextRelative = true; //從session中獲取,並清空上一次失敗保存的信息 SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request); //上一次請求失敗的保存的對象 而且是get請求(這里一般是直接瀏覽器輸入的url) 如果是post請求過來的(一般是表單),直接返回目標路徑 if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) { successUrl = savedRequest.getRequestUrl(); contextRelative = false; }
  //第一次請求時,successUrl為null, 登陸成功后,有值(上一次失敗的url)
if (successUrl == null) { successUrl = fallbackUrl; }
  //15、發出重定向 WebUtils.issueRedirect(request, response, successUrl,
null, contextRelative); } public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative) throws IOException { issueRedirect(request, response, url, queryParams, contextRelative, true); } // 會把sessionID寫在rul上, 下面的內容就不帶大家看了 public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException { RedirectView view = new RedirectView(url, contextRelative, http10Compatible); view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response)); }

 

 SavedRequest

// SavedRequest的構造方法
public SavedRequest(HttpServletRequest request) {
  //當前請求的方式(get|post...)
this.method = request.getMethod();
  //當前請求的參數
this.queryString = request.getQueryString();
  //當前請求失敗的路徑
this.requestURI = request.getRequestURI(); }

 

RedirectView (拼接重定向的參數請求頭、url加sessionID,url編碼等操作都由這兒進入)

public final void renderMergedOutputModel(
            Map model, HttpServletRequest request, HttpServletResponse response) throws IOException {

        // Prepare name URL.
        StringBuilder targetUrl = new StringBuilder();
        if (this.contextRelative && getUrl().startsWith("/")) {
            targetUrl.append(request.getContextPath());
        }
        targetUrl.append(getUrl());
     //拼接請求參數
appendQueryProperties(targetUrl, model, this.encodingScheme); sendRedirect(request, response, targetUrl.toString(), this.http10Compatible); }

 

AbstractNativeSessionManager

//14、創建session時會調用
public
Session start(SessionContext context) { //創建simpleSession Session session = createSession(context); //重置session時間 applyGlobalSessionTimeout(session);
  //會將sessionID存到cookie onStart(session, context);
//調用session的Listner notifyStart(session); //Don't expose the EIS-tier Session object to the client-tier: return createExposedSession(session, context); }

 

那么此時一個沒有授權的請求就執行完畢,現在就來到了我們的登陸界面

  登陸使用authc過濾器

 

FormAuthenticationFilter

和上面的過程一樣,會判斷是否認證,如果沒有會執行onAccessDenied方法

// 這里需要該過濾器的 登陸url 和登陸所在的界面的url一樣
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    //條件: 配置的該過濾器的登陸路徑和請求路徑相同
    if (isLoginRequest(request, response)) {
        //1、HttpServletRequest  2、post請求
        if (isLoginSubmission(request, response)) {
            return executeLogin(request, response);
        } else {
            //登陸頁面的url 請求方式為get
            return true;
        }
    } else {
        //如果一個請求路徑配置的authc過濾器,然后沒有登陸直接調用,會走到這里
        //重定向到登陸頁面  會創建一個StoppingAwareProxiedSession類型的session 並把sessionId放在登陸頁面的url上
        saveRequestAndRedirectToLogin(request, response);
        return false;
    }
}
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { //調用 new UsernamePasswordToken(username, password, rememberMe, host); AuthenticationToken token = createToken(request, response); try { Subject subject = getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } } //登陸成功后 重定向到上一次重定向過來的路徑或者當前過濾器的登陸路徑 //可以重寫該方法登陸后直接跳到當前過濾器配置的url 而不是上一次失敗的url protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { //調用父類AuthenticationFilter的issueSuccessRedirect方法 issueSuccessRedirect(request, response); //重定向后,阻止過濾連調用 return false; }

 

AuthenticationFilter

protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
    //當前過濾器配置的登陸url
    WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}

DelegatingSubject

public void login(AuthenticationToken token) throws AuthenticationException {
    clearRunAsIdentitiesInternal();
    //委托給securiManager登陸
    Subject subject = securityManager.login(this, token);
    PrincipalCollection principals;
    String host = null;
    if (subject instanceof DelegatingSubject) {
        DelegatingSubject delegating = (DelegatingSubject) subject;
        //認證信息
        principals = delegating.principals;
        host = delegating.host;
    } else {
        principals = subject.getPrincipals();
    }
    this.principals = principals;
    //標記已經登陸過
    this.authenticated = true;
    if (token instanceof HostAuthenticationToken) {
        host = ((HostAuthenticationToken) token).getHost();
    }
    if (host != null) {
        this.host = host;
    }
    //獲取登陸時的session
    Session session = subject.getSession(false);
    if (session != null) {
        //執行new StoppingAwareProxiedSession(session, this);  登陸后的session封裝成StoppingAwareProxiedSession代理對象
        this.session = decorate(session);
    } else {
        this.session = null;
    }
}

 

AbstractRememberMeManager  處理 remeberme

public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
    //清空之前的認證信息
    forgetIdentity(subject);

    //如果是rememberMe類型的token
    if (isRememberMe(token)) {
        //記錄
        rememberIdentity(subject, token, info);
    }
}
public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
    //從認證后的信息中獲取
    PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
    rememberIdentity(subject, principals);
}
// 加密處理
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
    byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
    rememberSerializedIdentity(subject, bytes);
}
//使用CipherService類進行處理
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
    byte[] bytes = serialize(principals);
    if (getCipherService() != null) {
        bytes = encrypt(bytes);
    }
    return bytes;
}
// 返回加密后的認證信息
protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        // getEncryptionCipherKey()  獲取的是rememberMe cookie加密和解密的密鑰
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}
//加密
protected byte[] encrypt(byte[] serialized) {
    byte[] value = serialized;
    CipherService cipherService = getCipherService();
    if (cipherService != null) {
        ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
        value = byteSource.getBytes();
    }
    return value;
}

 

CookieRememberMeManager

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
    HttpServletRequest request = WebUtils.getHttpRequest(subject);
    HttpServletResponse response = WebUtils.getHttpResponse(subject);

    //base 64 encode it and store as a cookie:
    String base64 = Base64.encodeToString(serialized);
    //rememberMe的cookie模板  key為自定義的名字  我這兒是rememberMe
    Cookie template = getCookie();
    Cookie cookie = new SimpleCookie(template);
    cookie.setValue(base64);
    cookie.saveTo(request, response);
}

 

下面是remember的配置

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMe"/>
    <property name="httpOnly" value="true"/>
    <property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>

<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <!-- rememberMe cookie加密和解密的密鑰 建議每個項目都不一樣 默認AES算法 密鑰長度(128 256 512 位)-->
    <property name="cipherKey"
              value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
    <property name="cookie" ref="rememberMeCookie"/>
</bean>

 

由於篇幅原因,本節詳細介紹的是登陸需要驗證的請求跳轉到登陸界面的源碼解析

小結:

  1、當第一次請求失敗后,會重定向到當前過濾器的登陸界面,並創建一個session,將sessinID存在cookie,重定向的url,還會存放在sessionDao中(默認是ehcache, 可自定義)

  2、當請求的路徑為不用認證(anon等自定義preHandle返回true的路徑),也會由servlet容器調用shiroRequest的getSession方法創建一個session,保存位置同1

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM