单点登陆02(现实篇)
要做个单点登陆的例子,要有以下: 1 用户身份认证服务器 2 两个应用系统。用户身份认证系统是提供认证功能和登陆功能的,应用系统是模拟跨域访问。项目结构如下图所示:
其中有三个项目: Client 、 Client2 、 Server 。 Client 和 Client2 是应用系统用于模拟跨域访问的;而 Server 系统是用户身份验证的。三个都是 Web 工程。既然是 web 工程,那么众所周知 Web 协议(也就是 HTTP )是一个无状态的协议。服务器是如何记录和保持登陆信息呢?
浏览器访问一次服务器,就和服务器建立一次 Sockect 连接当服务器响应完毕时,就会断口连接。第二次访问的时候模式一样。为了保存信息软件师们发明了两个东西: Session 和 Cookie 。 Session 是保持在服务端的会话信息, Cookie 是持久化在客户端的会话信息。浏览器每次访问 Web 站点时都把此站点相应的 Cookie 信息发送给服务器,服务器从 Cookie 中取相应的字段( sessionId ),然后根据字段在服务器的缓存中查找相应的 Session ,如果没查到则 Request 对象会自动新建一个 Session 对象。这些是基础没有这些知识很难理解如何实现 SSO 的。
首先建好上面三个工程,然后各自建相应的过滤器和页面(源码 )可以先下载下来导入 Eclipse 中,启动 Tomcat 服务器。
第一: client 端的 AuthFilter 代码
package org.hundsun.sso.filter.auth;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
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;
//登陆验证
public class AuthFilter implements Filter {
private String casUrl;
private String CASSERVER_URL;
private String SERVER_NAME;
private String VARERVER_URL;
private ConnectionUtil connectionUtil;
public void init(FilterConfig config) throws ServletException {
CASSERVER_URL=config.getInitParameter("casServer");
SERVER_NAME=config.getInitParameter("serverName");
VARERVER_URL=config.getInitParameter("varServer");
connectionUtil = new ConnectionUtil();
casUrl = new StringBuffer().append(CASSERVER_URL).append("?serverName=").append(SERVER_NAME).toString();
System.out.println(casUrl);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException {
HttpServletResponse response_ = (HttpServletResponse) response;
HttpServletRequest request_ = (HttpServletRequest) request;
//获得Ticket
String ticket = request.getParameter("ticket");
//转跳状态
boolean doFilter = false;
String CAS_USRR=(String)request_.getSession().getAttribute("CAS_USRR");
//如果已经登陆
if ( CAS_USRR!= null&&!"".equals(CAS_USRR)){
System.out.println("用户已经登录,转发用户请求");
System.out.println("CAS_USRR:"+CAS_USRR);
doFilter = true;
}
else
{
System.out.println("用户没有登录,验证用户ticket");
System.out.println("ticket详细如:"+ticket);
//Ticket不为空
String userName = null;
//解析用户信息
if (ticket != null && (userName = connectionUtil.getUserName(ticket)) != null) {
System.out.println("验证用户ticket成功!");
request_.getSession().setAttribute("CAS_USRR", userName);
doFilter = true;
}else{
System.out.println("验证用户ticket失败或ticket为空!");
}
}
if(doFilter)
chain.doFilter(request, response);
else {
//转跳到CAS服务器
System.out.println("用户没有登录且没携带ticket,转跳到CAS服务器登录验证!");
System.out.println("ticket验证地址:"+casUrl);
response_.sendRedirect(casUrl);
}
}
public void destroy() {
}
private class ConnectionUtil {
public String getUserName(String ticket) {
HttpURLConnection connection = null;
BufferedReader bfReader = null;
String userName = null;
try
{
//连接CAS验证服务器
System.out.println("连接CAS验证服务器!ticket:"+ticket);
String url=VARERVER_URL + "?ticket=" + ticket;
System.out.println("验证服务器URL:"+url);
connection = (HttpURLConnection) new URL(url).openConnection();
connection.connect();
bfReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
//获得解密后的用户名
userName = bfReader.readLine();
System.out.println("CAS验证服务器还回结果(userName):"+userName);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
finally
{
try
{
if(bfReader!=null)bfReader.close();
if(connection!=null)connection.connect();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
return userName;
}
}
}
这个是 client 端的验证用户是否登陆已经是否含有 tickect 信息的过滤器。
在上面的代码需要用到的参数在 web.xml 配置了,具体如下:
<?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>autoFilter</filter-name> <filter-class>org.hundsun.sso.filter.auth.AuthFilter</filter-class> <init-param> <param-name>casServer</param-name> <param-value>http://127.0.0.1:8888/Server/login</param-value> <description>认证服务器地址</description> </init-param> <init-param> <param-name>varServer</param-name> <param-value>http://127.0.0.1:8888/Server/server</param-value> <description>服务器地址</description> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://127.0.0.1:8888/Client/welcome.jsp</param-value> <description>跳转页面</description> </init-param> </filter> <filter-mapping> <filter-name>autoFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
其次也就是用户验证服务,有 LoginServer 和 ValidationServer 两个 Servlet , LoginServer 是检验用户 cookies 中是否含有 tickect ,然后重定向到验证服务中,而 ValidationServer 就是验证服务。 认证服务的配置信息在 web.xml 中:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <listener> <listener-class>org.hundsun.sso.listener.KeyListener</listener-class> </listener> <servlet> <servlet-name>LoginServer</servlet-name> <servlet-class>org.hundsun.sso.service.LoginServer</servlet-class> </servlet> <servlet> <servlet-name>ValidationServer</servlet-name> <servlet-class>org.hundsun.sso.service.ValidationServer</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServer</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>ValidationServer</servlet-name> <url-pattern>/server</url-pattern> </servlet-mapping> </web-app>
LoginServer 代码:
public class LoginServer extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 发出请求的Client
String serverName = request.getParameter("serverName");
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies)
{
if (cookie.getName().equals("CAS")) {
// 生成Ticket
String ticket = "CAS" + ":" + cookie.getValue();
String url = new StringBuffer().append(serverName).append("?ticket=").append(ticket).toString();
response.sendRedirect(url);
return;
}
}
}
request.getRequestDispatcher("/WEB-INF/login.jsp").forward(request, response);
return;
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
ValidationServer 代码:
import org.hundsun.sso.utiil.RSAUtil;
//验证服务
public class ValidationServer extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String ticket = request.getParameter("ticket");
String serverName = request.getParameter("serverName");
String name = request.getParameter("name");
String pass = request.getParameter("pass");
//优先验证ticket信息
if(ticket!=null)
{
try {
System.out.println("收的的ticket为:"+ticket);
String[] result = ticket.split(":");
//验证服务器信息和密码
System.out.println("验证密码:"+result[0]);
System.out.println("验证信息:"+result[1]);
response.getWriter().print(result[1]);
return;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//验证表单登录信息
else
{
System.out.println("name:"+name);
System.out.println("pass"+pass);
//登陆成功后将信息保存到Cookie
Cookie cookie = new Cookie("CAS", name);
cookie.setMaxAge(-1);
response.addCookie(cookie);
String url = new StringBuffer().append(serverName).append("?ticket=").append("CAS" + ":" + name).toString();
response.sendRedirect(url);
return;
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doGet(request,response);
}
}
以上是代码,具体的流程序列图如:
运行下可以实现了传说中的 SSO 了。