單點登入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)
- <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.cloud.sso.server</groupId>
- <artifactId>sso-server</artifactId>
- <packaging>war</packaging>
- <version>0.0.1-SNAPSHOT</version>
- <name>sso-server Maven Webapp</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- <version>2.5</version>
- <scope>provided</scope>
- </dependency>
- </dependencies>
- <build>
- <finalName>sso-server</finalName>
- <plugins>
- <plugin>
- <groupId>org.mortbay.jetty</groupId>
- <artifactId>jetty-maven-plugin</artifactId>
- <version>8.1.9.v20130131</version>
- <configuration>
- <!-- 配置jetty的容器 埠等 -->
- <connectors>
- <connectorimplementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
- <port>8081</port>
- <maxIdleTime>30000</maxIdleTime>
- </connector>
- </connectors>
- <!-- 新增一個特殊的埠和控制鍵(mvn jetty:stop 停止jetty服務) -->
- <stopKey>stop</stopKey>
- <stopPort>9977</stopPort>
- <!-- 發現內容改變,進行熱部署,預設是0,不熱部署 -->
- <scanIntervalSeconds>1</scanIntervalSeconds>
- <!-- 配置web容器 -->
- <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>
- <webAppConfig>
- <!-- 專案的根目錄,預設是"/" -->
- <contextPath>/sso-server</contextPath>
- <!-- <descriptor></descriptor> --><!-- The path to the web.xml file for your webapp -->
- <!-- <defaultsDescriptor>src/main/resources/webdefault.xml</defaultsDescriptor>
- webdefault.xml的路徑,若沒有配置就是用jetty預設,這個檔案在web.xml載入之前載入 -->
- </webAppConfig>
- <!-- 自動部署預設是 automatic -->
- <reload>automatic</reload>
- <systemProperties>
- <systemProperty>
- <name>org.mortbay.util.URI.charset</name>
- <value>UTF-8</value>
- </systemProperty>
- </systemProperties>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </project>
SSO Server中的filter
- package com.cloud.sso.server.filter;
- import java.io.IOException;
- 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 com.cloud.sso.server.JVMCache;
- public class SSOServerFilter implements Filter {
- @Override
- public void destroy() {
- }
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
- String service = request.getParameter("service");
- //String ticket = request.getParameter("ticket");
- Cookie[] cookies = request.getCookies();
- String username = "";
- //判斷使用者是否已經登陸認證中心
- if (null != cookies) {
- for (Cookie cookie : cookies) {
- if ("sso".equals(cookie.getName())) {
- username = cookie.getValue();
- break;
- }
- }
- }
- // if (null == service && null != ticket) {
- // filterChain.doFilter(servletRequest, servletResponse);
- // return;
- // }
- //實現一處登入處處登入
- if (null != username && !"".equals(username)) {
- long time = System.currentTimeMillis();
- //生成認證憑據--ticket
- String ticket = username + time;
- JVMCache.TICKET_AND_NAME.put(ticket, username);
- StringBuilder url = new StringBuilder();
- url.append(service);
- if (0 <= service.indexOf("?")) {
- url.append("&");
- } else {
- url.append("?");
- }
- //返回給使用者一個認證的憑據--ticket
- url.append("ticket=").append(ticket);
- response.sendRedirect(url.toString());
- } else {
- filterChain.doFilter(servletRequest, servletResponse);
- }
- }
- @Override
- public void init(FilterConfig arg0) throws ServletException {
- }
- }
兩個servlet:
- package com.cloud.sso.server.servlet;
- import java.io.IOException;
- 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.cloud.sso.server.JVMCache;
- public class LoginServlet extends HttpServlet {
- private static final long serialVersionUID = -3170191388656385924L;
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- this.doPost(request, response);
- }
- @Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- String username = request.getParameter("username");
- String password = request.getParameter("password");
- String service = request.getParameter("service");
- //判斷使用者名稱和密碼是否正確
- if ("admin".equals(username) && "1111".equals(password)) {
- Cookie cookie = new Cookie("sso", username);
- cookie.setPath("/");
- response.addCookie(cookie);
- long times = System.currentTimeMillis();
- //生成認證憑據--ticket
- String ticket = username + times;
- JVMCache.TICKET_AND_NAME.put(ticket, username);
- if (null != service) {
- StringBuilder url = new StringBuilder();
- url.append(service);
- if (0 <= service.indexOf("?")) {
- url.append("&");
- } else {
- url.append("?");
- }
- //返回給使用者一個認證的憑據--ticket
- url.append("ticket=").append(ticket);
- response.sendRedirect(url.toString());
- } else {
- response.sendRedirect("/sso-server/index.jsp");
- }
- } else {
- response.sendRedirect("/sso-server/index.jsp?service=" + service);
- }
- }
- }
- package com.cloud.sso.server.servlet;
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.cloud.sso.server.JVMCache;
- /**
- * HttpClient呼叫這個Servlet獲取username
- * @author Hys
- *
- */
- public class TicketServlet extends HttpServlet {
- private static final long serialVersionUID = 5964206637772848290L;
- @Override
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- super.doGet(request, response);
- }
- @Override
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- String ticket = request.getParameter("ticket");
- String username = JVMCache.TICKET_AND_NAME.get(ticket);
- //保證一個ticket只會用一次
- JVMCache.TICKET_AND_NAME.remove(ticket);
- PrintWriter writer = response.getWriter();
- writer.write(username);
- }
- }
JVMCache.Java
- package com.cloud.sso.server;
- import java.util.HashMap;
- import java.util.Map;
- public class JVMCache {
- //存放username,再通過HttpClient獲取(在實際專案應該放到Memcached中)
- public static Map<String, String>TICKET_AND_NAME = new HashMap<String, String>();
- }
web.xml
- <?xmlversion="1.0"encoding="UTF-8"?>
- <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">
- <welcome-file-list>
- <welcome-file>index.jsp</welcome-file>
- </welcome-file-list>
- <filter>
- <filter-name>ssoServerFilter</filter-name>
- <filter-class>com.cloud.sso.server.filter.SSOServerFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>ssoServerFilter</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- <servlet>
- <servlet-name>login</servlet-name>
- <servlet-class>com.cloud.sso.server.servlet.LoginServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>login</servlet-name>
- <url-pattern>/user/login</url-pattern>
- </servlet-mapping>
- <servlet>
- <servlet-name>ticket</servlet-name>
- <servlet-class>com.cloud.sso.server.servlet.TicketServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>ticket</servlet-name>
- <url-pattern>/ticket</url-pattern>
- </servlet-mapping>
- </web-app>
下面是OA系統中的程式碼:
pom.xml
(注意:埠為8082)
- <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>com.cloud.sso.oa</groupId>
- <artifactId>sso-oa</artifactId>
- <packaging>war</packaging>
- <version>0.0.1-SNAPSHOT</version>
- <name>sso-oa Maven Webapp</name>
- <url>http://maven.apache.org</url>
- <dependencies>
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>servlet-api</artifactId>
- <version>2.5</version>
- <scope>provided</scope>
- </dependency>
- <dependency>
- <groupId>commons-httpclient</groupId>
- <artifactId>commons-httpclient</artifactId>
- <version>3.1</version>
- </dependency>
- </dependencies>
- <build>
- <finalName>sso-oa</finalName>
- <plugins>
- <plugin>
- <groupId>org.mortbay.jetty</groupId>
- <artifactId>jetty-maven-plugin</artifactId>
- <version>8.1.9.v20130131</version>
- <configuration>
- <!-- 配置jetty的容器 埠等 -->
- <connectors>
- <connectorimplementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
- <port>8082</port>
- <maxIdleTime>30000</maxIdleTime>
- </connector>
- </connectors>
- <!-- 新增一個特殊的埠和控制鍵(mvn jetty:stop 停止jetty服務) -->
- <stopKey>stop</stopKey>
- <stopPort>9966</stopPort>
- <!-- 發現內容改變,進行熱部署,預設是0,不熱部署 -->
- <scanIntervalSeconds>1</scanIntervalSeconds>
- <!-- 配置web容器 -->
- <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>
- <webAppConfig>
- <!-- 設定專案的虛擬根目錄,預設是"/" -->
- <contextPath>/sso-oa</contextPath>
- <!-- <descriptor></descriptor> --><!-- The path to the web.xml file for your webapp -->
- <!-- <defaultsDescriptor>src/main/resources/webdefault.xml</defaultsDescriptor>
- webdefault.xml的路徑,若沒有配置就是用jetty預設,這個檔案在web.xml載入之前載入 -->
- </webAppConfig>
- <!-- 自動部署預設是 automatic -->
- <reload>automatic</reload>
- <systemProperties>
- <systemProperty>
- <name>org.mortbay.util.URI.charset</name>
- <value>UTF-8</value>
- </systemProperty>
- </systemProperties>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </project>
OA系統中的filter:
SSOClientFilter.java
- package com.cloud.sso.oa.filter;
- import java.io.IOException;
- import java.net.URLEncoder;
- 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.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import javax.servlet.http.HttpSession;
- import org.apache.commons.httpclient.HttpClient;
- import org.apache.commons.httpclient.methods.PostMethod;
- public class SSOClientFilter implements Filter {
- @Override
- public void destroy() {
- }
- @Override
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) servletRequest;
- HttpServletResponse response = (HttpServletResponse) servletResponse;
- HttpSession session = request.getSession();
- String username = (String) session.getAttribute("username");
- String ticket = request.getParameter("ticket");
- String url = URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");
- System.out.println("使用者名稱1:"+username);
- //判斷使用者是否已經登入oa系統
- if (null == username) {
- //1.判斷使用者是否有認證憑據--ticket(認證中心生成)
- if (null != ticket && !"".equals(ticket)) {
- PostMethod postMethod = new PostMethod("http://localhost:8081/sso-server/ticket");
- //給url新增新的引數
- postMethod.addParameter("ticket", ticket);
- HttpClient httpClient = new HttpClient();
- try {
- //通過httpClient呼叫SSO Server中的TicektServlet
- httpClient.executeMethod(postMethod);
- //將HTTP方法的響應正文(如果有)返回為String
- username = postMethod.getResponseBodyAsString();
- //釋放此HTTP方法正在使用的連線
- postMethod.releaseConnection();
- } catch (Exception e) {
- e.printStackTrace();
- }
- //2.判斷認證憑據是否有效
- if (null != username && !"".equals(username)) {
- //session設定使用者名稱,說明使用者登入成功了
- session.setAttribute("username", username);
- filterChain.doFilter(request, response);
- } else {
- response.sendRedirect("http://localhost:8081/sso-server/index.jsp?service=" + url);
- }
- } else {//第一次訪問oa系統,需要到sso-server系統驗證
- response.sendRedirect("http://localhost:8081/sso-server/index.jsp?service=" + url);
- }
- } else {
- filterChain.doFilter(request, response);
- }
- }
- @Override
- public void init(FilterConfig arg0) throws ServletException {
- }
- }
OAServlet.java
- package com.cloud.sso.oa.servlet;
- import java.io.IOException;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- public class OAServlet extends HttpServlet {
- private static final long serialVersionUID = 3615122544373006252L;
- @Override