之前在项目中用到了单点登录系统来解决分布式环境中Session共享的问题,趁着现在闲了,总结一下......

什么是sso系统

SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。

为什么要有单点登录系统

针对这个问题,我就拿现有的一个项目说一下吧,这个项目不仅涉及到集群,还涉及到了分布式。

先不说分布式,就单单拿集群来说,就会存在一个问题,比如,我这次访问网站,进行了登录,过一会之后我访问个人中心,Nginx将我的请求发到了另一台服务器,这时就出现问题了,这台服务器中并没没有保存我的登录状态,因而我需要重新登录,然后我又访问一次个人中心,Nginx又将我的请求发送到另一台服务器,然后...又提醒我登录,这当然是不能忍的,对于这种情况,除了搭建单点登录系统,还有一个解决方案,就是在Tomcat中配置Session复制,配置好了之后,tomcat会以广播的形式共享Session信息,但是这大大地增加了Tomcat的压力,而且存在一个问题,当你的集群中节点数量不断增加,就会出现问题,session共享太占用系统资源了,所以一般不选择这个作为解决方案。分布式环境下的登录问题就更不用说了,和集群中类似......这个时候我们就需要搭建一个单点登录系统,提供一个接口,共其他模块调用,以检测用户登录状态。

SSO单点登录系统说白了就是使用redis模拟Session(Key-Value),实现Session的统一管理。

具体实现

SSO表现层

定义了三个处理器,分别用于注册、登录、外部调用,查看用户登录状态:

RegisterController.java

package cn.e3mall.sso.controller;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;import cn.e3mall.common.utils.E3Result;
import cn.e3mall.pojo.TbUser;
import cn.e3mall.sso.service.RegisterService;/*** 注册功能Controller*/
@Controller
public class RegisterController {@Autowiredprivate RegisterService registerService;@RequestMapping("/page/register")public String showRegister() {return "register";}@RequestMapping("/user/check/{param}/{type}")@ResponseBodypublic E3Result checkData(@PathVariable String param, @PathVariable Integer type) {return registerService.checkData(param, type);}@RequestMapping(value="/user/register", method=RequestMethod.POST)@ResponseBodypublic E3Result saveUser(TbUser user) {return registerService.saveUser(user);}}

这个没什么好说的,就是调用服务进行用户注册,将注册信息插入数据库中。

LoginController.java

