SSO单点登录

前言:今天刚学完B站up主“楠哥教你学Java”前些日子的一个直播教学,通过录播跟着把代码敲了一遍,整理了一下。前半部分取自楠哥的笔记,后部分代码和思维导图自己归纳总结,代码细节今天是撸不完了,明后天在多研究几遍一些细节

单点登录

什么是单点登录:一处登录,处处登录,一处登出,处处登出。

用户只需要登录一次就可以访问所有相互信任的应用系统。

SSO

Single Sign On 单点登录

企业业务整合解决方案

一票通

SSO 原理

  • 当用户第一次访问淘宝的时候,因为还没有登录,会被引导到认证中心进行登录。
  • 根据用户提供的登录信息,认证系统进行身份验证,如果通过,则登录成功,并返回给用户一个认证的凭据(token)。
  • 当用户访问天猫时,就会将这个 token 带上,作为自己认证的凭据。
  • 应用系统接收到请求后会把 token 送到认证中心进行校验,检查 token 的合法性。
  • 如果通过校验,用户就可以在不用再次登录的情况下访问天猫了。

img

SSO 实现技术

Cookie 单点登录

使用 Cookie 作为媒介,存放用户凭证。

用户登录淘宝之后,返回一个 token,存入客户端的 Cookie 中,当用户访问天猫的时候,会自动带上 Cookie,这样 token 又传给了认证中心,进行校验。

分布式 Session

img

1、用户第一次登录时,将会话信息,写入分布式 Session。

2、用户再次登录时,获取分布式 Session,判断是否有登录信息,如果没有则返回登录页面。

3、Session 一般存储到 Redis 中,因为它有持久化功能,如果分布式 Session 宕机后,重启之后可以从持久化存储中重新加载会话信息。

SSO 常见方案

OAuth2(第三方登录授权)

第三方系统访问主系统资源,用户无需将主系统的账户告知第三方,只需要通过主系统的授权,第三方就可以使用主系统的资源。

jwt

Json Web Token,是为了在网络应用之间传递信息而执行的一种,基于 JSON 的开放标准,难度较高,需要了解很多协议,偏向底层的东西,需要你基于 JWT 认证协议,自己开放 SSO 服务和权限控制。

CAS(不是并发的 CAS)

单点登录的 CAS 和并发的 CAS 完全是两码事

中央认证服务,Central Authentication Service

CAS 是耶鲁大学发起的一个开源项目,为 Web 应用系统提供单点登录的解决方案,实现多个系统只需要登录一次,无需重复登录。

  • CAS Server
  • CAS Client

Server 和 Client 分别独立部署,Server 主要负责认证工作

Client 负责处理对客户端资源的访问请求,需要登录时,重定向到 Server 进行认证。

1、授权服务器保存一个全局 session,多个客户端各自保存自己的 session。

2、客户端登录时判断自己的 session 是否已经登录,如果没有登录,则重定向到服务器进行授权(带上自己的地址,用于回调)。

3、授权服务器判断全局的 session 是否已经登录,如果未登录则重定向到登录页面,提供用户登录,登录成功之后,授权服务器重定向到客户端,带上 ticket。

4、客户端收到 ticket 后,请求服务器获取用户信息。

5、服务器同意客户端授权后,服务器保存用户信息到全局 session,客户端将用户信息保存至本地 session。

手写单点登录系统架构

Spring Boot + Thymelaf

Thymeleaf 是个什么?

简单说, Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下三个极吸引人的特点:

1.Thymeleaf 在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以 thymeleaf 的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。

2.Thymeleaf 开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、 OGNL表达式效果,避免每天套模板、该jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。

3.Thymeleaf 提供spring标准方言和一个与 SpringMVC 完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。

SSO(主工程) 创建三个子工程

  • taobao (客户端)
  • tmall (客户端)
  • server (服务端)

SSO_Client_Taobao

img

1、首先配置淘宝客户端的配置文件application.yml(访问端口、thymeleaf 模板、前后缀、标头、编码)

server:
  port: 8081
spring:
  thymeleaf:
    suffix: .html
    prefix: classpath:/templates/
    servlet:
      content-type: text/html
    encoding: UTF-8

