自己动手写SSO(单点登录)
来源:互联网 发布:淘宝童装店铺简介 编辑:程序博客网 时间:2024/06/08 03:25
一、什么是单点登录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)
<project xmlns="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><connector implementation="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
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns: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)
<project xmlns="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><connector implementation="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 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("/WEB-INF/jsp/welcome.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } }
web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns: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>ssoClientFilter</filter-name> <filter-class>com.cloud.sso.oa.filter.SSOClientFilter</filter-class> </filter> <filter-mapping> <filter-name>ssoClientFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping><servlet> <servlet-name>list</servlet-name> <servlet-class>com.cloud.sso.oa.servlet.OAServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>list</servlet-name> <url-pattern>/list</url-pattern> </servlet-mapping> </web-app>
下面是PRO系统的代码:
pom.xml
(注意:端口为8083)
<project xmlns="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.pro</groupId> <artifactId>sso-pro</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>sso-pro 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-pro</finalName> <plugins><plugin><groupId>org.mortbay.jetty</groupId><artifactId>jetty-maven-plugin</artifactId><version>8.1.9.v20130131</version><configuration><!-- 配置jetty的容器 端口等 --><connectors><connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector"><port>8083</port><maxIdleTime>30000</maxIdleTime></connector></connectors><!-- 添加一个特殊的端口和控制键(mvn jetty:stop 停止jetty服务) --><stopKey>stop</stopKey> <stopPort>6000</stopPort> <!-- 发现内容改变,进行热部署,默认是0,不热部署 --><scanIntervalSeconds>1</scanIntervalSeconds><!-- 配置web容器 --><webAppSourceDirectory>src/main/webapp</webAppSourceDirectory><webAppConfig><!-- 设置项目的虚拟根目录,默认是"/" --><contextPath>/sso-pro</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>
PRO系统中的filter
SSOClientFilter.java
package com.cloud.sso.pro.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"); if (null == username) { if (null != ticket && !"".equals(ticket)) { PostMethod postMethod = new PostMethod("http://localhost:8081/sso-server/ticket"); postMethod.addParameter("ticket", ticket); HttpClient httpClient = new HttpClient(); try { httpClient.executeMethod(postMethod); username = postMethod.getResponseBodyAsString(); postMethod.releaseConnection(); } catch (Exception e) { e.printStackTrace(); } if (null != username && !"".equals(username)) { session.setAttribute("username", username); filterChain.doFilter(request, response); } else { response.sendRedirect("http://localhost:8081/sso-server/index.jsp?service=" + url); } } else { response.sendRedirect("http://localhost:8081/sso-server/index.jsp?service=" + url); } } else { filterChain.doFilter(request, response); } } @Override public void init(FilterConfig arg0) throws ServletException { }}
ProServlet.java
package com.cloud.sso.pro.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 ProServlet extends HttpServlet { private static final long serialVersionUID = -1334093914490423930L; @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("/WEB-INF/jsp/welcome.jsp").forward(request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doPost(request, response); }}
web.xml
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns: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>ssoClientFilter</filter-name> <filter-class>com.cloud.sso.pro.filter.SSOClientFilter</filter-class> </filter> <filter-mapping> <filter-name>ssoClientFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>list</servlet-name> <servlet-class>com.cloud.sso.pro.servlet.ProServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>list</servlet-name> <url-pattern>/list</url-pattern> </servlet-mapping></web-app>
运行结果:
1:分别启动这三个工程。
2:访问OA系统,URL:http://localhost:8082/oa/list
3:这样到登录页面,如图:
4:用户名为:admin,密码为:1111,点击登录则会显示,如图:
5:然后去进入PRO系统,URL:http://localhost:8083/pro/list,则就不需要登录了,直接进入,如图:
奉上源码:http://pan.baidu.com/s/1qYHbhRe
- 自己动手写SSO(单点登录)
- 自己动手写SSO(单点登录)
- 自己动手写SSO(单点登录)
- 自己动手写SSO(单点登录)
- 自己动手写SSO(单点登录)
- 单点登录(SSO)
- 单点登录(sso)
- 单点登录(SSO)
- 单点登录方案(SSO)
- 单点登录(SSO)服务
- 单点登录(SSO)服务
- 单点登录(SSO)简介
- 单点登录(SSO)服务
- SSO单点登录(转)
- 单点登录(SSO)服务
- 单点登录(SSO)原理
- sso(单点登录一)
- 漫谈单点登录(SSO)
- enote笔记语言(2)(ver0.2)
- Java中的阻塞队列总结
- 异常好用的分布式版本控制软件Git入门
- HTTP 错误 403.14
- netty常用数据结构备忘
- 自己动手写SSO(单点登录)
- 仿RecycleView.Adapter封装BaseAdapter
- LeetCode 43. Multiply Strings
- sql注入--入门篇
- 弱校联盟#con3--概率(阶乘逆元打表)
- Angular知识点梳理
- 设计模式博文索引
- 设计模式概述
- 排序算法之归并排序