一、单点登录系统介绍

  对于一个开发项目来说,每个项目都必不可少要有登录的这个功能。但是随着项目的变大,变大,再变大。系统可能会被拆分成多个小系统,咱们就拿支付宝和淘宝来说,咱们在淘宝上购物,然后就可以直接连接到自己的支付宝,这个过程不需要我们再次登录系统,自动就完成了跳转。这个操作就是小编这次向大家介绍的——单点登录。

1.1 什么是单点登录?

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

  可以看出,咱们使用了单点登录系统,在分布式架构APP中,咱们的系统和系统之间的跳转就可以达到无缝链接了。用户体验度非常好,用户根本没有觉察出是多个系统。这个就达到了我们的目的,即提高了性能,又增加了我们系统之间跳转的灵活度。

1.2 为什么使用单点登录

  下面小编从一个技术小白做订餐系统,一步一步演变框架。

  首先呢,小编做了一个java Web的项目,这个项目用户进入订餐界面,选择要定的饭后,提交的时候,会判断是否登录。如果没有登录,就会跳转到用户登录界面,然后进行登录的判断;如果已经登录了,就可以直接提交信息了。

  这个小系统非常的简单,操作也很流畅。但是它有一个适应范围:非高并发使用流畅。

    

  当我们的这个系统有了很高的并发,就像美团外卖一样,每天的用户很多。我们能做的就是把我们的APP多发布到几个tomcat上,然后通过Nginx反向代理来均分权重。

  随之而来的就是Session共享问题了:  用户1登录后,nginx给他分配到tomcat1上,session被存储在tomcat1上。当下次登录可能分配到tomcat2上,这样还需要重新登录。

  解决Session共享的问题:

  1. tomcat有一个session同步方案,就是一个传播机制,打个比方有A B C 3台tomcat,这3台tomcat的user信息都在session中并且保持一致,如果其中一台的user信息变化了,那么就会传播至另外两台,则实现同步,这样做没问题,但是仅仅只是在做tomcat集群的时候tomcat很少的时候会用,一旦集群增大,有100台,那么就互相传播吧,传播是需要性能损耗的,那么整个网站的性能就会被拉低,形成网络风暴。推荐节点数量不要超过5个。

  2、分布式架构。拆分成多个子系统。 独立建立一个单点登录系统,登录后,把用户信息存储到redis,把key值存储到cookie中,当其他系统需要用户信息的时候,就可以通过读取redis中的信息,如果redis中存在,就直接使用。如果不存在就跳转到单点登录系统进行登录。单点登录系统是使用redis模拟Session,实现Session的统一管理。

   

二、单点登录系统的实现

2.1 环境准备

  • eclipse
  • redis

2.2 单点登录流程图

  这个是简单的单点登录流程图,就拿淘宝来说,当我们进入淘宝首页的时候是没有登录的,点击登录的时候,会跳转到用户登录界面。此时的用户登录界面就是咱们SSO系统的一部分,根据登录的要求,会接收用户名和密码,然后根据用户名查询密码是否正确。

  • 如果不正确就跳转到登录页,提示不正确;
  • 如果正确就要进行以下步骤:
    • 生成一个uuid,作为token;
    • 把用户信息序列化存储到redis,存储的key为token,存储成功后,返回token;
    • 把token存储到cookie;
    • 判断是否有回调url,如果有,跳转到指定url;如果没有,跳转到系统首页;

  

2.3 登录逻辑实现

【DAO层】

  单表查询,可以直接使用Mybatis逆向工程产生的代码。

【Service层 】

  • 参数:

    • 用户名:String username

    • 密码:String password

    • 返回值:E3Result,包装token

  • 业务逻辑 :

    1. 判断用户名密码是否正确。

    2. 登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。

    3. 把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。

    4. 使用String类型保存Session信息。可以使用“前缀:token”为key

    5. 设置key的过期时间。模拟Session的过期时间。一般半个小时。

    6. 返回e3Result包装token。

