一种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, 还可以实现提醒用户登录的功能, 类似京东效果: 如图: 

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

转载请注明出处: 划船一哥;

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