2022年 11月 4日

自己动手写SSO(单点登录)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

SSO在我们的应用中非常常见,例如我们在OA系统登录了,我们就可以直接进入采购系统,不需要再登录了,这样使我们非常方便。现在网上也有很多实现方法,于是乎我也想写一个看看。我主要用到的是cookie的机制。在此,分享给大家, 同时提供源代码下载 。

进入主题:

工程说明

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会校验用户信息(我为了快,我的校验方式就是要用户名为:cloud,同时密码为:cloud),同时在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)

4.0.0com.cloud.sso.serversso-serverwar0.0.1-SNAPSHOTsso-server Maven Webapphttp://maven.apache.orgjavax.servletservlet-api2.5providedsso-serverorg.mortbay.jettyjetty-maven-plugin8.1.9.v20130131stop6000/sso4808160000

SSO Server中的filter

SSOServerFilter.java

  1. 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() {
  2. } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  3. HttpServletRequest request = (HttpServletRequest) servletRequest;
  4. HttpServletResponse response = (HttpServletResponse) servletResponse;
  5. String service = request.getParameter("service");
  6. String ticket = request.getParameter("ticket");
  7. Cookie[] cookies = request.getCookies();
  8. String username = ""; if (null != cookies) { for (Cookie cookie : cookies) { if ("sso".equals(cookie.getName())) {
  9. username = cookie.getValue(); break;
  10. }
  11. }
  12. } if (null == service && null != ticket) {
  13. filterChain.doFilter(servletRequest, servletResponse); return;
  14. } if (null != username && !"".equals(username)) { long time = System.currentTimeMillis();
  15. String timeString = username + time;
  16. JVMCache.TICKET_AND_NAME.put(timeString, username);
  17. StringBuilder url = new StringBuilder();
  18. url.append(service); if (0 <= service.indexOf("?")) {
  19. url.append("&");
  20. } else {
  21. url.append("?");
  22. }
  23. url.append("ticket=").append(timeString);
  24. response.sendRedirect(url.toString());
  25. } else {
  26. filterChain.doFilter(servletRequest, servletResponse);
  27. }
  28. } @Override public void init(FilterConfig arg0) throws ServletException {
  29. }
  30. }

两个servlet:

LoginServlet.java

  1. 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);
  2. } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  3. String username = request.getParameter("username");
  4. String password = request.getParameter("password");
  5. String service = request.getParameter("service"); if ("cloud".equals(username) && "cloud".equals(password)) {
  6. Cookie cookie = new Cookie("sso", username);
  7. cookie.setPath("/");
  8. response.addCookie(cookie); long time = System.currentTimeMillis();
  9. String timeString = username + time;
  10. JVMCache.TICKET_AND_NAME.put(timeString, username); if (null != service) {
  11. StringBuilder url = new StringBuilder();
  12. url.append(service); if (0 <= service.indexOf("?")) {
  13. url.append("&");
  14. } else {
  15. url.append("?");
  16. }
  17. url.append("ticket=").append(timeString);
  18. response.sendRedirect(url.toString());
  19. } else {
  20. response.sendRedirect("/sso/index.jsp");
  21. }
  22. } else {
  23. response.sendRedirect("/sso/index.jsp?service=" + service);
  24. }
  25. }
  26. }

 TicketServlet.java

  1. 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; 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);
  2. } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  3. String ticket = request.getParameter("ticket");
  4. String username = JVMCache.TICKET_AND_NAME.get(ticket);
  5. JVMCache.TICKET_AND_NAME.remove(ticket);
  6. PrintWriter writer = response.getWriter();
  7. writer.write(username);
  8. }
  9. }

JVMCache.java

  1. package com.cloud.sso.server; import java.util.HashMap; import java.util.Map; public class JVMCache { public static Map TICKET_AND_NAME = new HashMap();
  2. }

