SSO单点登录

本文实现了Web-SSO单点登录功能,实例中包含三个工程, SSOAuth, SSOWebDemo1, SSOWebDemo2, SSOAuth为认证系统,使用SSOWebDemo1登录系统时,要使用SSOAuth进行鉴权, 登录上Demo1后,系统可实现自动登录到Demo2的功能

实例下载:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=490

本文开发工具:

MyEclipse10: https://pan.baidu.com/s/1eRPzTJG

JDK1.7 : https://pan.baidu.com/s/1jHWJSdK

1.认识SSO

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

单点登录的机制其实是比较简单的,用一个现实中的例子做比较。颐和园是北京著名的旅游景点,也是我常去的地方。在颐和园内部有许多独立的景点,例如“苏州 街”、“佛香阁”和“德和园”,都可以在各个景点门口单独买票。很多游客需要游玩所有的景点,这种买票方式很不方便,需要在每个景点门口排队买票,钱包拿 进拿出的,容易丢失,很不安全。于是绝大多数游客选择在大门口买一张通票(也叫套票),就可以玩遍所有的景点而不需要重新再买票。他们只需要在每个景点门 口出示一下刚才买的套票就能够被允许进入每个独立的景点。

2.SSO实现机制

单点登录的机制也一样,如下图所示,当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录(1);根据用户提供的登录信息,认证系统进行身份效验,如果通过效验,应该返回给用户一个认证的凭据--ticket(2);用户再访问别的应用的时候(3,5)就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行效验,检查ticket的合法性(4,6)。如果通过效验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。

SSO单点登录

统一的认证系统并不是说只有单个的认证服务器,如下图所示,整个系统可以存在两个以上的认证服务器,这些服务器甚至可以是不同的产品。认证服务器之间要通过标准的通讯协议,互相交换认证信息,就能完成更高级别的单点登录。如下图,当用户在访问应用系统1时,由第一个认证服务器进行认证后,得到由此服务器产生的ticket。当他访问应用系统4的时候,认证服务器2能够识别此ticket是由第一个服务器产生的,通过认证服务器之间标准的通讯协议(例如SAML)来交换认证信息,仍然能够完成SSO的功能。

SSO单点登录

3. Web-SSO实现方法

例如用户在访问页面1的时候进行了登录,客户端(浏览器)的每个请求都是单独的连接,当客户再次访问页面2的时候,如何才能告诉Web服务器,客户刚才已经登录过了呢?浏览器和服务器之间有约定:通过使用cookie技术来维护应用的状态。Cookie是可以被Web服务器设置的字符串,并且可以保存在浏览器中。如下图所示,当浏览器访问了页面1时,web服务器设置了一个cookie,并将这个cookie和页面1一起返回给浏览器,浏览器接到cookie之后,就会保存起来,在它访问页面2的时候会把这个cookie也带上,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状态。Web-SSO完全可以利用Cookie来完成用户登录信息的保存,将浏览器中的Cookie和上文中的Ticket结合起来,完成SSO的功能。

SSO单点登录

4. 实例概述

本实例一共有三个Web工程, SSOAuth, SSOWebDemo1, SSOWebDemo2, 访问SSOWebDemo1的jsp页面时,demo1的应用程序会判断是否登录,如果未登录,进入登录页面,如果已经登录,显示登录人的基本信息; 同样访问SSOWebDemo2的jsp页面时,也会像访问SSOWebDemo1一样。 SSOWebDemo1与SSOWebDemo2的跳转的登录页面由SSOAuth提供, 用户信息由SSOAuth进行校验

4.1访问SSOWebDemo1路径: http://localhost:8080/SSOWebDemo1/test.jsp

如果未登录成功,跳转到登录页面,见下图

SSO单点登录

如果已经登录,则显示登录人信息, 见下图

SSO单点登录

同理 SSOWebDemo2

5. 代码详解(由于SSOWebDemo1与SSOWebDemo2类别,下面只讲解SSOWebDemo1和SSOAuth)

5.1 SSOWebDemo1的web.xml中配置jsp过滤器,访问工程中页面时,会被过滤器类SSOFilter拦截

SSO单点登录

<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"><filter><filter-name>SSOFilter</filter-name><filter-class>SSO.SSOFilter</filter-class><!-- <init-param> <param-name>CookieName</param-name> <param-value>SsoTicket</param-value> </init-param> <init-param> <param-name>SSOServiceURL</param-name> <param-value>http://localhost:8080/SSOAuth/SSOAuth</param-value> </init-param> <init-param> <param-name>SSOLoginPage</param-name> <param-value>http://localhost:8080/SSOAuth/login.jsp</param-value> </init-param> --></filter><filter-mapping><filter-name>SSOFilter</filter-name><url-pattern>*.jsp</url-pattern></filter-mapping><session-config><session-timeout> 60 </session-timeout></session-config><welcome-file-list><welcome-file> index.jsp </welcome-file></welcome-file-list></web-app>