2、resources下创建templates目录,写入index.html静态页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/layui/css/layui.css}" media="all">
</head>
<body>
<div class="layui-container" style="width: 700px;height: 600px;margin-top: 0px;padding-top: 60px;">

    <h1>淘宝首页</h1>

    <div style="margin-left: 460px; width: 200px;">
        欢迎回来!admin
        <a th:href="${serverLogoutUrl}">
            <button class="layui-btn layui-btn-warm layui-btn-radius">退出</button>
        </a>
    </div>

    <img width="700px" th:src="@{/images/taobao.png}">

</div>
</body>
</html>

3、static目录导入layui,layui是一个前端框架

img

​ static目录导入一个图片模拟淘宝首页

img

img

4、创建运行类TaobaoApplication

package com.janeroad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TaobaoApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaobaoApplication.class,args);
    }
}

5、创建控制器 TaobaoHandler,处理异步请求

package com.janeroad.controller;

import com.janeroad.util.SSOClientUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
public class TaobaoHandler {
    @GetMapping("/taobao")
    public String index(Model model){
       model.addAttribute("serverLogoutUrl", SSOClientUtil.getServerLogoutUrl());
        return "index";
    }
    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "redirect:/taobao";
    }
}

6、创建拦截器TaobaoInterceptor,继承与HandelerInterceptor,用于当请求服务端页面时判断用户是否登录,如果登录过放行,如果未登录跳转登录页面

package com.janeroad.interceptor;

import com.janeroad.util.HttpUtil;
import com.janeroad.util.SSOClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;

@Slf4j
public class TaobaoInterceptor implements HandlerInterceptor {
    
    /**
     * 功能描述: <br>
     * true 放行
     * false 不放行
     * @Param: [request, response, handler]
     * @Return: boolean
     * @Author: JaneRoad
     * @Date: 2020/3/29 15:30
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1、判断是否已经登陆
        HttpSession session =request.getSession();
        Boolean isLogin =(Boolean) session.getAttribute("isLogin");
        if(isLogin!=null && isLogin){
            return true;
        }
        //2、判断 token
        String token=request.getParameter("token");
        if(!StringUtils.isEmpty(token)){
            //验证token
            log.info("token存在,需要验证");
            //发起验证
            String httpUrl = SSOClientUtil.SERVER_HOST_URL+"/verify";
            HashMap<String,String> params = new HashMap<>();
            params.put("token",token);
            params.put("clientLogoutUrl",SSOClientUtil.getClientLogoutUrl());
            String isVerify = HttpUtil.sendHttpRequest(httpUrl,params);
            if("true".equals(isVerify)){
                log.info("token验证通过,token={}",token);
                //token保存到本地Cookie
                Cookie cookie = new Cookie("token",token);
                response.addCookie(cookie);
                session.setAttribute("isLogin",true);
                return true;
            }
        }

        //3、跳转到认证中心进行登录
        SSOClientUtil.redirectToCheckToken(request, response);
        return false;
    }
}

7、创建工具类SSOClientUtil

package com.janeroad.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;

public class SSOClientUtil {

    private static Properties properties = new Properties();
    public static String SERVER_HOST_URL;
    public static String CLIENT_HOST_URL;

    static {
        try {
            properties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
            SERVER_HOST_URL = properties.getProperty("server.host.url");
            CLIENT_HOST_URL = properties.getProperty("client.host.url");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //跳转到认证中心
    public static void redirectToCheckToken(HttpServletRequest request, HttpServletResponse response){
        StringBuffer url=new StringBuffer();
        url.append(SERVER_HOST_URL)
                .append("/checkToken?redirectUrl=")
                .append(CLIENT_HOST_URL)
                .append(request.getServletPath());
        try {
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static String getServerLogoutUrl(){
        return SERVER_HOST_URL+"/logout";
    }

    public static String getClientLogoutUrl(){
        return CLIENT_HOST_URL+"/logout";
    }
}

8、resources下写入一个配置文件sso.properties,用于记录服务端地址

server.host.url=http://localhost:8080
client.host.url=http://localhost:8081

9、创建工具类HttpUtil

package com.janeroad.util;

import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {
    /**
     * id=1
     * name=tom
     * {id=1,name=tom} id=1&name=tom
     * @param httpUrl
     * @param params
     * @return
     */
    public static String sendHttpRequest(String httpUrl, Map<String,String> params){
        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            if(params!=null && params.size()>0){
                StringBuffer stringBuffer = new StringBuffer();
                for(Map.Entry<String,String> entry:params.entrySet()){
                    stringBuffer.append("&")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue());
                }
                connection.getOutputStream().write(stringBuffer.substring(1).toString().getBytes("UTF-8"));
            }
            connection.connect();
            String response = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("UTF-8"));
            return response;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
}

