作者微信 bishe2022

代码功能演示视频在页面下方,请先观看;如需定制开发,联系页面右侧客服

本文开发工具: 

   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了。

   blob.png

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

blob.png



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的功能。


           blob.png

4. 实例概述

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

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

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

       blob.png

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

       blob.png

     同理 SSOWebDemo2

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

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

<?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>

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

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++) {
			        //根据名称判断是否含有需要处理的cookie
				if (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>\n<head>\n<title>Error</title>\n</head>\n<body>\n");

				pw.print("<h1>The resource did not process correctly</h1>\n<pre>\n");
				pw.print(stackTrace);
				pw.print("</pre></body>\n</html>");
				pw.close();
				ps.close();
				response.getOutputStream().close();
			} catch (Exception ex) {
			}
		} else
			try {
				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);
	}
}

  5.3 基本常量信息

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;
}

 5.4 SSOWebDemo1中访问的jsp页面

<%@ 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>

*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

6.下面讲解SSOAuth

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

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

                SSOAuth自身的登录退出操作

<?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>

 6.2 SSOAuth类详解

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

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

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

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

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

package SSO;

import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;

public class SSOAuth extends HttpServlet {
	private static ConcurrentMap SSOIDs;
	protected void processRequest(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		PrintWriter out = response.getWriter();
		String action = request.getParameter("action");
		String gotoURL = request.getParameter("goto");
		String result = "failed";
		if (action == null) {
			userLogin(request, response);
		} else if (action.equals("authcookie")) {
			String myCookie = request.getParameter("cookiename");
			if (myCookie != null)
				result = authCookie(myCookie);
			out.print(result);
			out.close();
		} else if (action.equals("logout")) {
			String myCookie = request.getParameter("cookiename");
			logout(myCookie);
			out.close();
		}
	}

	public static String authCookie(String value) {
		User user= (User) SSOIDs.get(value);
		String strUserInfo = "";
		if (user == null) {
			strUserInfo = "failed";
			System.out.println("Authentication failed!");
		} else {
			strUserInfo = JSON.toJSONString(user);
			System.out.println("Authentication success!");
		}
		return strUserInfo;
	}

	/**
	 * 校验用户登录信息是否正确
	 * @param userName
	 * @param password
	 * @return
	 */
	public static User authUser(String userName, String password) {
		if(null == userName || null == password){
			return null;
		}
		
		User loginUser = null;
		for(User user : Constant.UserAccounts){
			if(userName.equals(user.getUserName()) && password.equals(user.getPassword())){
				loginUser = user;
			}
		}
		
		return loginUser;
	}
		

	
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		processRequest(request, response);
	}

	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		processRequest(request, response);
	}

	public String getServletInfo() {
		return "Short description";
	}

	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		/*this.domainname = config.getInitParameter("domainname");
		this.cookiename = config.getInitParameter("cookiename");*/
		SSOIDs = new ConcurrentHashMap();
	}

	private static String createUID(String userName) {
		Date now = new Date();
		long time = now.getTime();
		return userName + time;
	}

	/**
	 * 
	 * @param UID
	 */
	private void logout(String UID) {
		System.out.println("Logout for " + UID);
		SSOIDs.remove(UID);
	}

	/**
	 * 
	 * @param request
	 * @param response
	 * @throws ServletException
	 * @throws IOException
	 */
	private void userLogin(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		String userName = request.getParameter("username");
		String password = request.getParameter("password");
		
		
		User user = authUser(userName, password);
		if (null == user){
			getServletContext().getRequestDispatcher("/failed.html").forward(request, response);
		}else{
			String gotoURL = request.getParameter("goto");
			String newID = createUID(userName);
			SSOIDs.put(newID, user);
			add2Cookie(response, Constant.CookieName, newID, 60 * 1000);
			System.out.println("login success, goto back url:" + gotoURL);
			if (gotoURL != null) {
				PrintWriter out = response.getWriter();
				response.sendRedirect(gotoURL);
				out.close();
			}
		}
	}

	/**
	 * 
	 * @param response
	 * @param cookieName
	 * @param cookieValue
	 * @param maxAge
	 */
	private void add2Cookie(HttpServletResponse response, String cookieName,
			String cookieValue, int maxAge) {
		Cookie cookie = new Cookie(cookieName, cookieValue);
		cookie.setDomain(Constant.DomainName);
		cookie.setPath("/");
		cookie.setMaxAge(maxAge);
		response.addCookie(cookie);
	}
}

  6.3 登录页面

<%@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>


Home