public e3Result login(String username, String password) {
// 1、判断用户名密码是否正确。
TbUserExample example = new TbUserExample();
Criteria criteria = example.createCriteria();
criteria.andUsernameEqualTo(username);
//查询用户信息
List<TbUser> list = userMapper.selectByExample(example);
if (list == null || list.size() == 0) {
return e3Result.build(400, "用户名或密码错误");
}
TbUser user = list.get(0);
//校验密码
if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) {
return e3Result.build(400, "用户名或密码错误");
}
// 2、登录成功后生成token。Token相当于原来的jsessionid,字符串,可以使用uuid。
String token = UUID.randomUUID().toString();
// 3、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。
// 4、使用String类型保存Session信息。可以使用“前缀:token”为key
user.setPassword(null);
jedisClient.set(USER_INFO + ":" + token, JsonUtils.objectToJson(user));
// 5、设置key的过期时间。模拟Session的过期时间。一般半个小时。
jedisClient.expire(USER_INFO + ":" + token, SESSION_EXPIRE);
// 6、返回e3Result包装token。
return e3Result.ok(token);
}
  • 发布服务:
<!-- 使用dubbo发布服务 -->
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="e3-sso"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20883"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="cn.e3mall.sso.service.LoginService" ref="loginServiceImpl" timeout="300000"/>

【Controller层】

功能分析:

  • 请求的url:/user/login

  • 请求的方法:POST

  • 参数:username、password,表单提交的数据。可以使用方法的形参接收。

  • 返回值:json数据,使用e3Result包含一个token。

  • 业务逻辑:

    • 接收两个参数。

    • 调用Service进行登录。

    • 从返回结果中取token,写入cookie。Cookie要跨域。

引用服务:

<!-- 引用dubbo服务 -->
<dubbo:application name="e3-sso-web"/>
<dubbo:registry protocol="zookeeper" address="192.168.25.128:2181"/>
<dubbo:reference interface="cn.e3mall.sso.service.TokenService" id="tokenServiceImpl"/>

LoginController:

@Controller
public class LoginController {
@Autowired
private LoginService loginService;
@Value("${COOKIE_TOKEN_KEY}")
private String COOKIE_TOKEN_KEY; @RequestMapping(value="/user/login", method=RequestMethod.POST)
@ResponseBody
public E3Result login(String username, String password,
HttpServletRequest request, HttpServletResponse response) {
// 1、接收两个参数。
// 2、调用Service进行登录。
E3Result result = loginService.login(username, password);
// 3、从返回结果中取token,写入cookie。Cookie要跨域。
String token = result.getData().toString();
CookieUtils.setCookie(request, response, COOKIE_TOKEN_KEY, token);
// 4、响应数据。Json数据。e3Result,其中包含Token。
return result;
}
}

2.4 通过token查询用户信息

【功能分析】

  • 请求的url:/user/token/{token}

  • 参数:String token需要从url中取。

  • 返回值:json数据。使用e3Result包装Tbuser对象。

  • 业务逻辑:

    1. 从url中取参数。

    2. 根据token查询redis。

    3. 如果查询不到数据。返回用户已经过期。

    4. 如果查询到数据,说明用户已经登录。

    5. 需要重置key的过期时间。

    6. 把json数据转换成TbUser对象,然后使用e3Result包装并返回。

【DAO层】

  使用JedisClient对象。

【Service层】

  • resource.properties:
#SESSION在redis的过期时间
SESSION_EXPIRE=1800
  • 实现类:
@Service
public class TokenServiceImpl implements TokenService { @Autowired
private JedisClient jedisClient;
@Value("${SESSION_EXPIRE}")
private Integer SESSION_EXPIRE; @Override
public E3Result getUserByToken(String token) {
// 根据token查询redis
String json = jedisClient.get("SESSION"+token);
if(StringUtils.isBlank(json)){
// 如果查询不到数据,返回用户已经过期
return E3Result.build(400, "用户登录已经过期,请重新登录");
}
// 如果查询到数据,说明用户已经登录
// 需要重置key的过期时间
jedisClient.expire("SESSION"+token, SESSION_EXPIRE);
// 把json数据转换成user对象,然后使用e3Result包装并返回。
TbUser user = JsonUtils.jsonToPojo(json, TbUser.class);
return E3Result.ok(user);
}
}

