1. 程式人生 > >單點登入demo

單點登入demo

轉載:http://blog.csdn.net/xqhys/article/details/63920161?locationNum=3&fps=1

一、什麼是單點登入SSO(Single Sign-On)

  SSO是一種統一認證和授權機制,指訪問同一伺服器不同應用中的受保護資源的同一使用者,只需要登入一次,即通過一個應用中的安全驗證後,再訪問其他應用中的受保護資源時,不再需要重新登入驗證。

二、單點登入解決了什麼問題

解決了使用者只需要登入一次就可以訪問所有相互信任的應用系統,而不用重複登入

工程說明

SSO的實現一般會有一個SSO Server,也叫認證系統,同時也會有被認證的系統,如OA系統、採購系統等,他們就相當於SSO Server的client。

為了更形象體現SSO,我寫的SSO是有三個工程:一個SSO Server埠為8081,一個OA系統埠為8082,一個採購系統埠為8083。如圖:

 
  流程介紹

在整個SSO流程當,有兩個流程非常重要,第一個是使用者沒有登入系統到登入系統的過程;第二是使用者在一個系統當中已經登入(例如在OA系統中登入 了),但又想進入另一個系統(例如進入PRO系統)的過程,如果把這兩個過程搞定了,那麼SSO也就搞定了。我畫了兩幅圖來說明這兩個過程。

先看使用者沒有登入系統到登入系統的過程,如圖:


 
 1:使用者通過URL訪問OA系統。

 2:在OA系統中的filter發現這個URL沒有ticket(你暫且就把ticket看做是門票),此時就會跳轉到SSO Server。

 3:SSO Server中的filter發現該客戶端中的cookie中沒有相應資訊,也即是一個沒有登入的使用者,那麼會跳轉到登入頁面。

 4:使用者在登入頁面填寫相應資訊,然後通過post方式提交到SSO Server中。

 5:SSO Server會校驗使用者資訊(我為了快,我的校驗方式就是要使用者名稱為:admin,同時密碼為:1111),同時在cookie中放username。

 6:將生成ticket和username放到JVMCache中,在實際專案應該放到Memcached中,它的用處等下分析。

7,8:就是在使用者訪問OA系統的URL基礎上加上了一個ticket引數,這樣跳轉到OA系統。

(此時進入OA系統時,filter發現URL是帶ticket的,則filter會根據帶過來的ticket並通過HttpClient的形式去呼叫SSO Server中的TicektServlet,這樣就會返回使用者名稱,其實這個使用者名稱就是從JVMCache拿到的,同時馬上將這個ticket從JVMCache中移除,這樣保證一個ticket只會用一次,然後把返回的使用者名稱放到session中)

 9:session中有了使用者名稱,說明使用者登入成功了,則會去本應該返問的servlet。

10,11:將OA系統返回的檢視給使用者。

第二過程,使用者已經登入成功了,但要訪問另一個系統,如圖:


 
  1:使用者通過URL訪問PRO系統。

  2:在PRO系統中的filter發現這個URL沒有ticket,此時就會跳轉到SSO Server。此時,由於使用者登入了,所以cookie中有相應的資訊(例如使用者名稱),此時SSO Server中的filter會生成一個ticket。

 3:將生成的ticket和username放到JVMCache中。

 4:就是在使用者訪問PRO系統的URL基礎上加上了一個ticket引數,這樣跳轉到PRO系統。

(此時進入PRO系統時,filter發現URL是帶ticket的,則filter會根據帶過來的ticket並通過HttpClient的形式去呼叫SSO Server中的TicektServlet,這樣就會返回使用者名稱,其實這個使用者名稱就是從JVMCache拿到的,同時馬上將這個ticket從JVMCache中移除,這樣保證一個ticket只會用一次,然後把返回的使用者名稱放到session中)

 5:session中有了使用者名稱,說明使用者登入成功了,則會去本應該返問的servlet。

5,7:將PRO系統返回的檢視給使用者。

關鍵程式碼

先看SSO Server工程中的程式碼:

pom.xml

