java之单点登录(SSO)
单点登录(SSO):SSO是指在多个应用系统中个,用户只需要登陆一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一用户的登陆的机制。
SSO的实现过程:
通过上述图形,我们可以看到SSO的大体实现步骤主要分为两大步:存储登录信息,查验登录信息。
对于SSO,我们也可以将之分为两大不同的类型:同域SSO和跨域SSO;其中同域SSO又可以分为完全同域SSO和同父域SSO。
一、完全同域SSO:指的是域名完全相同的多个应用系统中实现单点登录。
其实现步骤主要分为:前期准备工作,编写统一登录接口,编写登录校验接口,编写验证页面,实现SSO;
编写统一登录接口:
登录页面的编写主要是录入用户的登录信息,包括用户名,密码,以及登录页面的地址。因为存在多个应用系统,所以用户在统一登录页面登陆成功后,系统需要知道用户想要访问的是哪个系统页面,这个时候就需要记录用户第一次访问的地址,等到用户登录成功后,就可直接跳转该页面。
<body> <center> <h1>请登录</h1> <form action="/sso/doLogin.action" method="POST"> <span>用户名:</span><input type="text" name="username" /> <span>密码:</span><input type="text" name="password" /> //暂存需要登录页面的url地址 <input type="hidden" name="gotoUrl" value="${gotoUrl}" /> <input type="submit" /> </form> </center> </body>
登录方法的编写:需要新建cookie,将用户的信息存进cookie中,并指定cookie的路径,因为是完全同域,所以cookie的地址可以简写为(“/”)。这里必须要设置cookie的路径,如果不设置,那么cookie的路径将并不一定在当前域名的顶层,它有可能就在当前的这个路径下才可见,这样会导致在当前域的其他路径下找不到这个cookie,解决办法就是把cookie设置到当前域的最顶层域里面,这样当前域下的所有应用就会都可见。
public String doLogin(){ //新建Cookie Cookie cookie = new Cookie("ssocookie","sso"); //设置Cookie路径 cookie.setPath("/"); HttpServletResponse response = ServletActionContext.getResponse(); response.addCookie(cookie); return "success"; }
登录校验接口的编写:通常SSO登录接口校验都会放在登录拦截器中,当用户想要访问某个系统时,登录拦截器将直接重定向到统一登录页面,用户填写完登录信息后,就会进行登录校验。
//cookie校验(放在拦截器中进行校验) public static boolean checkCookie(HttpServletRequst request){ Cookie[] cookies=request.getCookies(); if(cookies!=null){ for(Cookies cookie:cookies){ if(cookie.getName().equals("ssocookie") &&cookie.getValue().equals("sso")){ return true; } } } return false; }
编写测试主页
public String main(){ HttpServletRequst request = ServletActionContext.getRequst(); if(SSOCheck.checkCookie(request)){ //登陆成功,记录用户登录信息...... return "success"; } //登录失败,暂存需要访问的地址,登录成功后,直接访问该地址 gotoUrl="/demo1/main.action"; return "login"; }
最后还有struts2的配置文件
<struts> <package name="sso" namespace="/sso" extends="struts-default"> <action name="doLogin" class="com.xm.controllerAction.SSOAction" method="doLogin">
<!-- 用户登录成功后,需要进行重定向,重新跳转到用户最初访问的路径 --> <result name="success" type="redirect">${gotoUrl}</result> </action> </package> <package name="demo1" namespace="/demo1" extends="struts-default"> <action name="main" class="com.xm.controllerAction.Demo1Action" method="main"> <result name="success">/success1.jsp</result> <result name="login">/login.jsp</result> </action> </package> <package name="demo2" namespace="/demo2" extends="struts-default"> <action name="main" class="com.xm.controllerAction.Demo2Action" method="main"> <result name="success">/success2.jsp</result> <result name="login">/login.jsp</result> </action> </package> </struts>
二、同父域SSO:指的是父域名相同的应用系统上实现SSO。
其实现步骤与上述完全同域SSO相同。
其中检验域名:http://check.x.com
测试页面域名:http://demo1.x.com和http://demo2.x.com
编写统一登录接口:
代码实现与完全同域名SSO基本一致,不过在设置提交路径时,因为二级域名不同,所以不能写成相对路径,需要写成绝对路径地址。
<body> <center> <h1>请登录</h1>
//表单提交地址需写成绝对路径,不能写相对路径 <form action="http://check.x.com/sso/doLogin.action" method="POST"> <span>用户名:</span><input type="text" name="username" /> <span>密码:</span><input type="text" name="password" /> //暂存需要登录页面的url地址 <input type="hidden" name="gotoUrl" value="${gotoUrl}" /> <input type="submit" /> </form> </center> </body>
登录方法:在设置cookie路径的时候有所变化,同上,为了使得当前两个同父域的应用系统都可见这个cookie,那么我们需要将这个cookie设置到父域下面,而不应该设置到本域下面,这样才可以实现域不同,但是父域相同的应用都可以看到的这个cookie。
public String doLogin(){
boolean ok = SSOCheck.checkLogin(userName,passWord);
if(ok){ //新建Cookie Cookie cookie = new Cookie("ssocookie","sso"); //设置Cookie的父域 cookie.setDomain(".x.com"); //设置Cookie路径 cookie.setPath("/"); HttpServletResponse response = ServletActionContext.getResponse(); response.addCookie(cookie); return "success";
}
}
登录校验接口:因为有着不同的域,所以我们应该将登录所获得cookie传到专门的校验域下的校验方法中进行校验,否则我们需要在各自不同的登录页面实现校验,这样显得代码十分的冗余。
private String cookieName;
private String cookieValue;
public String getCookieName() {
return cookieName;
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
}
public String getCookieValue() {
return cookieValue;
}
public void setCookieValue(String cookieValue) {
this.cookieValue = cookieValue;
}
//二级域名向二级域名发送请求 public void checkCookie() throws IOException{ boolean ok=SSOCheck.checkCookie(cookieName,cookieValue); String result="0"; if(ok){ result="1"; } HttpServletResponse response = ServletActionContext.getResponse(); response.getWriter().print(result); response.getWriter().close(); }
public static boolean checkCookie(String cookieName,String cookieValue){ if(cookieName.equals("ssocookie")&&cookieValue.equals("sso")){ return true; } return false; }
编写测试主页
public String main(){ HttpServletRequst request = ServletActionContext.getRequst();
//获取cookie Cookie[] cookies=request.getCookies(); if(cookies!=null){ for(Cookie cookie:cookies){ if(cookie.getName().equals("ssocookie")){
//向检验服务器中发送cookieName和cookieValue String result = Demo1Tool.doGet("http://check.x.com/so/checkCookie.action", cookie.getName(),cookie.getValue()); if(result.equals("1")){ return "success"; } } } } //暂存需要访问的地址,登录成功后,直接访问该地址 gotoUrl="http://demo1.x.com/demo1/main.action"; return "login"; }
public static String doGet(String url,String cookieName,String cookieValue){ //定义返回值 StringBuffer sb = new StringBuffer(); HttpURLConnection httpURLConnection = null; try{ //校验方法所在的地址 URL urls = new URL(url+ "?cookieName="+cookieName+"&cookieValue="+cookieValue); //打开连接 httpURLConnection = (HttpURLConnection) urls.openConnection(); //设置打开连接的方法 httpURLConnection.setRequestMethod("GET"); //开始连接 httpURLConnection.connect(); InputStream in = httpURLConnection.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); String temp = null; while((temp = br.readLine())!=null){ sb.append(temp); } br.close(); isr.close(); in.close(); } catch(IOException e){ e.printStackTrace(); } finally{ if(httpURLConnection!=null){ httpURLConnection.disconnect(); } } return sb.toString(); }
struts配置文件
<struts> <package name="sso" namespace="/sso" extends="struts-default"> <action name="doLogin" class="com.xm.controllerAction.SSOAction" method="doLogin"> <result name="success" type="redirect">${gotoUrl}</result> </action> <action name="checkCookie" class="check.x.com.SSOAction" method="checkCookie"> </action> </package> <package name="demo1" namespace="/demo1" extends="struts-default"> <action name="main" class="demo1.x.com.Demo1Action" method="main"> <result name="success">/success1.jsp</result> <result name="login">/login.jsp</result> </action> </package> <package name="demo2" namespace="/demo2" extends="struts-default"> <action name="main" class="demo2.x.com.Demo2Action" method="main"> <result name="success">/success2.jsp</result> <result name="login">/login.jsp</result> </action> </package> </struts>
三、跨域SSO:在域名完全不同的应用程序上实现SSO。
其实现步骤与完全同域SSO相同。
其中检验域名:http://www.x.com
测试页面域名:http://www.a.com和http://www.b.com
编写统一登录接口:
<body> <center> <h1>请登录</h1> //这里需要向当前所在的域提交申请,因为如果向校验域提交申请,那么在本域中将无法看到cookie <form action="/${path}/doLogin.action" method="POST"> <span>用户名:</span><input type="text" name="username" /> <span>密码:</span><input type="text" name="password" /> //暂存需要登录页面的url地址 <input type="hidden" name="gotoUrl" value="${gotoUrl}" /> <input type="submit" /> </form> </center> </body>
登录方法:
public String doLogin(){ Map<String,String> map = new HashMap<String,String>(); map.put("userName", userName); map.put("password", passWord); String result = Demo1Tool.doGet("http://www.x.com/sso/doLogin.action",map); if(result.equals("1")){ return "success"; } return "login"; }
public void doLogin() throw IOException{ boolean ok=SSOCheck.checkCookie(cookieName,cookieValue); String result = "0"; if(ok){ result = "1"; } HttpServletResponse response = ServletActionContext.getResponse(); response.getWriter().print(result); response.getWriter().close(); }
登录校验接口;和同父域SSO的登录校验基本一致,没有什么变化。
public void checkCookie() throws IOException{ boolean ok=SSOCheck.checkCookie(cookieName,cookieValue); String result="0"; if(ok){ result="1"; } HttpServletResponse response = ServletActionContext.getResponse(); response.getWriter().print(result); response.getWriter().close(); }
编写测试主页
public String main(){ HttpServletRequst request = ServletActionContext.getRequst(); Cookie[] cookies=request.getCookies(); if(cookies!=null){ for(Cookie cookie:cookies){ if(cookie.getName().equals("ssocookie")){ Map<String,String> map = new HashMap<String,String>(); map.put("userName", cookie.getName()); map.put("password", cookie.getValue()); String result = Demo1Tool.doGet("http://www.x.com/so/checkCookie.action", cookie.getName(),cookie.getValue()); if(result.equals("1")){ return "success"; } } } } //暂存需要访问的地址,登录成功后,直接访问该地址 path = "demo1"; gotoUrl="http://www.a.com/demo1/main.action"; return "login"; }
public static String doGet(String url,Map<String,String> map){ //定义返回值 StringBuffer sb = new StringBuffer(); HttpURLConnection httpURLConnection = null; try{ StringBuffer t_s = new StringBuffer(url).append("?"); for(Map.Entry<String, String> entry:map.entrySet()){ t_s.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); } url = t_s.substring(0,t_s.length()-1); //校验方法所在的地址 URL urls = new URL(url); //打开连接 httpURLConnection = (HttpURLConnection) urls.openConnection(); //设置打开连接的方法 httpURLConnection.setRequestMethod("GET"); //开始连接 httpURLConnection.connect(); InputStream in = httpURLConnection.getInputStream(); InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); String temp = null; while((temp = br.readLine())!=null){ sb.append(temp); } br.close(); isr.close(); in.close(); } catch(IOException e){ e.printStackTrace(); } finally{ if(httpURLConnection!=null){ httpURLConnection.disconnect(); } } return sb.toString(); }
至此,准备工作做完,接下来是跨域SSO实现中最重要的部分,也就是cookie的设置,在之前完全同域和同父域的情况下,为了实现SSO,我们在进行doLogin时就设置了cookie,因为域名相同,所以十分简单,但是在跨域SSO中,因为不同域之间的cookie是不可见的,所以我们不可能只设置一个cookie,然后令所有的域名下的应用程序皆可见,所以我们应该在每个域下面都有着为本域设置cookie的方法,而不应该直接将设置cookie交给校验域。
//为本域设置cookie的方法 public void addCookie(){ Cookie cookie = new Cookie("ssocookie","sso"); cookie.setPath("/"); HttpServletResponse response = ServletActionContext.getResponse(); response.addCookie(cookie); }
还需要在配置文件中进行配置:
<action name="addCookie" class="www.a.com.Demo1Action" method="addCookie"></action>
写完好方法,则需要进行调用,因此我们需要找一个可以让二者进行交会的地方,在这里我选择了登录成功的瞬间,通过隐藏的Iframe让二者进行交会。
public String doLogin(){ Map<String,String> map = new HashMap<String,String>(); map.put("userName", userName); map.put("password", passWord); String result = Demo1Tool.doGet("http://www.x.com/sso/doLogin.action",map); if(result.equals("1")){ return "success"; } List hidderUrl = new ArrayList<String>(); hidderUrl.add("http://www.a.com/demo1/addCookie.action"); hidderUrl.add("http://www.b.com/demo2/addCookie.action"); return "login"; }
<c:forEach var="url" item="${hiddenUrl}">
<iframe src="${url}" width="0px" heigth="0px" style="display:none"></iframe>
</c:forEach>