1. 程式人生 > >JSP——過濾器篇

JSP——過濾器篇

一、過濾器的基本概念

1、什麼是過濾器

過濾器是一個伺服器端的元件,它可以擷取客戶端的請求和服務端的響應資訊,並對這些資訊進行過濾。

2、過濾器的工作原理

過濾器的工作原理可以依據下圖進行分析(圖片轉自慕課網)。
使用者在請求Web資源時,使用者的請求會先被過濾器攔截,過濾器對使用者的請求進行過濾,過濾之後過濾器再將使用者的請求傳送到Web資源,Web資源在將響應返回給使用者時,響應也會先被過濾器攔截,對響應進行過濾之後由過濾器將響應傳送給使用者。

二、過濾器的生命週期與常用方法

1、過濾器的生命週期

過濾器的生命週期分為四個階段:例項化、初始化、過濾和銷燬:例項化是指在Web工程的web.xml檔案裡宣告一個過濾器,在聲明瞭過濾器之後,Web容器會建立一個過濾器的例項;初始化是指在建立了過濾器例項之後,伺服器會執行過濾器中的init()方法,這是過濾器的初始化方法;初始化之後過濾器就可以對請求和響應進行過濾了,過濾主要呼叫的是過濾器的doFilter()方法;最後當伺服器停止時,會將過濾器銷燬,銷燬過濾器前主要呼叫過濾器的destory()方法,釋放資源。

2、過濾器的常用方法

從上面對過濾器的生命週期的分析中可以看到,過濾器最常用的方法有三個:init()、doFilter()和destory()。 1)init()方法:這是過濾器的初始化方法,在Web容器建立了過濾器例項之後將呼叫這個方法進行一些初始化的操作,這個方法可以讀取web.xml中為過濾器定義的一些初始化引數。 2)doFilter()方法:這是過濾器的核心方法,會執行實際的過濾操作,當用戶訪問與過濾器關聯的URL時,Web容器會先呼叫過濾器的doFilter方法進行過濾。 3)destory()方法:這是Web容器在銷燬過濾器例項前呼叫的方法,主要用來釋放過濾器的資源等。

三、第一個過濾器例項

在瞭解了過濾器的相關概念之後,就可以嘗試寫一個過濾器了。首先定義一個過濾器名為FirstFilter,它是繼承自Filter這個介面的,而要繼承Filter介面,就需要實現這個介面的三個方法init()、doFilter()和destory(),可以發現這三個方法正是過濾器的常用方法,FirstFilter內容如下。
public class FirstFilter implements Filter {

	public void destroy() {
		System.out.println("First Filter------Destory");
	}

	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		System.out.println("First Filter------doFilter start");

		arg2.doFilter(arg0, arg1);
		
		System.out.println("First Filter------doFilter end");
	}

	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("First Filter------Init");
	}

}
在這三個方法裡分別輸出一句話表示這個方法已經執行了,其中doFilter()方法中FiterChain.doFilter()方法表示將請求傳給下一個過濾器或目標資源,當過濾器收到響應之後再執行FilterChain.doFilter()之後的內容。 定義了過濾器之後需要在web.xml檔案中進行宣告,過濾器在web.xml檔案中的配置可以參考下圖(圖片轉自慕課網)。 下面是我在web.xml檔案中的配置,當用戶請求URL為index.jsp時會觸發過濾器。
  <filter>
  	<filter-name>firstFilter</filter-name>
  	<filter-class>com.imooc.filter.FirstFilter</filter-class>
  </filter>
  <filter-mapping>
  	<filter-name>firstFilter</filter-name>
  	<url-pattern>/index.jsp</url-pattern>
  </filter-mapping>
index.jsp的內容很簡單,只是輸出一句話。
<html>
<body>
<h2>Hello World!</h2>

<%
	System.out.println("index.jsp------");