SSO单点登录

5.2 SSOFilter拦截器中获取浏览器中所有cookie信息,判断是否有需要处理的cookie(根据名称), 如果没有,则认为其未登录, 页面跳转到登录页面; 如果cookie列表有需要处理的cookie,调用SSOAuth接口判断cookie是否有效,如果无效,由跳转到登录页面, 如果有效,SSOAuth返回用户基本信息给SSOWebDemo1,将用户信息显示在页面上

SSO单点登录

package SSO;import java.io.IOException;import java.io.PrintStream;import java.io.PrintWriter;import java.io.StringWriter;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.Cookie;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.httpclient.HttpClient;import org.apache.commons.httpclient.methods.GetMethod;import com.alibaba.fastjson.JSON;public class SSOFilter implements Filter { private FilterConfig filterConfig = null; public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { log("SSOFilter:doFilter()"); HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String result = "failed"; String url = request.getRequestURL().toString(); String qstring = request.getQueryString(); log("SSOFilter: 访问路径:" + url); log("SSOFilter: 查询参数:" + qstring); if (qstring == null) qstring = ""; String cookieValue = ""; //获取所有cookie信息Cookie[] diskCookies = request.getCookies(); if (diskCookies != null) { for (int i = 0; i < diskCookies.length; i++) { //根据名称判断是否含有需要处理的cookieif (diskCookies[i].getName().equals(Constant.CookieName)) { cookieValue = diskCookies[i].getValue(); log("SSOFilter: cookie[" + Constant.CookieName + "," + cookieValue + "]"); //将获取的cookie值发送给SSOAuth进行校验, //无效返回failed, 有效返回用户信息result = SSOService(cookieValue); log("SSOFilter: SSOAuth 返回值:" + result); break; } } } if (result.equals("failed")) { //页面跳转到SSOAuth中的登录页面, goto对应的url为登录成功后跳转的页面路径response.sendRedirect(Constant.SSOLoginPage + "?goto=" + url); } else if (qstring.indexOf("logout") > 1) { log("SSOFilter: logout action!"); logoutService(cookieValue); response.sendRedirect(Constant.SSOLoginPage + "?goto=" + url); } else { //将用户的json串信息 反转 为User对象User user = JSON.parseObject(result, User.class); request.setAttribute("SSOUser", user); Throwable problem = null; try { chain.doFilter(req, res); } catch (Throwable t) { problem = t; t.printStackTrace(); } if (problem != null) { if ((problem instanceof ServletException)) throw ((ServletException) problem); if ((problem instanceof IOException)) throw ((IOException) problem); sendProcessingError(problem, res); } } } public FilterConfig getFilterConfig() { return this.filterConfig; } public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; } public void destroy() { } public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; if (filterConfig != null) { log("SSOFilter:Initializing filter"); } /*获取web.xml的配置信息*//*Constant.CookieName = filterConfig.getInitParameter("CookieName"); Constant.SSOServiceURL = filterConfig.getInitParameter("SSOServiceURL"); Constant.SSOLoginPage = filterConfig.getInitParameter("SSOLoginPage");*/ } public String toString() { if (this.filterConfig == null) return "SSOFilter()"; StringBuffer sb = new StringBuffer("SSOFilter("); sb.append(this.filterConfig); sb.append(")"); return sb.toString(); } private void sendProcessingError(Throwable t, ServletResponse response) { String stackTrace = getStackTrace(t); if ((stackTrace != null) && (!stackTrace.equals(""))) { try { response.setContentType("text/html"); PrintStream ps = new PrintStream(response.getOutputStream()); PrintWriter pw = new PrintWriter(ps); pw.print("<html> <head> <title>Error</title> </head> <body> "); pw.print("<h1>The resource did not process correctly</h1> <pre> "); pw.print(stackTrace); pw.print("</pre></body> </html>"); pw.close(); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } elsetry { PrintStream ps = new PrintStream(response.getOutputStream()); t.printStackTrace(ps); ps.close(); response.getOutputStream().close(); } catch (Exception ex) { } } public static String getStackTrace(Throwable t) { String stackTrace = null; try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.close(); sw.close(); stackTrace = sw.getBuffer().toString(); } catch (Exception ex) { } return stackTrace; } /** * 使用HttpClient向SSOAuth鉴权 * @param cookievalue * @return * @throws IOException */private String SSOService(String cookievalue) throws IOException { String authAction = "?action=authcookie&cookiename="; HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod(Constant.SSOServiceURL + authAction + cookievalue); try { httpclient.executeMethod(httpget); String result = httpget.getResponseBodyAsString(); return result; } finally { httpget.releaseConnection(); } } /** * 退出,让SSOAuth删除用户鉴权信息 */private void logoutService(String cookievalue) throws IOException { String authAction = "?action=logout&cookiename="; HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod(Constant.SSOServiceURL + authAction + cookievalue); try { httpclient.executeMethod(httpget); httpget.getResponseBodyAsString(); } finally { httpget.releaseConnection(); } } public void log(String msg) { this.filterConfig.getServletContext().log("[工程1]" + msg); }}

SSO单点登录

5.3 基本常量信息

SSO单点登录

package SSO;public class Constant { public static String CookieName = "SsoTicket"; public static String SSOServiceURL = "http://localhost:8080/SSOAuth/SSOAuth"; public static String SSOLoginPage = "http://localhost:8080/SSOAuth/login.jsp"; public static final boolean debug = true;}

SSO单点登录

5.4 SSOWebDemo1中访问的jsp页面

SSO单点登录

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%><%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ page import="SSO.User" %><%String contextPath = request.getContextPath();%><!DOCTYPE html><html><head><title>系统1</title><link rel="STYLESHEET" type="text/css" href="css.css"></head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0"><br><div><h1>系统1登录成功</h1><table border="0" cellpadding="0" cellspacing="0" width="748"><tr><td height="100" width="300"><h3>Welcome!</h3><h4>姓名: <%=((User)request.getAttribute("SSOUser")).getUserName()%></h4><h4>密码: <%=((User)request.getAttribute("SSOUser")).getPassword()%></h4><h4>年龄: <%=((User)request.getAttribute("SSOUser")).getAge()%></h4><h4>地址: <%=((User)request.getAttribute("SSOUser")).getAddress()%></h4><h3>Success Access Web SSO Demo1</h3><br><h3><a href="test.jsp?action=logout">LogOut</a></h3></td></tr></table></div></body></html>

SSO单点登录

6.下面讲解SSOAuth

6.1 web.xml中配置servlet,用于向SSOWebDemo1,SSOWebDemo2提供接口调用,

接口功能: SSOWebDemo1根据cookie值到SSOAuth中获取登录人基本信息;

SSOAuth自身的登录退出操作

SSO单点登录

29<?xml version="1.0" encoding="UTF-8"?><web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"><servlet><servlet-name>SSOAuth</servlet-name><servlet-class>SSO.SSOAuth</servlet-class><!-- <init-param> <param-name>domainname</param-name> <param-value>localhost</param-value> </init-param> <init-param> <param-name>cookiename</param-name> <param-value>SsoTicket</param-value> </init-param> --></servlet><servlet-mapping><servlet-name>SSOAuth</servlet-name><url-pattern>/SSOAuth</url-pattern></servlet-mapping><session-config><session-timeout> 30 </session-timeout></session-config><welcome-file-list><welcome-file> index.jsp </welcome-file></welcome-file-list></web-app>

SSO单点登录

6.2 SSOAuth类详解

SSOAuth初始一个线程同步对象 ConcurrentMap SSOIDs; 此对象存储key: cookie, value: 用户信息

当用户登录时,判断用户的用户名与密码是否正确,如果正确, 使用相应算法生成cookie值 和 用户信息保存在SSOIDs中;

登录成功后,将cookie信息让浏览器进行保存;

SSOWebDemo1中发出SSO校验请求,根据传递的cookie参数值判断是否有效,如果有效,将对应的用户信息返回;

SSOWebDemo1中发出退出操作请求,根据cookie参数来删除SSOIDs对应的用户信息

SSO单点登录

SSO单点登录

6.3 登录页面

SSO单点登录

<%@page contentType="text/html"%><%@page pageEncoding="UTF-8"%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head><title>登录</title><link rel="STYLESHEET" type="text/css" href="css.css"></head><body topmargin="0" leftmargin="0" rightmargin="0" bottommargin="0"><br><div><table border="0" cellpadding="0" cellspacing="0" width="748"><tr><td height="85" width="300"><h3>统一登录页面</h3><br><h1>登录页面</h1></td></tr></table><table border="0" cellpadding="0" cellspacing="0" width="748"><tr><td><table border='0' cellspacing='0' cellpadding='0'><tr><td width='598' valign='top'><p><form action='/SSOAuth/SSOAuth' method='post'>用户名: <input type='text' name='username'><br><br>密&nbsp;&nbsp;&nbsp;码: <input type='password' name='password'> <br><input type='hidden' name='goto' value=<%=request.getParameter("goto")%>></input><br><input type='submit' value='登录' style="width:100px;"></input></form></p></td></tr></table></td></tr></tr></table></div></body></html>

转载于:https://my.oschina.net/u/3743971/blog/1791632

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值