前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单点登录

单点登录

作者头像
周杰伦本人
发布2022-10-25 15:49:16
1.2K0
发布2022-10-25 15:49:16
举报
文章被收录于专栏:同步文章

SSO英文全称Single Sign On,单点登录; SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

1)、任何系统都必须去登陆服务器进行登录 2)、服务器就记住了登录状态 3)、其他系统访问受保护资源,需要再次登录,跳转到sso_server登录的时候,服务器告诉客户端,已经登录过,无须登录。登录过得信息

单点登录开源框架:https://gitee.com/xuxueli0323/xxl-sso

每一个应用下都有一个相同的cookie。单点登录的核心就是不同系统之间同步cookie即可。

由于不同域名之间cookie没法共享 认证服务器只能将自己旗下的登录过的用户的标识以url地址参数的方式交给另外一个域名。让这个域名对应的服务器,自己妥善保存这个cookie信息。 所以说解决跨域名cookie共享问题我们采用通过url地址携带

客户端代码:

application.properties

代码语言:javascript
复制
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

##单点登录url
sso.server.url=http://ssoserver.com:8082
## 单点登录请求路径
sso.server.loginpath=/login

SsoConfig 读取配置文件中的配置

代码语言:javascript
复制
package com.xiepanpan.gmall.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author: xiepanpan
 * @Date: 2020/3/2
 * @Description: sso配置类
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "sso.server")
public class SsoConfig {

    private String url;
    private String loginPath;
}

HelloController.java

代码语言:javascript
复制
package com.xiepanpan.gmall.controller;

import com.xiepanpan.gmall.config.SsoConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: xiepanpan
 * @Date: 2020/3/2
 * @Description: 
 */
@Controller
public class HelloController {

    @Autowired
    SsoConfig ssoConfig;

    @GetMapping("/")
    public String index(Model model, @CookieValue(value = "sso_user",required = false)String ssoUserCookie,
                        @RequestParam(value = "sso_user",required = false) String ssoUserParam,
                        HttpServletRequest request,
                        HttpServletResponse response) throws IOException {
        StringBuffer requestURL = request.getRequestURL();
        if (!StringUtils.isEmpty(ssoUserParam)) {
            //没有去登录页面登录 跳转回来 说明已经远程登录了
            Cookie sso_user = new Cookie("sso_user",ssoUserParam);
            response.addCookie(sso_user);
        }
        //判断是否登录
        if (StringUtils.isEmpty(ssoUserCookie)) {
            //没登录 重定向到登录服务器
            String url = ssoConfig.getUrl() + ssoConfig.getLoginPath() + "?redirect_url=" + requestURL.toString();
            response.sendRedirect(url);
            return null;
        }
        //登录了,redis.get(ssoUserCookie)获取到用户信息,
        model.addAttribute("loginUser","XP");
        return "index";

    }

}

index.html

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>client客户端</title>
</head>
<body>
    <h1>欢迎 <label th:text="${loginUser}"></label></h1>
</body>
</html>

服务端代码

配置文件

代码语言:javascript
复制
server.port=8082
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

spring.redis.host=192.168.217.130

LoginController.java

代码语言:javascript
复制
package com.xiepanpan.gmall.controller;

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author: xiepanpan
 * @Date: 2020/3/2
 * @Description: 登录控制层
 */
@Controller
public class LoginController {

    @Autowired
    StringRedisTemplate redisTemplate;

    /**
     * 登录
     * @param redirectUrl
     * @param ssoUser
     * @param response
     * @return
     * @throws IOException
     */
    @GetMapping("/login")
    public String login(@RequestParam(value = "redirect_url")String redirectUrl,
                        @CookieValue(value = "sso_user",required = false)String ssoUser,
                        HttpServletResponse response, Model model) throws IOException {
        //判断是否登录过
        if (!StringUtils.isEmpty(ssoUser)) {
            //登录过 回到之前的地方 并且把当前ssoserver获取到的cookie以url参数方式传递到其他域名 实现cookie同步
            String url = redirectUrl + "?" + "sso_user=" + ssoUser;
            response.sendRedirect(url);
            return null;
        }else {
            //转到login.html页面
            model.addAttribute("redirect_url",redirectUrl);
            return "login";
        }
    }

    /**
     * 处理登录操作
     * @param username
     * @param password
     * @param response
     * @param redirectUrl
     * @throws IOException
     */
    @PostMapping("/doLogin")
    public void doLogin(String username,String password,HttpServletResponse response,
                        @RequestParam(value = "redirect_url")String redirectUrl) throws IOException {
        //1. 模拟用户信息
        Map<String,Object> map = new HashMap<>();
        map.put("username",username);
        map.put("email",username+"@qq.com");
        //2. 标识用户登录
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, JSON.toJSONString(map));

        //3. 登录成功做两件事
        // (1) 浏览器保存当前token作为cookie sso_user=token
        Cookie cookie = new Cookie("sso_user",token);
        response.addCookie(cookie);
        // (2) 重定向到之前的路径
        response.sendRedirect(redirectUrl+"?"+"sso_user="+token);
    }
}

登录页面login.html:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form th:action="@{/doLogin}" method="post">
        用户名:<input name="username"/><br/>
        密码:<input name="password" type="password"><br/>
        <input type="hidden" name="redirect_url" th:value="${redirect_url}"/>
        <input type="submit" value="登录"/>
    </form>
</body>
</html>

整个流程图:

大体思路是这样的 模拟两个客户端 一个认证中心 第一个客户端通过认证中心输入密码登录 第二个客户端再通过认证中心登录就能访问到数据 添加hosts

代码语言:javascript
复制
127.0.0.1 client1.com
127.0.0.1 client2.com
127.0.0.1 ssoserver.com

client1流程

  1. 客户端client1 发起请求 重定向到ssoserver
  2. ssoserver 跳转到登录页面进行登录 登录成功后 并生成一个随机数 (1). 使用sso_user作为key 生成的随机数作为value写入cookie (2). 这个随机数作为key 用户信息作为value写入redis 方便client通过key从redis中获取用户信息 然后重定向到client1 携带sso_user=生成的随机值
  3. client1 写判断是否本地cookie中有值 没有使用sso_user作为key 生成的随机数作为value写入cookie 这样client1.com:8080 和ssoserver.com:8082对应的cookie一致 然后从redis中读取用户信息 返回主页

client2流程

  1. client2 发起请求 重定向到ssoserver
  2. ssoserver发起请求自然会携带刚刚写入的cookie信息 有cookie信息后说明已经登录了 校验一下没问题就不用跳转到登录页面了 直接重定向到client2并携带sso_user=生成的随机值
  3. client2 判断本地没有cookie信息 写入cookie信息 然后从redis读取用户信息 返回主页

这只是一个简单的模拟 当然还有很多问题。。

客户端每次发送请求要去服务器认证中心校验 cookie中对应的value是否有效 如果被篡改 匹配不到 要重新登录

那么每次都要去服务器认证 怎么摆脱认证中心 自己认证行不行 可以使用jwt+RSA jwt就是把请求认证中心返回的随机值改成jwt形式 jwt里面包含用户信息 只要客户端自己能从jwt中解析出来用户信息就可以了 因为请求认证中心的本质也是获取用户信息 这样就不用频繁的每次请求都调用认证中心去认证了

多端登录和适配:

参考博客: https://blog.csdn.net/keketrtr/article/details/73771652

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-03-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 客户端代码:
  • 服务端代码
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档