zoukankan      html  css  js  c++  java
  • 单点登录(SSO)原理

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

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

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

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

    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系统返回的视图给用户。

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

     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);
        }
        
    }
  • 相关阅读:
    java 8新特性 匿名内部类的使用
    java 8新特性
    jmeter 性能测试
    idea 背景颜色设置
    SpringBoot yaml的配置及使用
    idea 类图显示
    SpringSecurity 获取认证信息 和 认证实现
    MySQL-慢查询日志
    微信小程序领取卡券
    ThinkPhp5-PHPExcel导出|导入 数据
  • 原文地址:https://www.cnblogs.com/a591378955/p/8489661.html
Copyright © 2011-2022 走看看