SSO_Server

img

1、首先配置服务端的配置文件application.yml(访问端口、thymeleaf 模板、前后缀、标头、编码)

server:
  port: 8080
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    servlet:
      content-type: text/html
    encoding: UTF-8

2、resources下创建templates目录,写入index.html静态页面

<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/layui/css/layui.css"  media="all">
</head>
<body>
<div class="layui-container" style="width: 500px;height: 330px;margin-top: 130px;border: 1px solid #009688;padding-top: 60px;border-radius: 15px">
    <form class="layui-form" action="/login" method="post">
        <input type="hidden" name="redirectUrl" th:value="${redirectUrl}">
        <div class="layui-form-item">
            <label class="layui-form-label">用户名</label>
            <div class="layui-inline">
                <input type="text" name="username" lay-verify="username" autocomplete="off" placeholder="请输入用户名" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">密码</label>
            <div class="layui-inline">
                <input type="password" name="password" lay-verify="password" placeholder="请输入密码" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <button class="layui-btn" lay-submit="" lay-filter="demo2" style="margin-left: 160px;">登陆</button>
        </div>
    </form>
</div>
<script src="/layui/layui.js" charset="utf-8"></script>
<script>
    layui.use(['form'], function(){
        var form = layui.form;

        //自定义验证规则
        form.verify({
            username: function(value){
                if(value.length == 0){
                    return '用户名不能为空';
                }
            }
            ,password: [/(.+){6,12}$/, '密码必须6到12位']
        });

    });
</script>
</body>
</html>

3、同客户端,static目录导入layui,layui是一个前端框架

4、创建运行类ServerApplication

package com.janeroad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerApplication.class,args);
    }
}

5、创建控制器 ServerHandler,处理异步请求

package com.janeroad.controller;

import com.janeroad.db.MockDB;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.thymeleaf.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@Controller
@Slf4j
public class ServerHandler {
    /**
     * 功能描述: 第一次登录验证
     * 〈〉
     * @Param: [redirectUrl, session, model]
     * @Return: java.lang.String
     * @Author: JaneRoad
     * @Date: 2020/3/29 16:21
     */

    @RequestMapping("/checkToken")
    public String checkToken(String redirectUrl, HttpSession session, Model model, HttpServletRequest request)
    {
        //获取token
        String token = (String) session.getServletContext().getAttribute("token");
        if(StringUtils.isEmpty(token)){
            model.addAttribute("redirectUrl",redirectUrl);
            return "login";
        }else{
            //验证token
            Cookie[] cookies = request.getCookies();
            for (Cookie cookie : cookies) {
                if(cookie.getValue().equals(token)){
                    //验证通过,返回客户端
                    log.info("token验证通过");
                    return "redirect:"+redirectUrl+"?token="+token;
                }
            }
        }
        model.addAttribute("redirectUrl",redirectUrl);
        return "login";

    }

    @PostMapping("/login")
    public String login(String username,
                        String password,
                        String redirectUrl,
                        HttpSession session,
                        Model model){
        //判断登录
        if("admin".equals(username) && "123123".equals(password)){
            //1、创建token
            String token = UUID.randomUUID().toString();
            log.info("token创建成功!token={}",token);
            //2、token保存到全局会话中
            session.getServletContext().setAttribute("token",token);
            //3、token保存到数据库
            MockDB.tokenSet.add(token);
            //4、返回客户端
            return "redirect:"+redirectUrl+"?token="+token;
        }else{
            log.error("用户名密码错误!username={},password={}",username,password);
            model.addAttribute("redirectUrl",redirectUrl);
            return "login";
        }
    }