%>
</body>
</html>
這樣一個簡單的過濾器就完成了,下面啟動伺服器觀察輸出。首先在伺服器啟動過程中會發現有如下輸出:
……
……
First Filter------Init
八月 12, 2016 7:00:24 下午 org.apache.coyote.AbstractProtocol start
資訊: Starting ProtocolHandler ["http-nio-8080"]
八月 12, 2016 7:00:24 下午 org.apache.coyote.AbstractProtocol start
資訊: Starting ProtocolHandler ["ajp-nio-8009"]
八月 12, 2016 7:00:24 下午 org.apache.catalina.startup.Catalina start
資訊: Server startup in 1510 ms
這就表示當伺服器啟動時,伺服器會建立一個web.xml檔案中定義的過濾器例項並執行過濾器中的init()方法。當伺服器啟動好後,訪問index.jsp,這時控制檯就會輸出如下內容:
First Filter------doFilter start
index.jsp------
First Filter------doFilter end
這就表示當用戶請求index.jsp時,請求首先會被髮送到過濾器,過濾器執行doFilter()方法進行過濾,在doFilter()方法中,過濾器首先對請求執行過濾操作,然後呼叫FilterChain.doFilter()將請求傳給資源,資源響應後對響應進行過濾,最後才將過濾後的響應顯示給客戶端。
最後,當我們停止伺服器時,控制檯會列印如下的輸出:
……
……
First Filter------Destory
八月 12, 2016 7:09:23 下午 org.apache.coyote.AbstractProtocol stop
資訊: Stopping ProtocolHandler ["http-nio-8080"]
八月 12, 2016 7:09:23 下午 org.apache.coyote.AbstractProtocol stop
資訊: Stopping ProtocolHandler ["ajp-nio-8009"]
八月 12, 2016 7:09:23 下午 org.apache.coyote.AbstractProtocol destroy
資訊: Destroying ProtocolHandler ["http-nio-8080"]
八月 12, 2016 7:09:23 下午 org.apache.coyote.AbstractProtocol destroy
資訊: Destroying ProtocolHandler ["ajp-nio-8009"]
可以看到,過濾器的destory()方法執行了,表示Web容器已經準備銷燬過濾器釋放資源了。 從這個例項中可以很清晰地分析一個過濾器的生命週期,從建立例項、初始化到進行過濾最後銷燬,整個過程都是通過呼叫過濾器的相應方法來實現的。

四、過濾器鏈

一個Web專案可能存在多個過濾器,而對於同一個Web資源,也可能有多個過濾器與之相關聯,所以當為同一個Web資源關聯多個過濾器時,如果客戶端請求相應的Web資源,Web容器該按照什麼樣的順序進行過濾呢?這時就用到了過濾器鏈的知識,伺服器會按照web.xml中過濾器定義的先後順序組裝成一條鏈順序執行。過濾器鏈的執行過程可以參考下圖(圖片轉自慕課網)。

當用戶傳送請求請求Web資源時,過濾器1會首先獲取使用者的請求,對請求進行過濾,然後執行FilterChain.doFilter()將請求傳送給過濾器2,過濾器2在過濾了使用者請求之後執行FilterChain.doFilter()方法請求實際的Web資源,Web資源的響應將首先被過濾器2獲取,在過濾器2對響應進行過濾之後將響應傳遞給過濾器1,在過濾器1過濾之後才將響應傳送給使用者。
下面仍然通過一個例項來分析過濾器鏈,首先定義兩個過濾器FirstFilter和SecondFilter,FirstFilter定義仍然使用上面的例項,SecondFilter的定義如下,也只是簡單的輸出一些語句。
public class SecondFilter implements Filter {

	public void destroy() {
		System.out.println("Second Filter------Destory");
	}

	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		System.out.println("Second Filter------doFilter start");
		arg2.doFilter(arg0, arg1);
		System.out.println("Second Filter------diFilter end");
	}

	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("Second Filter------Init");
	}

}
然後在web.xml中宣告兩個過濾器,兩個過濾器都對index.jsp進行過濾,其中FirstFilter在SecondFilter之前宣告。
  <filter>
  	<filter-name>firstFilter</filter-name>
  	<filter-class>com.imooc.filter.FirstFilter</filter-class>
  </filter>
  <filter>
  	<filter-name>secondFilter</filter-name>
  	<filter-class>com.imooc.filter.SecondFilter</filter-class>
  </filter>
  <filter-mapping>
  	<filter-name>firstFilter</filter-name>
  	<url-pattern>/index.jsp</url-pattern>
  </filter-mapping>
  <filter-mapping>
  	<filter-name>secondFilter</filter-name>
  	<url-pattern>/index.jsp</url-pattern>
  </filter-mapping>
下面就可以啟動伺服器,訪問index.jsp,觀察控制檯的輸出:
First Filter------doFilter start
Second Filter------doFilter start
index.jsp------
Second Filter------diFilter end
First Filter------doFilter end
從輸出不難發現,Web容器對過濾器的執行過程就是依據前面所介紹的過濾器鏈的執行過程執行的。