(注意:埠為8081)

  1. <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3.     <modelVersion>4.0.0</modelVersion>
  4.     <groupId>com.cloud.sso.server</groupId>
  5.     <artifactId>sso-server</artifactId>
  6.     <packaging>war</packaging>
  7.     <version>0.0.1-SNAPSHOT</version>
  8.     <name>sso-server Maven Webapp</name>
  9.     <url>http://maven.apache.org</url>
  10.     <dependencies>
  11.         <dependency>
  12.             <groupId>javax.servlet</groupId>
  13.             <artifactId>servlet-api</artifactId>
  14.             <version>2.5</version>
  15.             <scope>provided</scope>
  16.         </dependency>
  17.     </dependencies>
  18.     <build>
  19.         <finalName>sso-server</finalName>
  20.         <plugins>
  21.             <plugin>
  22.                 <groupId>org.mortbay.jetty</groupId>
  23.                 <artifactId>jetty-maven-plugin</artifactId>
  24.                 <version>8.1.9.v20130131</version>
  25.                 <configuration>
  26.                     <!-- 配置jetty的容器 埠等 -->
  27.                     <connectors>
  28.                         <connectorimplementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
  29.                             <port>8081</port>
  30.                             <maxIdleTime>30000</maxIdleTime>
  31.                         </connector>
  32.                     </connectors>
  33.                     <!-- 新增一個特殊的埠和控制鍵(mvn jetty:stop 停止jetty服務) -->
  34.                     <stopKey>stop</stopKey>
  35.                     <stopPort>9977</stopPort>
  36.                     <!-- 發現內容改變,進行熱部署,預設是0,不熱部署 -->
  37.                     <scanIntervalSeconds>1</scanIntervalSeconds>
  38.                     <!-- 配置web容器 -->
  39.                     <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>
  40.                     <webAppConfig>
  41.                         <!-- 專案的根目錄,預設是"/" -->
  42.                         <contextPath>/sso-server</contextPath>
  43.                         <!-- <descriptor></descriptor> --><!-- The path to the web.xml file for your webapp -->
  44.                         <!-- <defaultsDescriptor>src/main/resources/webdefault.xml</defaultsDescriptor>
  45.                             webdefault.xml的路徑,若沒有配置就是用jetty預設,這個檔案在web.xml載入之前載入 -->
  46.                     </webAppConfig>
  47.                     <!-- 自動部署預設是 automatic -->
  48.                     <reload>automatic</reload>
  49.                     <systemProperties>
  50.                         <systemProperty>
  51.                             <name>org.mortbay.util.URI.charset</name>
  52.                             <value>UTF-8</value>
  53.                         </systemProperty>
  54.                     </systemProperties>
  55.                 </configuration>
  56.             </plugin>
  57.         </plugins>
  58.     </build>
  59. </project>

SSO Server中的filter

  1. package com.cloud.sso.server.filter;  
  2. import java.io.IOException;  
  3. import javax.servlet.Filter;  
  4. import javax.servlet.FilterChain;  
  5. import javax.servlet.FilterConfig;  
  6. import javax.servlet.ServletException;  
  7. import javax.servlet.ServletRequest;  
  8. import javax.servlet.ServletResponse;  
  9. import javax.servlet.http.Cookie;  
  10. import javax.servlet.http.HttpServletRequest;  
  11. import javax.servlet.http.HttpServletResponse;  
  12. import com.cloud.sso.server.JVMCache;  
  13. public class SSOServerFilter implements Filter {  
  14.     @Override  
  15.     public void destroy() {  
  16.     }  
  17.     @Override  
  18.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
  19.         HttpServletRequest request = (HttpServletRequest) servletRequest;  
  20.         HttpServletResponse response = (HttpServletResponse) servletResponse;  
  21.         String service = request.getParameter("service");  
  22.         //String ticket = request.getParameter("ticket");  
  23.         Cookie[] cookies = request.getCookies();  
  24.         String username = "";  
  25.         //判斷使用者是否已經登陸認證中心  
  26.         if (null != cookies) {  
  27.             for (Cookie cookie : cookies) {   
  28.                 if ("sso".equals(cookie.getName())) {  
  29.                     username = cookie.getValue();                
  30.                     break;  
  31.                 }  
  32.             }  
  33.         }  
  34. //        if (null == service && null != ticket) {  
  35. //            filterChain.doFilter(servletRequest, servletResponse);  
  36. //            return;  
  37. //        }  
  38.         //實現一處登入處處登入  
  39.         if (null != username && !"".equals(username)) {  
  40.             long time = System.currentTimeMillis();  
  41.             //生成認證憑據--ticket  
  42.             String ticket = username + time;  
  43.             JVMCache.TICKET_AND_NAME.put(ticket, username);  
  44.             StringBuilder url = new StringBuilder();  
  45.             url.append(service);  
  46.             if (0 <= service.indexOf("?")) {  
  47.                 url.append("&");  
  48.             } else {  
  49.                 url.append("?");  
  50.             }  
  51.             //返回給使用者一個認證的憑據--ticket  
  52.             url.append("ticket=").append(ticket);  
  53.             response.sendRedirect(url.toString());  
  54.         } else {  
  55.             filterChain.doFilter(servletRequest, servletResponse);  
  56.         }  
  57.     }  
  58.     @Override  
  59.     public void init(FilterConfig arg0) throws ServletException {  
  60.     }  
  61. }  