【Controller层】

  • 请求的url:/user/token/{token}

  • 参数:String token需要从url中取。

  • 返回值:json数据。使用e3Result包装Tbuser对象。
Controller
public class TokenController {
@Autowired
private TokenService tokenService; @RequestMapping("/user/token/{token}")
@ResponseBody
public Object getUserByToken(@PathVariable String token,String callback){
E3Result result = tokenService.getUserByToken(token);
// 响应结果之前,判断是否为jsonp请求
if(StringUtils.isNotBlank(callback)){
// 把结果封装成一个js语句响应
MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
mappingJacksonValue.setJsonpFunction(callback);
return mappingJacksonValue;
}
return result;
}
}

2.5 首页展示用户名

功能分析:

  1. 当用户登录成功后,在cookie中有token信息。

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

  3. 把用户名展示到首页。

问题:服务接口在sso系统中。Sso.e3.com(localhost:8088),在首页显示用户名称,首页的域名是www.e3.com(localhost:8082),使用ajax请求跨域了。而JS不可以跨域请求数据

什么是跨域:

  1. 域名不同
  2. 域名相同,端口号不同

解决js的跨域问题可以使用jsonp

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

2.6 Jsonp原理

  

2.7 Jsonp实现

【客户端】

var E3MALL = {
checkLogin : function(){
var _ticket = $.cookie("token");
if(!_ticket){
return ;
}
$.ajax({
url : "http://localhost:8087/user/token/" + _ticket,
dataType : "jsonp",
type : "GET",
success : function(data){
if(data.status == 200){
var username = data.data.username;
var html = username + ",欢迎来到宜立方购物网!<a href=\"http://www.e3mall.cn/user/logout.html\" class=\"link-logout\">[退出]</a>";
$("#loginbar").html(html);
}
}
});
}
} $(function(){
// 查看是否已经登录,如果已经登录查询登录信息
E3MALL.checkLogin();
});

【服务端】

  • 功能分析:
    1. 接收callback参数,取回调的js的方法名。

    2. 业务逻辑处理。

    3. 响应结果,拼接一个js语句。

  • 实现方法一:

        @RequestMapping(value="/user/token/{token}",produces=MediaType.APPLICATION_JSON_UTF8_VALUE"application/json;charset=utf-8")
    @ResponseBody
    public 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);
  • 如果spring是4.1以上的版本,可以使用方法二:
      @RequestMapping(value="/user/token/{token}")
    @ResponseBody
    public Object getUserByToken(@PathVariable String token, String callback) {
    E3Result result = tokenService.getUserByToken(token);
    //响应结果之前,判断是否为jsonp请求
    if (StringUtils.isNotBlank(callback)) {
    //把结果封装成一个js语句响应
    MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result);
    mappingJacksonValue.setJsonpFunction(callback);
    return mappingJacksonValue;
    }
    return result;
    }

参考博文:https://blog.csdn.net/kisscatforever/article/details/76409250