五、過濾器的分類

所謂過濾器的分類就是指定義的過濾器對哪一種型別的請求進行過濾,具體體現在在web.xml檔案中宣告一個過濾器時,宣告的dispatcher標籤的值,具體的取值及用法可以參考下圖(圖片轉自慕課網)。
下面來分別介紹一下這幾個值的用法。

1、REQUEST

這個型別是最常用的,也是dispatcher標籤的預設值,表示使用者直接請求一個Web資源時觸發過濾器。之前介紹的幾個過濾器例項都是REQUEST型別的,當用戶直接請求index.jsp頁面時觸發了相應的過濾器,下面來看另一個例子,取消SecondFilter,修改上面的FirstFilter如下,在doFIlter()方法中使用HttpServletResponse的重定向方法sendRedirect()方法,重定向到index.jsp。
public class FirstFilter implements Filter {

	public void destroy() {
		System.out.println("First Filter------Destory");
	}

	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		System.out.println("First Filter------doFilter start");
		HttpServletResponse response = (HttpServletResponse) arg1;
		response.sendRedirect("index.jsp");
		arg2.doFilter(arg0, arg1);
		System.out.println("First Filter------doFilter end");
	}

	public void init(FilterConfig arg0) throws ServletException {
		System.out.println("First Filter------Init");
	}

}
這時再訪問index.jsp會發現瀏覽器處於一個卡死的狀態,觀察控制檯會發現控制檯不停地列印內容,陷入了一個死迴圈的狀態,這是因為當用戶請求index.jsp時,過濾器首先接收到使用者請求,在對請求過濾的過程中,重定向到了index.jsp,而重定向相當於一個新的請求,也就是使用者再次請求index.jsp,這樣再次觸發過濾器,如此迴圈往復,陷入了死迴圈的狀態。現在思考一個問題,如果將重定向改為伺服器內部轉發,會有什麼結果呢?再次修改FirstFilter的doFilter()方法,使用HttpServletRequest..getRequestDispatcher("index.jsp").forward(arg0, arg1)方法將請求轉發給index.jsp。
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		System.out.println("First Filter------doFilter start");
		HttpServletRequest request = (HttpServletRequest) arg0;
		request.getRequestDispatcher("index.jsp").forward(arg0, arg1);
		arg2.doFilter(arg0, arg1);
		
		System.out.println("First Filter------doFilter end");
	}
執行程式,訪問index.jsp,這時會發現頁面可以正常訪問,而後臺也沒有陷入死迴圈的狀態。這是為什麼呢?因為過濾器FirstFilter的型別為REQUEST,而伺服器內部轉發的請求是FORWARD型別的,所以過濾器不會對這種型別的請求進行過濾,所以可以正常訪問頁面,而要想過濾FORWARD請求,就需要將過濾器的型別改為FORWARD型別。

2、FORWARD

仍然使用上面伺服器內部轉發的案例,修改web.xml中對FirstFilter的配置,將過濾器型別改為FORWARD。
  <filter-mapping>
  	<filter-name>firstFilter</filter-name>
  	<url-pattern>/index.jsp</url-pattern>
  	<dispatcher>FORWARD</dispatcher>
  </filter-mapping>
定義一個middle.jsp,這個jsp只是包含了一個forward的動作,將請求轉發給index.jsp。
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<jsp:forward page="index.jsp"/>
</body>
</html>
再修改FirstFilter的doFilter()方法,將請求轉發到index.jsp。
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		System.out.println("First Filter------doFilter start");
		HttpServletRequest request = (HttpServletRequest) arg0;
		request.getRequestDispatcher("index.jsp").forward(arg0, arg1);
		arg2.doFilter(arg0, arg1);
		
		System.out.println("First Filter------doFilter end");
	}
這時訪問middle.jsp,會發現頁面再次變成卡死狀態,後臺也變成了死迴圈的狀態。這是因為在middle.jsp頁面中將請求轉發到index.jsp,這時被FORWARD型別的過濾器攔截,然後再次轉發到index.jsp,再次觸發過濾器,程式也因此陷入了死迴圈狀態。

3、INCLUDE

INCLUDE型別的過濾器與FORWARD型別的類似,在使用RequestDispatcher.include()方法呼叫Web資源時被觸發,在這裡就不再贅述了。

4、ERROR

