单点登录(SSO)原理

摘要:
第二个是用户已经登录到一个系统(如OA系统),但想要进入另一个系统的过程(如PRO系统)。第一个是用户尚未登录系统的过程:用户通过URL访问OA系统。OA系统中的过滤器发现该URL没有票证。也就是说,将票据参数添加到用户访问OA系统的URL。

在整个SSO流程当,有两个流程非常重要:

  第一个是用户没有登录系统到登录系统的过程;

  第二是用户在一个系统当中已经登录(例如在OA系统中登录 了),但又想进入另一个系统(例如进入PRO系统)的过程

一、用户没有登录系统到登录系统的过程:

单点登录(SSO)原理第1张

1:用户通过URL访问OA系统。

 2:在OA系统中的filter发现这个URL没有ticket,此时就会跳转到SSO Server。

 3:SSO Server中的filter发现该客户端中的cookie中没有相应信息,也即是一个没有登录的用户,那么会跳转到登录页面。

 4:用户在登录页面填写相应信息,然后通过post方式提交到SSO Server中。

 5:SSO Server会校验用户信息,同时在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系统返回的视图给用户。

二、用户已经登录成功了,但要访问另一个系统

单点登录(SSO)原理第2张

 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。

 6、7:将PRO系统返回的视图给用户。

关键代码:

SSOServer:

SSOServerFilter.java(认证中心的过滤器):

package 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 util.JVMCache;

public class SSOServerFilter implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            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 (cookies!=null) {
            for (Cookie cookie : cookies) {
                if ("SSO".equals(cookie.getName())) {//如果cookie中有"sso",则已生成了认证凭证
                    username = cookie.getValue();
                    System.out.println("扫描cookie中的SSO:"+username);
                    break;
                }
            }
        }
        
        //实现一处登录处处登录
        if (username!=null && !"".equals(username)) {
            System.out.println("从cookie中获取的username:"+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 (service.indexOf("?")>=0) {//请求url带了参数
                url.append("&");
            }else{
                url.append("?");
            }
            //返回给用户一个认证的凭据--ticket
            url.append("ticket="+ticket);
            //重定向
            response.sendRedirect(url.toString());
        }else {
            chain.doFilter(servletRequest, servletResponse);
        }
    }

    @Override
    public void destroy() {
        
    }

}

LoginServlet.java(验证登录的servlet):

package 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 util.JVMCache;

public class LoginServlet extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @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)&&"123456".equals(password)) {//用户名和密码正确
            Cookie cookie = new Cookie("SSO", username);
            cookie.setPath("/");
            response.addCookie(cookie);
            
            long time = System.currentTimeMillis();
            //生成认证凭据--ticket
            String ticket = username+time;
            JVMCache.TICKET_AND_NAME.put(ticket, username);
            
            if (service!=null) {//目的url不为空
                StringBuilder url = new StringBuilder();
                url.append(service);
                if (service.indexOf("?")>=0) {
                    url.append("&");
                }else{
                    url.append("?");
                }
                //返回给用户一个认证的凭据--ticket
                url.append("ticket="+ticket);
                response.sendRedirect(url.toString());
            }else {//如果用户没填跳转目的的url,则返回当前页面
                response.sendRedirect("/sso_server/index.jsp");
            }
        }else{//用户名或者密码错误
            response.sendRedirect("/sso_server/index.jsp?service="+service);
        }
    }
    
}

TicketServlet.java(获取认证凭证的servlet):

package 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 util.JVMCache;

/**
 * HttpClient调用这个Servlet获取username
 * @author 尐蘇
 *
 */
public class TicketServlet extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = -5580725166413724608L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String ticket = req.getParameter("ticket");
        String username = JVMCache.TICKET_AND_NAME.get(ticket);
        System.out.println("获取令牌username:"+username);
        //保证一个ticket只会用一次
        JVMCache.TICKET_AND_NAME.remove(ticket);
        PrintWriter writer = resp.getWriter();
        writer.println(username);
        writer.close();
    }
    
    
}

JVMCache.java(存储ticket的工具类)

package util;

import java.util.HashMap;
import java.util.Map;

/*
 * Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。
 * 它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。
 * Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。
 */


public class JVMCache {
    //存放username,再通过HttpClient获取(在实际项目应该放到Memcached中)
    public static Map<String, String> TICKET_AND_NAME = new HashMap<>();
}

OA系统:

SSOClientFilter.java(客户端过滤器):

package filter;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

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.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class SSOClientFilter implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            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");
        System.out.println("ticket:"+ticket);
        String url = URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");
        System.out.println("username:"+username);
        //判断用户是否已登录OA系统
        if (username == null) {//如果没有username这个参数,说明不是登录请求,不直接放行,最好是在配置的时候不拦截登录请求
            //1.判断用户是否有认证凭据--ticket(认证中心生成)
            if (ticket!=null && !"".equals(ticket)) {//有认证凭据,连接认证中心认证
                CloseableHttpClient httpClient = HttpClients.createDefault();
                HttpPost post = new HttpPost("http://localhost:8085/sso_server/ticket");
                //给url添加新的参数
                List<NameValuePair> params = new ArrayList<>();
                params.add(new BasicNameValuePair("ticket", ticket));
                post.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
                //通过httpClient调用SSO Server中的TicektServlet
                CloseableHttpResponse closeableHttpResponse = httpClient.execute(post);
                //将HTTP方法的响应正文(如果有)返回为String
                HttpEntity entity = closeableHttpResponse.getEntity();
                username = EntityUtils.toString(entity, "UTF-8");
                System.out.println("认证中心返回的username:"+username);
                //释放连接
                closeableHttpResponse.close();
                httpClient.close();
                
                //2.判断认证凭据是否有效
                if (username!=null && !"".equals(username)) {
                    //session设置用户名,说明用户登录成功了
                    session.setAttribute("username", username);
                    chain.doFilter(request, response);
                }else{
                    response.sendRedirect("http://localhost:8085/sso_server/index.jsp?service="+url);
                }
            }else{//第一次访问OA系统,需要到sso-server系统验证
                response.sendRedirect("http://localhost:8085/sso_server/index.jsp?service="+url);
            }
        }else{
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        
    }

}