兩個servlet:

  1. package com.cloud.sso.server.servlet;  
  2. import java.io.IOException;  
  3. import javax.servlet.ServletException;  
  4. import javax.servlet.http.Cookie;  
  5. import javax.servlet.http.HttpServlet;  
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8. import com.cloud.sso.server.JVMCache;  
  9. public class LoginServlet extends HttpServlet {  
  10.     private static final long serialVersionUID = -3170191388656385924L;  
  11.     @Override  
  12.     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  13.         this.doPost(request, response);  
  14.     }  
  15.     @Override  
  16.     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  17.         String username = request.getParameter("username");  
  18.         String password = request.getParameter("password");  
  19.         String service = request.getParameter("service");  
  20.         //判斷使用者名稱和密碼是否正確  
  21.         if ("admin".equals(username) && "1111".equals(password)) {  
  22.             Cookie cookie = new Cookie("sso", username);  
  23.             cookie.setPath("/");  
  24.             response.addCookie(cookie);  
  25.             long times = System.currentTimeMillis();  
  26.             //生成認證憑據--ticket  
  27.             String ticket = username + times;  
  28.             JVMCache.TICKET_AND_NAME.put(ticket, username);  
  29.             if (null != service) {  
  30.                 StringBuilder url = new StringBuilder();  
  31.                 url.append(service);  
  32.                 if (0 <= service.indexOf("?")) {  
  33.                     url.append("&");  
  34.                 } else {  
  35.                     url.append("?");  
  36.                 }  
  37.                 //返回給使用者一個認證的憑據--ticket  
  38.                 url.append("ticket=").append(ticket);  
  39.                 response.sendRedirect(url.toString());  
  40.             } else {  
  41.                 response.sendRedirect("/sso-server/index.jsp");  
  42.             }  
  43.         } else {  
  44.             response.sendRedirect("/sso-server/index.jsp?service=" + service);  
  45.         }  
  46.     }  
  47. }  
  1. package com.cloud.sso.server.servlet;  
  2. import java.io.IOException;  
  3. import java.io.PrintWriter;  
  4. import javax.servlet.ServletException;  
  5. import javax.servlet.http.HttpServlet;  
  6. import javax.servlet.http.HttpServletRequest;  
  7. import javax.servlet.http.HttpServletResponse;  
  8. import com.cloud.sso.server.JVMCache;  
  9. /**  
  10.  * HttpClient呼叫這個Servlet獲取username  
  11.  * @author Hys  
  12.  *  
  13.  */  
  14. public class TicketServlet extends HttpServlet {  
  15.     private static final long serialVersionUID = 5964206637772848290L;  
  16.     @Override  
  17.     protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  18.         super.doGet(request, response);  
  19.     }  
  20.     @Override  
  21.     protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {  
  22.         String ticket = request.getParameter("ticket");  
  23.         String username = JVMCache.TICKET_AND_NAME.get(ticket);  
  24.         //保證一個ticket只會用一次  
  25.         JVMCache.TICKET_AND_NAME.remove(ticket);  
  26.         PrintWriter writer = response.getWriter();  
  27.         writer.write(username);  
  28.     }  
  29. }  

JVMCache.Java

  1. package com.cloud.sso.server;  
  2. import java.util.HashMap;  
  3. import java.util.Map;  
  4. public class JVMCache {  
  5.     //存放username,再通過HttpClient獲取(在實際專案應該放到Memcached中)  
  6.     public static Map<String, String>TICKET_AND_NAME = new HashMap<String, String>();  
  7. }  