ERROR型別的過濾器是指宣告式異常處理機制被呼叫時觸發,可以具體舉個例子來分析。在Web工程裡,為了使用者友好常常需要配置404的錯誤頁面,就是指當用戶請求一個不存在的頁面時伺服器會自動跳轉到一個設定好的錯誤頁面,而錯誤頁面需要在web.xml檔案裡進行如下配置:
<error-page>
  	<error-code>404</error-code>
  	<location>/error.jsp</location>
  </error-page>
當用戶訪問發生404錯誤時會自動跳轉到error.jsp,在這個例子裡error.jsp只是簡單地列印一句話。
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	Error page.
</body>
</html>
而當用戶訪問不存在的頁面跳轉到並error.jsp頁面時,這時就會觸發ERROR型別的過濾器,接下來修改FirstFilter過濾器的型別為ERROR,並將過濾器對映的URL改為error.jsp。
  <filter-mapping>
  	<filter-name>firstFilter</filter-name>
  	<url-pattern>/error.jsp</url-pattern>
  	<dispatcher>ERROR</dispatcher>
  </filter-mapping>
再修改FIrstFilter,在doFilter()方法中只是簡單地輸出內容。
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		System.out.println("First Filter------doFilter start");
		arg2.doFilter(arg0, arg1);
		System.out.println("First Filter------doFilter end");
	}
這時啟動程式,訪問一個不存在的頁面,比如test.jsp,這時頁面會自動跳轉到error.jsp,同時控制檯也打印出過濾器中輸出的內容。這裡需要注意,如果只是直接地訪問error.jsp,是不會觸發過濾器的,因為直接訪問頁面的請求型別是REQUEST,過濾器型別是ERROR的,所以不會進行過濾。

5、ASYNC

ASYNC型別是在Servlet3.0之後新增加的過濾器型別,前面介紹的幾種過濾器型別都是Servlet2.5中定義的過濾器型別,而ASYNC型別支援非同步的請求,因為非同步操作前臺有很多種方式可以實現,所以在這裡我就不介紹這種型別的過濾器了。

六、過濾器的一個綜合案例

前面對過濾器的基本知識做了一個比較全面的講解,現在可以結合前面的介紹開發一個小案例,考慮一個業務場景,使用者登入,如果使用者登入成功,頁面跳轉到成功頁面,並顯示使用者名稱,如果登入失敗,則跳轉到登入失敗頁面,這個功能很簡單,之前也介紹了好幾次,先簡單理一下。首先是登入頁面login.jsp,包含使用者名稱、密碼和提交按鈕。
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登入</title>
</head>
<body>
	<form action="dologin.jsp" method="post">
		使用者名稱:<input type="text" name="userName" /><br/>
		密碼:<input type="password" name="password"/><br/>
		<input type="submit" name="登入"/>
	</form>
</body>
</html>
使用者點選登入後,登入的表單交給dologin.jsp進行處理。
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%
	String userName = request.getParameter("userName");
	String password = request.getParameter("password");
	System.out.println(request.getParameter("userName"));
	
	if("admin".equals(userName) && "admin".equals(password)) {
		request.getSession().setAttribute("userName", userName);
		response.sendRedirect("success.jsp");
	} else {
		response.sendRedirect("fail.jsp");
	}
%>
這裡就是簡單做一個判斷,如果使用者名稱密碼都為admin,表示登入成功,在session裡新增一個使用者名稱屬性,並跳轉到success頁面將使用者名稱顯示出來,如果登入失敗,則跳轉到失敗頁面。success.jsp和fail.jsp定義如下。 success.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登入成功</title>
</head>
<body>
	登入成功!歡迎您:<%=session.getAttribute("userName") %>
</body>
</html>
fail.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登入失敗</title>
</head>
<body>
	登入失敗!請檢查您的使用者名稱和密碼!
</body>
</html>
這時一個簡單的登入功能就完成了,啟動程式,進入login.jsp,輸入使用者名稱密碼,就可以進行簡單的登入了,這時需要思考一個問題,現在實現的功能裡,當用戶名和密碼都為admin時,登入成功並在成功頁面顯示使用者名稱,那麼如果還沒有登入就直接訪問success.jsp,會是什麼結果呢?很明顯,如果直接訪問success.jsp,也是可以訪問的,只是會獲取不到使用者名稱,因而頁面使用者名稱就會顯示為空,這顯然不是我們希望的結果,我們不希望使用者在沒有登入的情況下就直接訪問成功頁面,在這裡就需要使用一個過濾器進行控制了。 我們定義一個過濾器LoginFilter,在這個過濾器的doFilter()方法裡進行一個判斷,判斷session裡面是否有使用者名稱這個屬性同時這個屬性的值是否為空,如果為空則重定向到login.jsp,否則繼續訪問Web資源。
public class LoginFilter implements Filter {
	