OAServlet.java

package 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 = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("WEB-INF/jsp/welcome.jsp").forward(req, resp);
        System.out.println("请求oa系统资源");
    }
    
}

PRO系统:

SSOClientFilter.java:

package filter;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

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.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

public class SSOClientFilter implements Filter{

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();
        String username = request.getParameter("username");
        String ticket = request.getParameter("ticket");
        String url = URLEncoder.encode(request.getRequestURL().toString(), "UTF-8");
        System.out.println("用户名:"+username);
        //判断用户是否已登录OA系统
        if (username == null) {//如果没有username这个参数,说明不是登录请求,不直接放行,最好是在配置的时候不拦截登录请求
            //1.判断用户是否有认证凭据--ticket(认证中心生成)
            if (ticket!=null && !"".equals(ticket)) {
                CloseableHttpClient httpClient = HttpClients.createDefault();
                HttpPost post = new HttpPost("http://localhost:8085/sso_server/ticket");
                //给url添加新的参数
                List<NameValuePair> params = new ArrayList<>();
                params.add(new BasicNameValuePair("ticket", ticket));
                post.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
                //通过httpClient调用SSO Server中的TicektServlet
                CloseableHttpResponse closeableHttpResponse = httpClient.execute(post);
                //将HTTP方法的响应正文(如果有)返回为String
                HttpEntity entity = closeableHttpResponse.getEntity();
                username = EntityUtils.toString(entity, "UTF-8");
                //释放连接
                closeableHttpResponse.close();
                httpClient.close();
                
                //2.判断认证凭据是否有效
                if (username!=null && !"".equals(username)) {
                    //session设置用户名,说明用户登录成功了
                    session.setAttribute("username", username);
                    chain.doFilter(request, response);
                }else{
                    response.sendRedirect("http://localhost:8085/sso_server/index.jsp?service="+url);
                }
            }else{//第一次访问OA系统,需要到sso-server系统验证
                response.sendRedirect("http://localhost:8085/sso_server/index.jsp?service="+url);
            }
        }else{
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        
    }

}

ProServlet.java

package 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 = 1L;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);;
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getRequestDispatcher("WEB-INF/jsp/welcome.jsp").forward(req, resp);
    }
    
}

免责声明:内容来源于网络,仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇油田合并我们的新闻发布系统!下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

分布式下Session一致性问题

一、Session一致性问题 1.1 什么是Session 用户使用网站的服务,基本上需要浏览器和web服务器进行多次交互,web服务器如何知道哪些请求是来自哪个会话的? 具体方式为:在会话开始时,分配一个唯一的会话标识(sessionId),通过cookie把这个标识告诉浏览器,以后每次请求的时候,浏览器都会带上这个会话标识来告诉web服务器请求是属于哪...

libcurl教程

名称 libcurl 的编程教程 目标 本文档介绍使用libcurl编程的一般原则和一些基本方法。本文主要是介绍 c 语言的调用接口,同时也可能很好的适用于其他类 c 语言的接口。 跨平台的可移植代码 libcurl库背后的开发人员投入了相当大的努力确保libcurl可以在很多不同的系统和环境里工作。 全局的准备 程序必须初始化一些libcurl的全局函数...

JS获取指定的cookie值

cookie Name为TEST_COOKIE:用如下方法可以获取cookie值: document.cookie.replace(/(?:(?:^|.*;s*)TEST_COOKIEs*=s*([^;]*).*$)|^.*$/, '$1')...

如何调整cookie的生命周期

一、什么是cookie 形象比喻成“网络身份证” 指某些网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。 (1)记录信息的盒子(2)识别每一个网络用户的证件 (3)12个月的存活期(4)每当用户访问了一个带有AdMaster代码的页面,cookie就会自动更新浏览信息 二、使用和禁用cookie 可以在浏览器的设置...

实习管理系统

实习管理系统图文介绍 1.登录界面,在登陆的时候判断登录用户账号密码是否正确,以及职位信息,如果没有账号密码则使用游客身份登录2.显示实习生周评分,本周使用的技术栈热度3.简历管理界面:如果登录的权限为指导教师,则西南是简历管理界面,对提交的简历进行管理查看4.用户管理页面:如果登录用户为管理员账号显示所有用户信息,对所有用户进行管理5.实习生管理页面:如...

.NET5.0 Identity认证

一、ASP.NET Core Identity概要  ASP.NET Core Identity的原理可以看ASP.NET Core 之 Identity 入门(一),讲的通俗易懂,这里主要通过示例使用ASP.NET Core Identity。 二、ASP.NET Core  Identity示例 1、创建具有身份验证的 Web 应用 选择 " 文件...