    @RequestMapping("/verify")
    @ResponseBody
    public String verifyToken(String token,String clientLogoutUrl){
        if(MockDB.tokenSet.contains(token)){
            Set<String> set = MockDB.clientLogoutUrlMap.get(token);
            if(set == null){
                set = new HashSet<>();
            }
            set.add(clientLogoutUrl);
            MockDB.clientLogoutUrlMap.put(token,set);
            return "true";
        }
        return "false";
    }

    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "login";
    }




}

6、创建监听器,登出的时候账号销毁的时候需要操作

package com.janeroad.listener;

import com.janeroad.db.MockDB;
import com.janeroad.util.HttpUtil;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import java.util.Iterator;
import java.util.Set;

@WebListener
public class SessionListener implements HttpSessionListener {
    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        //1、删除全局会话中的token
        //2、删除数据库的用户信息
        //3、通知所有客户端销毁session
        String token = (String) se.getSession().getServletContext().getAttribute("token");
        se.getSession().getServletContext().removeAttribute("token");
        MockDB.tokenSet.remove(token);
        Set<String> set = MockDB.clientLogoutUrlMap.get(token);
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            HttpUtil.sendHttpRequest(iterator.next(),null);
        }
        MockDB.clientLogoutUrlMap.remove(token);
    }
}

7、创建监听器配置ListenerConfig

package com.janeroad.config;

import com.janeroad.listener.SessionListener;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class ListenerConfig implements WebMvcConfigurer {

    @Bean
    public ServletListenerRegistrationBean bean(){
        ServletListenerRegistrationBean bean = new ServletListenerRegistrationBean();
        bean.setListener(new SessionListener());
        return bean;
    }

}

8、创建MockDB模拟数据库

package com.janeroad.db;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class MockDB{
    //记录token
    public static Set<String> tokenSet=new HashSet<>();
    //客户端登出地址
    public static Map<String,Set<String>> clientLogoutUrlMap = new HashMap<>();
}

9、创建HttpUtil工具类

package com.janeroad.util;

import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {
    /**
     * id=1
     * name=tom
     * {id=1,name=tom} id=1&name=tom
     * @param httpUrl
     * @param params
     * @return
     */
    public static String sendHttpRequest(String httpUrl, Map<String,String> params){
        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            if(params!=null && params.size()>0){
                StringBuffer stringBuffer = new StringBuffer();
                for(Map.Entry<String,String> entry:params.entrySet()){
                    stringBuffer.append("&")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue());
                }
                connection.getOutputStream().write(stringBuffer.substring(1).toString().getBytes("UTF-8"));
            }
            connection.connect();
            String response = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("UTF-8"));
            return response;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
}

SSO_Client——Tmall

img

天猫客户端和淘宝的相似

1、首先配置淘宝客户端的配置文件application.yml(访问端口、thymeleaf 模板、前后缀、标头、编码)

server:
  port: 8082
spring:
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    servlet:
      content-type: text/html
    encoding: UTF-8

2、resources下创建templates目录,写入index.html静态页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/layui/css/layui.css}" media="all">
</head>
<body>
<div class="layui-container" style="width: 700px;height: 600px;margin-top: 0px;padding-top: 60px;">

    <h1>天猫首页</h1>

    <div style="margin-left: 460px; width: 200px;">
        欢迎回来!admin
        <a th:href="${serverLogoutUrl}">
            <button class="layui-btn layui-btn-warm layui-btn-radius">退出</button>
        </a>
    </div>

    <img width="700px" th:src="@{/images/tmall.png}">

</div>
</body>
</html>

3、static目录导入layui,layui是一个前端框架。image下导入图片,模拟天猫首页

img

img

4、创建运行类TmallApplication

package com.janeroad;


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TmallApplication {
    public static void main(String[] args) {
        SpringApplication.run(TmallApplication.class,args);
    }
}

5、创建控制器 TmallHandler,处理异步请求

package com.janeroad.controller;

import com.janeroad.util.SSOClientUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

@Controller
public class TmallHandler {

    @GetMapping("/tmall")
    public String index(Model model){
        model.addAttribute("serverLogoutUrl", SSOClientUtil.getServerLogoutUrl());
        return "index";
    }

    @RequestMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "redirect:/tmall";
    }
}