	@Override
	public void destroy() {
		

	}

	@Override
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) arg0;
		HttpServletResponse response = (HttpServletResponse) arg1;
		
		String userName = (String) request.getSession().getAttribute("userName");
		
		if(null != userName) {
			arg2.doFilter(arg0, arg1);
		} else {
			response.sendRedirect(request.getContextPath() + "/login/login.jsp");
		}

	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {

	}

}
定義了上面的過濾器之後就能夠完成登入功能了嗎?很顯然還是有欠缺的,思考一下,當用戶首次訪問login.jsp頁面時,session裡也是沒有使用者名稱資訊的,根據上面定義的過濾器,很顯然會自動重定向到login.jsp,這時又會觸發過濾器,這樣程式又陷入了死迴圈的狀態,這就意味著需要給過濾器過濾的頁面新增一些例外,當用戶訪問這些例外的頁面時過濾器將不會重定向,在這個功能裡我們需要過濾的頁面有登入頁面login.jsp、登入處理頁面dologin.jsp,因為登入頁面裡填寫表單是提交給dologin.jsp進行處理的,而這時還沒有使用者資訊,同時還要將登入失敗頁面新增未例外。新增例外的頁面可以直接在doFilter()裡進行新增,判斷訪問的資源的路徑是否是這三個,但是如果在一個大型的專案裡,需要新增例外的頁面很多,並且經常更新,我們不能每新增一個就在程式碼裡新增一個判斷,這樣的程式碼複用性會很差,這時就可以使用過濾器的初始化引數了,可以在web.xml中定義param-name和param-value來表示引數資訊,param-name表示引數名,param-value表示引數值。在這裡我定義了兩個引數,exceptPage和encoding,exceptPage是指需要開例外的頁面,頁面名以分好間隔,encoding是指編碼,因為在實際開發過程中經常會出現亂碼問題,解決亂碼的一種方式就是新增一個過濾器來轉換編碼,這裡使用引數的方式將編碼名傳給過濾器也提高了程式碼的複用性。
  <filter>
  	<filter-name>loginFilter</filter-name>
  	<filter-class>com.imooc.filter.LoginFilter</filter-class>
  	<init-param>
  		<param-name>exceptPage</param-name>
  		<param-value>login.jsp;dologin.jsp;fail.jsp</param-value>
  	</init-param>
  	<init-param>
  		<param-name>encoding</param-name>
  		<param-value>UTF-8</param-value>
  	</init-param>
  </filter>
  
  <filter-mapping>
  	<filter-name>loginFilter</filter-name>
  	<url-pattern>*</url-pattern>
  </filter-mapping>
再修改LoginFilter。
	FilterConfig config;
	
	@Override
	public void destroy() {
		

	}

	@Override
	public void doFilter(ServletRequest arg0, ServletResponse arg1,
			FilterChain arg2) throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) arg0;
		HttpServletResponse response = (HttpServletResponse) arg1;
		
		String encoding = config.getInitParameter("encoding");
		if(null == encoding) {
			request.setCharacterEncoding("UTF-8");
		} else {
			request.setCharacterEncoding(encoding);
		}
		String userName = (String) request.getSession().getAttribute("userName");
		
		String exceptPage = config.getInitParameter("exceptPage");
		if(null != exceptPage && !"".equals(exceptPage.trim())) {
			String[] exceptPages = exceptPage.split(";");
			for (String except : exceptPages) {
				if(request.getRequestURI().indexOf(except) != -1) {
					arg2.doFilter(arg0, arg1);
					return;
				}
				
			}
		}
		
		if(null != userName) {
			arg2.doFilter(arg0, arg1);
		} else {
			response.sendRedirect(request.getContextPath() + "/login/login.jsp");
		}

	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		this.config = arg0;
	}

}
我們可以通過FilterConfig來獲取過濾器的初始化引數,當獲取到例外頁面時,依據分號將字串分隔開,然後通過迴圈來判斷使用者請求的頁面是否是例外,如果在例外頁面當中,就可以直接訪問,否則仍然需要判斷使用者是否登入。同時我們也取出編碼格式,設定request的編碼。這樣我們再啟動應用訪問程式時,過濾器就可以正確地執行並判斷使用者是否登入了。