web.xml

 index.jspssoServerFiltercom.cloud.sso.server.filter.SSOServerFilterssoServerFilter/*  logincom.cloud.sso.server.servlet.LoginServletlogin/user/loginticketcom.cloud.sso.server.servlet.TicketServletticket/ticket

下面是OA系统中的代码:

pom.xml

(注意:端口为8082)

4.0.0com.cloud.sso.oasso-oawar0.0.1-SNAPSHOTsso-oa Maven Webapphttp://maven.apache.orgjavax.servletservlet-api2.5providedcommons-httpclientcommons-httpclient3.1sso-oaorg.mortbay.jettyjetty-maven-plugin8.1.9.v20130131stop6000/oa4808260000

OA系统中的filter:

SSOClientFilter.java

  1. 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() {
  2. } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  3. HttpServletRequest request = (HttpServletRequest) servletRequest;
  4. HttpServletResponse response = (HttpServletResponse) servletResponse;
  5. HttpSession session = request.getSession();
  6. String username = (String) session.getAttribute("username");
  7. String ticket = request.getParameter("ticket");
  8. String url = URLEncoder.encode(request.getRequestURL().toString(), "UTF-8"); if (null == username) { if (null != ticket && !"".equals(ticket)) {
  9. PostMethod postMethod = new PostMethod("http://localhost:8081/sso/ticket");
  10. postMethod.addParameter("ticket", ticket);
  11. HttpClient httpClient = new HttpClient(); try {
  12. httpClient.executeMethod(postMethod);
  13. username = postMethod.getResponseBodyAsString();
  14. postMethod.releaseConnection();
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. } if (null != username && !"".equals(username)) {
  18. session.setAttribute("username", username);
  19. filterChain.doFilter(request, response);
  20. } else {
  21. response.sendRedirect("http://localhost:8081/sso/index.jsp?service=" + url);
  22. }
  23. } else {
  24. response.sendRedirect("http://localhost:8081/sso/index.jsp?service=" + url);
  25. }
  26. } else {
  27. filterChain.doFilter(request, response);
  28. }
  29. } @Override public void init(FilterConfig arg0) throws ServletException {
  30. }
  31. }

OAServlet.java

  1. 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 {
  2. request.getRequestDispatcher("/WEB-INF/jsp/welcome.jsp").forward(request, response);
  3. } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response);
  4. }
  5. }

web.xml

 index.jspssoClientFiltercom.cloud.sso.oa.filter.SSOClientFilterssoClientFilter/*  listcom.cloud.sso.oa.servlet.OAServletlist/list

下面是PRO系统的代码:

pom.xml

(注意:端口为8083)

4.0.0com.cloud.sso.prosso-prowar0.0.1-SNAPSHOTsso-pro Maven Webapphttp://maven.apache.orgjavax.servletservlet-api2.5providedcommons-httpclientcommons-httpclient3.1sso-proorg.mortbay.jettyjetty-maven-plugin8.1.9.v20130131stop6000/pro4808360000

PRO系统中的filter

SSOClientFilter.java

  1. 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() {
  2. } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  3. HttpServletRequest request = (HttpServletRequest) servletRequest;
  4. HttpServletResponse response = (HttpServletResponse) servletResponse;
  5. HttpSession session = request.getSession();
  6. String username = (String) session.getAttribute("username");
  7. String ticket = request.getParameter("ticket");
  8. String url = URLEncoder.encode(request.getRequestURL().toString(), "UTF-8"); if (null == username) { if (null != ticket && !"".equals(ticket)) {
  9. PostMethod postMethod = new PostMethod("http://localhost:8081/sso/ticket");
  10. postMethod.addParameter("ticket", ticket);
  11. HttpClient httpClient = new HttpClient(); try {
  12. httpClient.executeMethod(postMethod);
  13. username = postMethod.getResponseBodyAsString();
  14. postMethod.releaseConnection();
  15. } catch (Exception e) {
  16. e.printStackTrace();
  17. } if (null != username && !"".equals(username)) {
  18. session.setAttribute("username", username);
  19. filterChain.doFilter(request, response);
  20. } else {
  21. response.sendRedirect("http://localhost:8081/sso/index.jsp?service=" + url);
  22. }
  23. } else {
  24. response.sendRedirect("http://localhost:8081/sso/index.jsp?service=" + url);
  25. }
  26. } else {
  27. filterChain.doFilter(request, response);
  28. }
  29. } @Override public void init(FilterConfig arg0) throws ServletException {
  30. }
  31. }

ProServlet.java

  1. 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 {
  2. request.getRequestDispatcher("/WEB-INF/jsp/welcome.jsp").forward(request, response);
  3. } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { super.doPost(request, response);
  4. }
  5. }

web.xml

 index.jspssoClientFiltercom.cloud.sso.pro.filter.SSOClientFilterssoClientFilter/*  listcom.cloud.sso.pro.servlet.ProServletlist/list

运行结果:

1:分别启动这三个工程。

2:访问OA系统,URL:http://localhost:8082/oa/list

3:这样到登录页面,如图:

  

 

4:用户名为:cloud,密码为:cloud,点击登录则会显示,如图:

 

5:然后去进入PRO系统,URL:http://localhost:8083/pro/list,则就不需要登录了,直接进入,如图:

 

 

源码下载:http://dl.iteye.com/topics/download/50d7ecf9-a49e-3b02-8291-82a58a8c519c

转载于:https://my.oschina.net/lenglingx/blog/205269