6、创建拦截器TmallInterceptor,继承与HandelerInterceptor,用于当请求服务端页面时判断用户是否登录,如果登录过放行,如果未登录跳转登录页面

package com.janeroad.interceptor;

import com.janeroad.util.HttpUtil;
import com.janeroad.util.SSOClientUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;

@Slf4j
public class TmallInterceptor implements HandlerInterceptor {
    /**
     * true 放行
     * false 不放行
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1、判断是否已经登录
        HttpSession session = request.getSession();
        Boolean isLogin = (Boolean) session.getAttribute("isLogin");
        if (isLogin != null && isLogin) {
            return true;
        }

        //2、判断token
        String token = request.getParameter("token");
        if (!StringUtils.isEmpty(token)) {
            //验证token
            log.info("token存在,需要验证");
            //发起验证
            String httpUrl = SSOClientUtil.SERVER_HOST_URL + "/verify";
            HashMap<String, String> params = new HashMap<>();
            params.put("token", token);
            params.put("clientLogoutUrl",SSOClientUtil.getClientLogoutUrl());
            String isVerify = HttpUtil.sendHttpRequest(httpUrl, params);
            if ("true".equals(isVerify)) {
                log.info("token验证通过,token={}", token);
                //token保存到本地Cookie
                Cookie cookie = new Cookie("token", token);
                response.addCookie(cookie);
                session.setAttribute("isLogin", true);
                return true;
            }
        }

        //3、跳转到认证中心进行登录
        SSOClientUtil.redirectToCheckToken(request, response);
        return false;
    }
}

7、创建工具类SSOClientUtil

package com.janeroad.util;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Properties;

public class SSOClientUtil {
    private static Properties properties = new Properties();
    public static String SERVER_HOST_URL;
    public static String CLIENT_HOST_URL;

    static {
        try {
            properties.load(SSOClientUtil.class.getClassLoader().getResourceAsStream("sso.properties"));
            SERVER_HOST_URL = properties.getProperty("server.host.url");
            CLIENT_HOST_URL = properties.getProperty("client.host.url");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //跳转到认证中心
    public static void redirectToCheckToken(HttpServletRequest request, HttpServletResponse response) {
        StringBuffer url = new StringBuffer();
        url.append(SERVER_HOST_URL)
                .append("/checkToken?redirectUrl=")
                .append(CLIENT_HOST_URL)
                .append(request.getServletPath());
        try {
            response.sendRedirect(url.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String getServerLogoutUrl(){
        return SERVER_HOST_URL+"/logout";
    }

    public static String getClientLogoutUrl(){
        return CLIENT_HOST_URL+"/logout";
    }
}

创建工具类HttpUtil

package com.janeroad.util;

import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Map;

public class HttpUtil {
    /**
     * id=1
     * name=tom
     * {id=1,name=tom} id=1&name=tom
     * @param httpUrl
     * @param params
     * @return
     */
    public static String sendHttpRequest(String httpUrl, Map<String,String> params){
        try {
            URL url = new URL(httpUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            if(params!=null && params.size()>0){
                StringBuffer stringBuffer = new StringBuffer();
                for(Map.Entry<String,String> entry:params.entrySet()){
                    stringBuffer.append("&")
                            .append(entry.getKey())
                            .append("=")
                            .append(entry.getValue());
                }
                connection.getOutputStream().write(stringBuffer.substring(1).toString().getBytes("UTF-8"));
            }
            connection.connect();
            String response = StreamUtils.copyToString(connection.getInputStream(), Charset.forName("UTF-8"));
            return response;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }
}

8、resources下写入一个配置文件sso.properties,用于记录服务端地址

server.host.url=http://localhost:8080
client.host.url=http://localhost:8082

最终实现效果

访问localhost://8081/taobao,模拟访问淘宝跳转到登录页面

img

访问localhost://8082/tmall,模拟访问天猫跳转到登录页面

img

淘宝输入用户名密码,跳转淘宝首页

img

img

刷新localhost://8082/tmall,天猫自动登录进入首页

img

在天猫和淘宝任一界面退出账号,另一个界面随之退出,从而实现一处登录处处登录,一处登出处处登出。

# Java  SpringBoot  SSO 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×