一種Freemarker+CAS單點登錄的可行方案

最近項目中遇到一個有意思的問題:

 

    描述如下:

        1. 產品詳情頁使用了Freemarker頁面靜態化技術, 所以爲了提高靜態頁面的併發訪問性能, 將其部署在了nginx服務器中;

        2. 同時要使用CAS做單點登錄功能, 但是CAS是Server+Client的模式, 直接部署靜態頁面就不存在Client, 無法直接使用CAS做單點登陸登出;

         

        在實際開發環境中, 我們往往遇到這樣活那樣的問題, 但是: 只要思想不滑坡, 方法總比困難多. 經過思考, 做出如下解決方案, 可能不是最優方案, 但肯定是一種實際可行的方案; 

 

    問題剖析及方案構思:

        1. 先確定, 萬變不離其宗, 瀏覽器登錄還是得靠Cookie, 怎麼登錄呢? 沒有Client那借一個可以嗎? 既然是登錄, 那就借用User模塊的吧;

        2. 怎麼借? 首先, 要跳轉到登錄頁面; 然後, 登錄完再跳轉回原來的靜態頁面; 最後, 靜態頁面還能動態獲取當前登錄的用戶名;

            跳轉到登錄頁: 訪問User模塊中一個要求登錄才能訪問的頁面, 來實現跳轉到登錄頁面的目的; 

            跳轉回原來的靜態頁面: 使用跳轉頁方案, 在地址欄將靜態頁地址作爲參數傳參給跳轉頁, 在登錄後由跳轉頁獲取地址後跳回靜態頁;

            動態獲取用戶名: 既然借了User模塊的登錄頁面, 不妨再借用User模塊的獲取登錄名接口; 

            登出: 訪問CAS服務器登出即可;

        3. 要想實現 2 中所述獲取用戶名功能, 需要面對一個新的問題: 跨域. 但是解決這個問題已經是常規操作啦, 本博採用W3C標準 CROS(跨域資源共享) 來解決, 另外jsonp也是一種顆星的純前端解決方案;

        

 

    其它需要交代的項目背景: 

        整體技術方案:  Angularjs+SSM+(CAS+Spring Security)+Freemarker+...

 

    方案實施:

        1. 實現跳轉到User模塊的登錄頁面:

            在ftl頁面中給登錄添加點擊事件, 然後跳轉到User模塊的 "跳轉頁" 跳轉頁不被user模塊放行, 所以需要登錄. 跳轉方法如下(地址欄操作不涉及跨域): 

	$scope.login=function(){
		location.href="http://192.168.25.1:8097/jumpBack.html?backPage="+location.href;
	}

            爲什麼要帶上參數呢? 那麼就引出了第2個操作, "跳轉頁":

        2. 當訪問 "跳轉頁" 時, 被要求登錄, 登錄後便進入跳轉頁, 跳轉頁立即獲取地址欄參數: backPage, 然後再通過地址欄操作跳轉回原靜態頁面, 此時借用的 User 模塊已處於登錄狀態. 跳轉頁代碼如下: 

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>跳轉中</title>
</head>
<body>
<script type="text/javascript">
    <!--獲取地址欄參數的方法-->
    function GetQueryString(name) {
        var reg = new RegExp("(^|&)"+ name +"=([^&]*)(&|$)");
        var r = window.location.search.substr(1).match(reg);
        if(r!=null)return  unescape(r[2]); return null;
    }
    location.href=GetQueryString("backPage");
</script>
</body>
</html>

        當跳轉回靜態頁面後, 靜態頁面需要通過 ajax 異步方式在頁面加載時即獲取用戶名, 又要借User模塊獲取用戶名方法醫用, 此時即出現跨域問題, 引出第 3 個操作: 跨域獲取用戶名:

        3. 跨域獲取用戶名:

            springMVC爲我們提供了簡便的跨域解決方案, 在允許被跨域訪問的方法上添加註解, 如下 User 模塊 LoginController 中的獲取用戶名方法 (其中對跨域起着作用的是 @CrossOrigin 註解, 其中 allowCredentials = "true" 爲默認值, 代表允許攜帶Cookie):

@RestController
@RequestMapping("login")
public class LoginController {
    @RequestMapping("findUsername")
    @CrossOrigin(origins = "http://item.pinyougou.com",allowCredentials = "true")
    public Map<String,String> findUsername(){
        String username = SecurityContextHolder.getContext().getAuthentication().getName();
        HashMap<String, String> loginHashMap = new HashMap<>();
        loginHashMap.put("username",username);
        return loginHashMap;
    }
}

             但是, 跨域時前端是默認不攜帶Cookie的, 故前端獲取用戶名方法如下 (其中cookie的配置爲: {'withCredentials':true}): 

	$scope.getUsername=function(){
		$http.get("http://192.168.25.1:8097/login/findUsername.do",{'withCredentials':true}).success(function(response){
			$scope.username=response.username;
		})
	}

        登錄效果:

            登錄前:  

            登錄頁面:

            

            地址欄實際爲User模塊的登錄:  

            登錄後:  

 

        4. 至此, 登錄方案實施完畢, 繼續實施登出方案, 登出直接訪問CAS服務器, 不需要借模塊, 唯一需要解決的問題是, 登錄後繼續留在當前靜態頁面: 

             故, 需要對CAS服務端進行少許配置, CAS服務端的 WEB-INF/cas_servlet.xml中:

<bean id="logoutAction" class="org.jasig.cas.web.flow.LogoutAction"
p:servicesManager-ref="servicesManager"
p:followServiceRedirects="${cas.logout.followServiceRedirects:true}"/>

            然後添加登出後跳轉的頁面作爲service參數即可, 故登出頁面js方法如下:

	$scope.logout=function(){
		location.href="http://192.168.25.129:8082/cas/logout?service="+location.href;
	}

         登出效果 (回到未登錄狀態):

          

    總結:

        1. 重要的不是具體的解決方案, 而是解決方案的思路, 抓住問題本質, 比如, 本博描述的問題的本質是: 要單點登錄, 要獲取用戶名, 要單點登出! 那就去想辦法得到這三個功能即可.

        2. 與其說是借用該模塊的登錄功能, 不如說是將nginx代理的靜態頁面模塊作爲了User模塊的資模塊中; 

 

囉嗦幾句: 有人會疑問, name這樣不是額外增加了User模塊的壓力嗎? 

        1. 靜態頁面的每一次登錄需要調用一次獲取用戶名方法, 所以, 調用誰不是調用呢? 

        2. 當用戶未登錄時, CAS直接攔截了該方法的訪問, 所以, 每一次的方法訪問成功都會是有效的, 不會出現無意義的方法調用; 

        3. 若同一瀏覽器登錄後, 每刷新一次就需要調用一次獲取用戶名方法, 這個只能依靠本地cookie存儲用戶名來解決, 存儲的數據模型爲Map(username:"張三", isLogin: 布爾類型) , cookie有效期與CAS登錄有效時長相同; 

            第一次登錄後, cookie被設置, 當第二次登錄時, 檢測該Map, 已得出是否登錄結論, 若參數2位false, 還可以實現提醒用戶登錄的功能, 類似京東效果: 如圖: 

----------------------------------------------------------------------------全篇完------------------------------------------------------------------------------------------------

轉載請註明出處: 划船一哥;

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