【SSO】单点登录系统的更多相关文章

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

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

  2. sso单点登录系统的压力测试

    环境:vmware centos7.4 2cpu 2核心 工具:ab压力测试工具 测试对象:sso单点登录系统 电脑:win10 4核 项目环境:flask+uwsgi+nginx(uwsgi 2进程 ...

  3. e3mall商城的归纳总结10之freemarker的使用和sso单点登录系统的简介

    敬给读者的话 本节主要讲解freemarker的使用以及sso单点登录系统,两种技术都是比较先进的技术,freemarker是一个模板,主要生成一个静态静态,能更快的响应给用户,提高用户体验. 而ss ...

  4. sso单点登录系统原理与实现

    sso单点登录 1.认识并理解sso及其应用,并能根据其实现原理自行实现sso 没有使用sso单点登录的系统用户再访问同一个系统的不同模块都必须的登录 使用sso单点登录,用户只需要登录一次,并且可以 ...

  5. sso单点登录系统

    sso单点登录概念 1.一处登录,处处登录.会单独做一个单点登录系统,只负责颁发token和验证token,和页面登录功能. 2.通过在浏览器cookie中放入token,和在redis中对应toke ...

  6. 统一登录中心SSO 单点登录系统的构想

    什么是单点登录?我想肯定有一部分人“望文生义”的认为单点登录就是一个用户只能在一处登录,其实这是错误的理解.单点登录指的是多个子系统只需要登录一个,其他系统不需要登录了(一个浏览器内).一个子系统退出 ...

  7. 基于cookie的SSO单点登录系统

    利用COOKIE实现单点登录功能 近期公司要求帮一个项目实现单点登录功能,在综合考量下决定采用cookie实现,大概的流程如下图所:

  8. php sso单点登录原理阐述

    原理:就是用户登录了单点登录系统(sso)之后,就可以免登录形式进入相关系统: 实现: 点击登录跳转到SSO登录页面并带上当前应用的callback地址 登录成功后生成COOKIE并将COOKIE传给 ...

  9. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

  10. Java项目接入sso单点登录

    最近在落地cat(java开发的一款开源监控系统)接入公司的内部项目,其中有项需求是接入公司的sso单点登录系统.研究了公司之前java项目接入sso系统,大部分是采用spring框架,然后依赖spr ...

随机推荐

  1. 用UltraISO把硬盘文件制作成ISO格式

    转自:https://wenku.baidu.com/view/0052c88dcc22bcd126ff0cbf.html 用UltraISO把硬盘文件制作成ISO格式方法: 制作硬盘ISO文件步骤一 ...

  2. 部署和调优 1.7 samba 部署和优化-1

    Samba服务可以实现linux上共享一个目录,windows上面访问. 安装 yum install -y samba samba-client 配置文件在 vim /etc/samba/smb.c ...

  3. FTP 命令 上传下载

    ftp   ftp [-v] [-n] [-i] [-d] [-g] [-s:filename] [-a] [-w:windowsize] [computer] 参数-v 禁止显示远程服务器响应.-n ...

  4. bluebird的安装配置

    安装 下载bluebird 3.5.0(开发) 意味着在开发中使用的未分类源文件.警告和长堆栈跟踪被启用,这会影响性能. <script src="//cdn.jsdelivr.net ...

  5. Tensorflow fetch和feed

    import tensorflow as tf #Fetch input1 = tf.constant(1.0)input2 = tf.constant(3.0)input3 = tf.constan ...

  6. 第一篇:Django基础

    Django框架第一篇基础 一个小问题: 什么是根目录:就是没有路径,只有域名..url(r'^$') 补充一张关于wsgiref模块的图片 一.MTV模型 Django的MTV分别代表: Model ...

  7. Mat类的输出格式

    从前面的例程中, 可以看到 Mat 类重载了<<操作符, 可以方便得使用流操作来输出矩阵的内容.默认情况下输出的格式是类似 Matlab 中矩阵的输出格式.除了默认格式,Mat 也支持其他 ...

  8. kaggle gradient_descent

    kaggle gradient_descent 1.描述 自写梯度下降 2.代码 import numpy as np import matplotlib.pyplot as plt # train_ ...

  9. 多线程学习-基础(一)Thread和Runnable实现多线程

    很久没记录一些技术学习过程了,这周周五的时候偶尔打开“博客园”,忽然让我产生一种重拾记录学习过程的想法,记录下学习研究过程的一点一滴,我相信,慢慢地就进步了!最近想学习一下多线程高并发,但是多线程在实 ...

  10. layui下select下拉框不显示或没有效果

    Layui会对select.checkbox.radio等原始元素隐藏,从而进行美化修饰处理.但这需要依赖于form组件,所以你必须加载 form,并且执行一个实例.值得注意的是:导航的Hover效果 ...