web.xml

  1. <?xmlversion="1.0"encoding="UTF-8"?>
  2. <web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:jsp="http://java.sun.com/xml/ns/javaee/jsp"xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"id="WebApp_ID"version="2.5">
  3.     <welcome-file-list>
  4.         <welcome-file>index.jsp</welcome-file>
  5.     </welcome-file-list>
  6.     <filter>
  7.         <filter-name>ssoServerFilter</filter-name>
  8.         <filter-class>com.cloud.sso.server.filter.SSOServerFilter</filter-class>
  9.     </filter>
  10.     <filter-mapping>
  11.         <filter-name>ssoServerFilter</filter-name>
  12.         <url-pattern>/*</url-pattern>
  13.     </filter-mapping>
  14.     <servlet>
  15.         <servlet-name>login</servlet-name>
  16.         <servlet-class>com.cloud.sso.server.servlet.LoginServlet</servlet-class>
  17.     </servlet>
  18.     <servlet-mapping>
  19.         <servlet-name>login</servlet-name>
  20.         <url-pattern>/user/login</url-pattern>
  21.     </servlet-mapping>
  22.     <servlet>
  23.         <servlet-name>ticket</servlet-name>
  24.         <servlet-class>com.cloud.sso.server.servlet.TicketServlet</servlet-class>
  25.     </servlet>
  26.     <servlet-mapping>
  27.         <servlet-name>ticket</servlet-name>
  28.         <url-pattern>/ticket</url-pattern>
  29.     </servlet-mapping>
  30. </web-app>

下面是OA系統中的程式碼:

pom.xml

(注意:埠為8082)

  1. <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2.     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  3.     <modelVersion>4.0.0</modelVersion>
  4.     <groupId>com.cloud.sso.oa</groupId>
  5.     <artifactId>sso-oa</artifactId>
  6.     <packaging>war</packaging>
  7.     <version>0.0.1-SNAPSHOT</version>
  8.     <name>sso-oa Maven Webapp</name>
  9.     <url>http://maven.apache.org</url>
  10.     <dependencies>
  11.         <dependency>
  12.             <groupId>javax.servlet</groupId>
  13.             <artifactId>servlet-api</artifactId>
  14.             <version>2.5</version>
  15.             <scope>provided</scope>
  16.         </dependency>
  17.         <dependency>
  18.             <groupId>commons-httpclient</groupId>
  19.             <artifactId>commons-httpclient</artifactId>
  20.             <version>3.1</version>
  21.         </dependency>
  22.     </dependencies>
  23.     <build>
  24.         <finalName>sso-oa</finalName>
  25.         <plugins>
  26.             <plugin>
  27.                 <groupId>org.mortbay.jetty</groupId>
  28.                 <artifactId>jetty-maven-plugin</artifactId>
  29.                 <version>8.1.9.v20130131</version>
  30.                 <configuration>
  31.                     <!-- 配置jetty的容器 埠等 -->
  32.                     <connectors>
  33.                         <connectorimplementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
  34.                             <port>8082</port>
  35.                             <maxIdleTime>30000</maxIdleTime>
  36.                         </connector>
  37.                     </connectors>
  38.                     <!-- 新增一個特殊的埠和控制鍵(mvn jetty:stop 停止jetty服務) -->
  39.                     <stopKey>stop</stopKey>
  40.                     <stopPort>9966</stopPort>
  41.                     <!-- 發現內容改變,進行熱部署,預設是0,不熱部署 -->
  42.                     <scanIntervalSeconds>1</scanIntervalSeconds>
  43.                     <!-- 配置web容器 -->
  44.                     <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>
  45.                     <webAppConfig>
  46.                         <!-- 設定專案的虛擬根目錄,預設是"/" -->
  47.                         <contextPath>/sso-oa</contextPath>
  48.                         <!-- <descriptor></descriptor> --><!-- The path to the web.xml file for your webapp -->
  49.                         <!-- <defaultsDescriptor>src/main/resources/webdefault.xml</defaultsDescriptor>
  50.                             webdefault.xml的路徑,若沒有配置就是用jetty預設,這個檔案在web.xml載入之前載入 -->
  51.                     </webAppConfig>
  52.                     <!-- 自動部署預設是 automatic -->
  53.                     <reload>automatic</reload>
  54.                     <systemProperties>
  55.                         <systemProperty>
  56.                             <name>org.mortbay.util.URI.charset</name>
  57.                             <value>UTF-8</value>
  58.                         </systemProperty>
  59.                     </systemProperties>
  60.                 </configuration>
  61.             </plugin>
  62.         </plugins>
  63.     </build>
  64. </project>

OA系統中的filter:

SSOClientFilter.java

  1. package com.cloud.sso.oa.filter;  
  2. import java.io.IOException;  
  3. import java.net.URLEncoder;  
  4. import javax.servlet.Filter;  
  5. import javax.servlet.FilterChain;  
  6. import javax.servlet.FilterConfig;  
  7. import javax.servlet.ServletException;  
  8. import javax.servlet.ServletRequest;  
  9. import javax.servlet.ServletResponse;  
  10. import javax.servlet.http.HttpServletRequest;  
  11. import javax.servlet.http.HttpServletResponse;  
  12. import javax.servlet.http.HttpSession;  
  13. import org.apache.commons.httpclient.HttpClient;  
  14. import org.apache.commons.httpclient.methods.PostMethod;  
  15. public class SSOClientFilter implements Filter {  
  16.     @Override  
  17.     public void destroy() {  
  18.     }  
  19.     @Override  
  20.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
  21.         HttpServletRequest request = (HttpServletRequest) servletRequest;  
  22.         HttpServletResponse response = (HttpServletResponse) servletResponse;  
  23.         HttpSession session = request.getSession();  
  24.         String username = (String) session.getAttribute("username");  
  25.         String ticket = request.getParameter("ticket");  
  26.         String url = URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");  
  27.         System.out.println("使用者名稱1:"+username);  
  28.         //判斷使用者是否已經登入oa系統  
  29.         if (null == username) {           
  30.             //1.判斷使用者是否有認證憑據--ticket(認證中心生成)  
  31.             if (null != ticket && !"".equals(ticket)) {  
  32.                 PostMethod postMethod = new PostMethod("http://localhost:8081/sso-server/ticket");  
  33.                 //給url新增新的引數  
  34.                 postMethod.addParameter("ticket", ticket);  
  35.                 HttpClient httpClient = new HttpClient();  
  36.                 try {  
  37.                     //通過httpClient呼叫SSO Server中的TicektServlet  
  38.                     httpClient.executeMethod(postMethod);  
  39.                     //將HTTP方法的響應正文(如果有)返回為String  
  40.                     username = postMethod.getResponseBodyAsString();  
  41.                     //釋放此HTTP方法正在使用的連線  
  42.                     postMethod.releaseConnection();  
  43.                 } catch (Exception e) {  
  44.                     e.printStackTrace();  
  45.                 }  
  46.                 //2.判斷認證憑據是否有效  
  47.                 if (null != username && !"".equals(username)) {  
  48.                     //session設定使用者名稱,說明使用者登入成功了  
  49.                     session.setAttribute("username", username);  
  50.                     filterChain.doFilter(request, response);  
  51.                 } else {  
  52.                     response.sendRedirect("http://localhost:8081/sso-server/index.jsp?service=" + url);  
  53.                 }  
  54.             } else {//第一次訪問oa系統,需要到sso-server系統驗證  
  55.                 response.sendRedirect("http://localhost:8081/sso-server/index.jsp?service=" + url);  
  56.             }  
  57.         } else {  
  58.             filterChain.doFilter(request, response);  
  59.         }  
  60.     }  
  61.     @Override  
  62.     public void init(FilterConfig arg0) throws ServletException {  
  63.     }  
  64. }  

OAServlet.java

  1. package com.cloud.sso.oa.servlet;  
  2. import java.io.IOException;  
  3. import javax.servlet.ServletException;  
  4. import javax.servlet.http.HttpServlet;  
  5. import javax.servlet.http.HttpServletRequest;  
  6. import javax.servlet.http.HttpServletResponse;  
  7. public class OAServlet extends HttpServlet {  
  8.     private static final long serialVersionUID = 3615122544373006252L;  
  9.     @Override