package cn.e3mall.sso.controller;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;import cn.e3mall.common.utils.CookieUtils;
import cn.e3mall.common.utils.E3Result;
import cn.e3mall.sso.service.LoginService;@Controller
public class LoginController {@Autowiredprivate LoginService loginService;@Value("${TOKEN_KEY}")private String TOKEN_KEY;@RequestMapping("/page/login")public String showLogin() {return "login";}@RequestMapping(value="/user/login", method=RequestMethod.POST)@ResponseBodypublic E3Result login(String username, String password, HttpServletRequest request, HttpServletResponse response) {E3Result e3Result = loginService.userLogin(username, password);// 判断是否登录成功if(e3Result.getStatus() == 200) {String token = (String) e3Result.getData();CookieUtils.setCookie(request, response, TOKEN_KEY, token);// 将token保存在cookie中,浏览器关闭即失效(类似sessionid)}// 如果登录成功,需要将token写入cookiereturn e3Result;}
}

这里调用了用户登录服务:

@Override
public E3Result userLogin(String username, String password) {// 1、判断用户名和密码是否正确TbUserExample example = new TbUserExample();Criteria criteria = example.createCriteria();criteria.andUsernameEqualTo(username);// 执行查询List<TbUser> userList = userMapper.selectByExample(example);if (userList == null || userList.size() == 0) {// 返回登录失败return E3Result.build(400, "用户名或密码错误");}// 取用户信息TbUser user = userList.get(0);if (!DigestUtils.md5DigestAsHex(password.getBytes()).equals(user.getPassword())) {// 返回登录失败return E3Result.build(400, "用户名或密码错误");}// 3、如果正确生成tokenString token = UUID.randomUUID().toString();// 4、把用户信息写入redis,key:token value:用户信息user.setPassword(null);jedisClient.set("SESSION:" + token, JsonUtils.objectToJson(user));jedisClient.expire("SESSION:" + token, SESSION_EXPIRE);// 5、把token返回return E3Result.ok(token);
}

这样,登录成功的话,服务层在Redis中会存在一个String类型的数据,并且这个数据设置了生存时间(30分钟),模拟了Session的生存时间,数据的key为SESSION:随机串,模仿的是sessionid,value为用户的信息(不包含密码)。表现层将服务层返回的token保存在cookie中,用于后续查询用户登录状态以及登录用户信息。

另外需要注意的是,cookie也存在跨域问题,即不能跨三级域名,但是可以跨二级域名,比如sso.code4j.cn和www.code4j.cn和search.code4j.cn之间的cookie是互通的,需要设置一下cookie.setDomain(.code4j.cn);即可

TokenController.java

package cn.e3mall.sso.controller;import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import cn.e3mall.common.utils.E3Result;
import cn.e3mall.common.utils.JsonUtils;
import cn.e3mall.sso.service.TokenService;/*** 根据token查询用户信息Controller* * @author Ldd**/
@Controller
public class TokenController {@Autowiredprivate TokenService tokenService;/*@RequestMapping(value = "/user/token/{token}", produces = "application/json;charset=utf-8")@ResponseBodypublic String getUserByToken(@PathVariable String token, String callback) {E3Result result = tokenService.getUserByToken(token);// 响应结果之前判断是否为jsonp请求if (StringUtils.isNotBlank(callback)) {// 把结果封装成一个JS语句响应return callback + "(" + JsonUtils.objectToJson(result) + ");";}return JsonUtils.objectToJson(result);}*/@RequestMapping("/user/token/{token}")@ResponseBodypublic Object getUserByToken(@PathVariable String token, String callback) {E3Result result = tokenService.getUserByToken(token);// 响应结果之前判断是否为jsonp请求if (StringUtils.isNotBlank(callback)) {// 响应Jsonp请求(使用于4.1版本以上的Spring)MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);mappingJacksonValue.setJsonpFunction(callback);return mappingJacksonValue;}return result;}
}

这里调用了服务层的getUserByToKen方法:

@Override
public E3Result getUserByToken(String token) {// 从redis中获取用户信息String user_json = jedisClient.get("SESSION:" + token);// 判断if(StringUtils.isBlank(user_json)) {return E3Result.build(201, "用户登录信息已过期");} else {TbUser user = JsonUtils.jsonToPojo(user_json, TbUser.class);// 重置过期时间jedisClient.expire("SESSION:" + token, SESSION_EXPIRE);// 返回结果return E3Result.ok(user);}
}

这个处理器用于外部调用以检查用户登录状态,并获取用户登录信息,外部以/user/token/xxxxxxx的形式进行访问,xxxxx即为本地cookie中的token(sessionid),然后服务层从Redis中查询,查询到则表示用户已经登录,然后返回用户信息,并重置Redis中该数据的生存时间。

调用SSO单点登录系统

具体需求:

    1.当用户登录之后,本地cookie中就会存在token信息。

2.从cookie中取token并根据token查询用户信息。

3.把用户名展示到首页

解决方案:

一、在Controller中取cookie中的token数据,调用sso服务查询用户信息

二、当页面加载完成后使用js取token的数据,使用ajax请求查询用户信息

这里我决定选择第二个方案,应为工程很多,如果使用第一种方案,需要更改每一个工程的Controller,而使用第二种解决方案,写一个js文件,然后在需要的地方引用即可,但是这里就存在一个问题,我们的服务接口在sso系统中sso.code4j.cn, 而首页的域名是www.code4j.cn使用ajax请求跨域了,而Js不可以跨域请求数据

什么是跨域:

1.域名不同

2.域名相同但端口不同

Js跨域的解决方案:Jsonp

Jsonp并不是什么新技术,而是跨域的解决方案。使用js的特性:Js可以跨域加载js文件,绕过跨域请求。

Jsonp原理:


看起来很复杂,其实真正使用的时候并不用这么麻烦,因为Jquery已经封装好了......

具体实现

客户端

var E3MALL = {checkLogin : function(){var _ticket = $.cookie("token");//获取cookie中的信息,使用了jquery.cookie.jsif(!_ticket){return ;}$.ajax({url : "http://localhost:8088/user/token/" + _ticket,dataType : "jsonp",// 表示跨域请求,加了这个,上面图示的一些动作jq就会自动完成,你只需在处理器中接收callback然后处理即可type : "GET",success : function(data){if(data.status == 200){var username = data.data.username;var html = username + ",欢迎您!<a href=\"http://www.code4j.cn/user/logout.html\" class=\"link-logout\">[退出]</a>";$("#loginbar").html(html);}}});}
}$(function(){// 查看是否已经登录,如果已经登录查询登录信息E3MALL.checkLogin();
});

这样就行了,获取到用户信息,就展示在首页顶部,没有接收到用户信息,就不做处理,还显示原来的登录按钮。

当然,这只是一个最基础的示范,具体业务还需具体分析,具体实现......

SSO单点登录系统的设计与实现相关推荐

  1. SpringBoot+MyBatis+Redis实现SSO单点登录系统(二)

    SpringBoot+MyBatis+Redis实现SSO单点登录系统(二) 三.代码 配置文件配置数据库,redis等相关的信息. # See http://docs.spring.io/sprin ...

  2. SpringBoot+MyBatis+Redis实现SSO单点登录系统(一)

    SpringBoot+MyBatis+Redis实现SSO单点登录系统(一) 一.SSO系统概述 SSO英文全称Single Sign On,单点登录.SSO是在多个应用系统中,用户只需要登录一次就可 ...

  3. sso单点登录系统(解决session共享)

    场景:假设一个用户将自己的登录信息提交到后台,如果session保存的信息分布在多台机器上,并且不共享,那么可能导致用户的登录信息出现短暂的丢失,为什么这样讲,因为用户访问服务器中间还要经过负载均衡服 ...

  4. 互联网分布式微服务云平台规划分析--SSO单点登录系统

    介绍 鸿鹄云架构[SSO单点登录系统]为所有微服务提供统一的用户认证服务,系统本身属于微服务模式,使用JWT+Redis分布式存储方案,确保不同微服务.系统之间的安全通讯和统一用户校验.认证.在整个服 ...

  5. Shiro整合SSO单点登录系统

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m0_37797991/article/ ...

  6. 手撕一套sso(单点登录)系统之原理篇1

    在手撕之前,你首先要了解一些原理,我写的案例成品可以访问zauth,语言是Java8. 目录 1.关于Http 2.用户信息怎么存?存什么?存在哪? 2.1 使用前端存储技术Storage或index ...

  7. SSO单点登录系统的实战运用

    通用介绍 单点登录系统,简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.任何SSO框架都需要创建统一的认 ...

  8. 单点登录系统的设计与实现方案

    目的: 对目前已有的 Web 应用系统,和将来待开发的 Web 应用系统系统进行集成,实现单点登录. 要求: 1. 对已有的 Web 应用系统不作大规模改造.    2. 不限制待开发的 Web 应用 ...

  9. SSO单点登录系统设计,数据库设计文档

    SSO 系统设计文档 1 .SSO 数据库设计 1.1 SSO用户表 ssoUsers 字段名 中文名称 字段类型 说明 ssoUserId 序号 Int(4) PK,自增 ssoLoginId SS ...

  10. sso单点登录系统的理解

    单点登录有两种方式,一种是跨域,一种是不跨域. 一:在不跨域的情况下, 只需要将cookie设置为顶域状态,即可以实现cookie的共享. 二:跨域登录 用户访问app系统,app系统是需要登录的,但 ...

最新文章

  1. 量子位MEET大会报名开启!各领域头部玩家集结,AI年度榜单揭晓,在这里预见智能科技新未来...
  2. html标签在html页面正常显示而不被解析
  3. IOS UITableView性能优化
  4. linux命令ftps,Linux下ftp+ssl实现ftps
  5. live2d_vue-live2d 看板娘
  6. 【全源码及文档】基于JAVA的干部档案管理系统
  7. 【二维码案例】“码”出行,交通运输领域二维码应用
  8. python保存快捷键是什么_python常用快捷键
  9. 全国计算机四六级报名时间,2018年6月四六级考试报名即将截止,请抓紧时间报名!...
  10. 如何在电脑上复制微信文章图片
  11. python爬虫--王者荣耀高清壁纸下载(多线程)
  12. MacOS redis开机启动设置
  13. 零输入响应,零状态响应,全响应
  14. IDEA设置标签多行展示
  15. kali网络渗透实验一:网络扫描与网络侦查
  16. [CVPR2020] Deep Global Registration
  17. labelme jason文件批处理
  18. JIL Widget开发入门
  19. Win7/Win8下双击运行jar程序的方法
  20. win10安装MySQL解压缩版 8.0.15(非转载,较详细)

热门文章

  1. 网络安全——D模块答题模式
  2. UART、RS-232、RS-422、RS-485
  3. 如何在WPS里添加字体?
  4. 逢看必会的三子棋小游戏:原来可以这么简单
  5. 广搜算法之翻转棋子游戏
  6. AcWing 1319. 移棋子游戏(sg 博弈)
  7. 怎么用命令提示符打开浏览器_从您当前的命令提示符/终端目录打开文件浏览器...
  8. 如何开启BBR算法优化ss
  9. axios.js下载
  10. mysql jemalloc_Jemalloc优化MySQL和Nginx