自學-Shiro的身份認證-05

   學習了前幾節,大家可能只是對Shiro有個大概的瞭解,其實,Shiro的重點及難點都在後面的博客中,接下來的這節我們來探討一下身份認證.

我們可以一起來看下身份認證流程,有個大概的思緒,在來一起寫代碼進行實現。

身份認證流程:


 

流程步驟(借鑑英文文檔翻譯):

1.首先調用Subject.login(token)進行登錄,其會自動委託給Security Manager,調用之前必須通過SecurityUtils. setSecurityManager()設置;

2.SecurityManager負責真正的身份驗證邏輯;它會委託給Authenticator進行身份驗證;

3.Authenticator纔是真正的身份驗證者,Shiro API中核心的身份認證入口點,此處可以自定義插入自己的實現;

4.Authenticator可能會委託給相應的AuthenticationStrategy進行多Realm身份驗證,默認ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm身份驗證;

5.Authenticator會把相應的token傳入Realm,從Realm獲取身份驗證信息,如果沒有返回/拋出異常表示身份驗證失敗了。此處可以配置多個Realm,將按照相應的順序及策略進行訪問。

流程大概就這個樣子,其實可以用更加通俗易懂的語言來描述更好。這些流程看源碼是最清楚的,所以我們可以針對源碼進行走一下,然後理解起來會更加的清楚明瞭。

代碼進行一一解釋

如下:

1.首先進行登錄:

 

currentUser.login(token);
2.SecurityManager一個大管家。

public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                    "empty value.  This value must be non null and populated with one or more elements.";
            throw new IllegalStateException(msg);
        }
        this.principals = principals;
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false);
        if (session != null) {
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }
3.Authenticator核心的身份認證入口點。

/**
     * Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.
     */
    public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

4.Authenticator可能會委託給相應的AuthenticationStrategy進行多Realm身份驗證。

 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {//單個
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {//多個
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }


總而言之就是這樣:

1.獲取獲取當前的 Subject. 調用 SecurityUtils.getSubject();

2.判斷當前的用戶是否已經被認證. 即是否已經登錄. 調用 Subject 的 isAuthenticated() 。

3.若沒有認證, 則把用戶名和密碼封裝爲 UsernamePasswordToken 對象。

4. 執行登錄。調用 Subject 的login(token); 方法. 

注:token指代是:AuthenticationToken

5.自定義 Realm 的方法, 從數據庫中獲取對應的記錄,並對密碼進行加密,來構建 AuthenticationInfo 對象並返回. 通常使用的實現類爲: SimpleAuthenticationInfo。

即:

SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

實例結構:


實例代碼:

LoginController.java:

package com.yiyi.controller;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
 * 登錄的Controller
 * @author hanyiyi
 *
 */
@Controller
@RequestMapping("/shiro")
public class LoginController {
	/**
	 * 登錄方法
	 * @param request
	 * @return
	 */
	@RequestMapping(value="/login",method=RequestMethod.POST)
	public String login(HttpServletRequest request){
		//獲取用戶名和密碼
		String username = request.getParameter("username");
		String password=request.getParameter("password");
		 // 使用SecurityUtils.getSubject();來獲取當前的 Subject. 
        Subject currentUser = SecurityUtils.getSubject();
        //判斷當前的用戶是否已經被認證。
        if(!currentUser.isAuthenticated()){
        	//若沒被認證,則把用戶名和密碼封裝爲UsernamePasswordToken對象
        	UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        	 token.setRememberMe(true);
             try {
             	// 執行登錄. 
                 currentUser.login(token);
             } 
             // ... catch more exceptions here (maybe custom ones specific to your application?
             // 所有認證時異常的父類. 
             catch (AuthenticationException ae) {
                 //unexpected condition?  error?
            	 System.out.println("登錄失敗---->"+ae.getMessage());
             }
         }
        return "redirect:/index.jsp";
	}

}

自定義Realm:
MyRealm.java:

package com.yiyi.realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.AuthenticatingRealm;

public class MyRealm extends AuthenticatingRealm{

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		//將AuthenticationToken對象轉換成UsernamePasswordToken對象
		 UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		 //獲取UsernamePasswordToken中的用戶名
		 String username = upToken.getUsername();
		 //從數據庫中查詢 username 對應的用戶記錄
                 System.out.println("從數據庫中查找"+username+"的信息");
                 //若用戶的信息不存在,則拋出UnknownAccountException異常。
		 if("unknown".equals(username)){
			 throw new UnknownAccountException("用戶不存在");
		 }
		//根據用戶的信息進行反饋,則拋出LockedAccountException異常。
		 if("han".equals(username)){
			 throw new  LockedAccountException("用戶被鎖定");
		 }
		 //根據用戶的信息來封裝SimpleAuthenticationInfo對象。
		 //當前 realm 對象的 name
		String realmName = getName();
		//認證的實體信息。
		Object principal = username;
		//密碼
		Object credentials="123456";
		SimpleAuthenticationInfo info =new SimpleAuthenticationInfo(principal, credentials, realmName);
		return info;
	}

}

登錄的頁面:
login.jsp:
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>登錄頁面</title>
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
  </head>
  <body>
  <h4>login page</h4>
	  <form action="shiro/login" method="post">
		     username:<input type="text" name="username"> <br/><br/>
		     password:<input type="password" name="password"><br/><br/>
		               <input type="submit" value="提交">
	  </form>
  </body>
</html>
攔截器配置;

先讓shiro/login進行匿名訪問:


圖形化界面;


現在我們來執行下這個操作:

首先進入登錄頁面:

http://localhost:8080/Shiro-03/login.jsp

根據代碼,我們先輸入一個正確的用戶名和密碼:zhao  123456 這時候會進入到對應的頁面,假如我們在一次進行入到登錄頁面,隨意輸入個錯誤的密碼,這時候還是可以登錄上的,這個是爲什麼呢?

原因是:shiro的緩存起到了作用,這時候避免出現這樣的現象我們直接在applicationContext.xml中配置一個logou的攔截器即可。

index.jsp:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>My JSP 'index.jsp' starting page</title>
    
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
  </head>
  
  <body>
    index jsp
    <a href="shiro/logout">登出</a>
  </body>
</html>

applicationContext.xml:


這其中還有很多重點需要一一解釋:

①:爲什麼自定義的Realm 爲什麼直接繼承AuthenticatingRealm呢?

②:SimpleAuthenticationInfo 這個對象中的參數都指代什麼呢?

③:shiro的密碼怎麼進行比對呢,怎麼進行加密呢?

這些都到下一節進行詳細的描述吧。

Ps:新手自學,哪裏不對還望指出,謝謝。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章