当前位置: 首页 > news >正文

SSO单点登录

本文是用jjwt 来做token,然后实现简单的单点登录的流程,主要用于了解单点登录流程,快速 理解单点登录

业务场景

现在假如我是公司老板,我手下有两个部门,一个是A部门,一个是B部门,AB两个部门功能不一,现在我想看登录A部门看A部门的业务情况,我得有账号密码,我登陆B部门看B部门的情况,我还得有账号密码,这样太麻烦了,我作为老板居然还要记着你们每个部门系统的账号密码,所以我现在要做一个系统, 一个母系统,要求我只要在这个母系统上登录之后,我在母系统里面点AB两个系统能跳转到AB两个系统里面,而且不用在额外登录,但是同时,也保留AB这两个系统的单独的登录情况

单点登录

那么我可以针对上面的业务情况我这样做,

老板->>母系统: 用老板账号登录母系统->>SSO认证中心: 验证身份SSO认证中心-->>母系统: 签发全局Token老板->>母系统: 点击"进入A系统"母系统->>子系统A: 携带加密Token跳转子系统A->>SSO认证中心: 验证Token有效性SSO认证中心-->>子系统A: 返回老板权限子系统A-->>老板: 自动进入A系统(无需登录)同理完成B系统访问

 其实说白了sso 就扮演一个中间人的角色 ,所有的权限认证token办法都需要经过sso

在我们的demo中,来做一个小验证,我们的流程如下,模拟一个sso认证中心

老板》》登录母系统》》登陆通过返回一个sso——token
前端存到cookie中》》在母系统中访问A系统 》》自动验证SSOtoken
》》验证通过可以登录
-------------------
如果单独访问A系统》》有单独的登录接口,
正常也应该是弄一个认证中心,这里我们也把token存到cookie中去

后端代码

jwt

package com.example.ssodemo.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;public class JwtUtil {// 使用安全的密钥生成方式private static final Key SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);private static final long EXPIRATION_TIME = 3600000; // 1小时public static String generateToken(String username) {return Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)).signWith(SECRET_KEY).compact();}public static Claims parseToken(String token) {return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody();}public static boolean validateToken(String token) {try {Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token);return true;} catch (Exception e) {return false;}}
}

ssocontroller层

package com.example.ssodemo.controller;/*** @author wyz* @date 2025-04-27 21:24*/
import com.example.ssodemo.util.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/sso")
public class SsoController {@PostMapping("/login")public String login(@RequestParam String username,@RequestParam String password) {log.info("sdengli");// 简单模拟登录验证if ("admin".equals(username) && "123456".equals(password)) {log.info("sdengli11");return JwtUtil.generateToken(username);}throw new RuntimeException("用户名或密码错误");}@GetMapping("/validate")public boolean validate(@RequestParam String token) {return JwtUtil.validateToken(token);}@GetMapping("/userinfo")public String getUserInfo(@RequestParam String token) {Claims claims = JwtUtil.parseToken(token);return claims.getSubject();}
}

子系统A

package com.example.ssodemo.controller;/*** @author wyz* @date 2025-04-27 21:26*/
import com.example.ssodemo.util.JwtUtil;
import org.springframework.web.bind.annotation.*;@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/subsystemA")
public class SubsystemAController {// 子系统自己的登录接口@PostMapping("/login")public String subsystemLogin(@RequestParam String username,@RequestParam String password) {// 子系统自己的认证逻辑if ("user".equals(username) && "sub123".equals(password)) {return JwtUtil.generateToken(username);}throw new RuntimeException("子系统登录失败");}// 通过SSO令牌访问的接口@GetMapping("/sso-access")public String ssoAccess(@RequestParam String token) {if (JwtUtil.validateToken(token)) {String username = JwtUtil.parseToken(token).getSubject();return "欢迎 " + username + " 通过SSO访问子系统";}throw new RuntimeException("无效的SSO令牌");}
}

子系统B

package com.example.ssodemo.controller;/*** @author wyz* @date 2025-04-27 21:55*/import com.example.ssodemo.util.JwtUtil;
import org.springframework.web.bind.annotation.*;@RestController
@CrossOrigin(origins = "*")
@RequestMapping("/subsystemB")
public class SubsystemBController {// 子系统自己的登录接口@PostMapping("/login")public String subsystemLogin(@RequestParam String username,@RequestParam String password) {// 子系统自己的认证逻辑if ("user".equals(username) && "123".equals(password)) {return JwtUtil.generateToken(username);}throw new RuntimeException("子系统登录失败");}// 通过SSO令牌访问的接口@GetMapping("/sso-access")public String ssoAccess(@RequestParam String token) {if (JwtUtil.validateToken(token)) {String username = JwtUtil.parseToken(token).getSubject();return "欢迎 " + username + " 通过SSO访问子系统";}throw new RuntimeException("无效的SSO令牌");}
}

拦截器

package com.example.ssodemo.config;import com.example.ssodemo.util.JwtUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author wyz* @date 2025-04-27 21:35*/
@Component
public class TokenHandler implements HandlerInterceptor {// 免认证路径private static final String[] EXCLUDE_PATHS = {"/subsystemA/login","/sso/login","/subsystemA/sso-access","/subsystemB/login","/subsystemB/sso-access"};
//前置处理@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {for (String excludePath : EXCLUDE_PATHS) {if (request.getRequestURI().contains(excludePath)) {return true;}}//不包含匹配的路径String token = request.getParameter("token");if (token != null && JwtUtil.validateToken(token)) {return true;}response.sendRedirect("/subsystem/login.html");return false;}}

前端代码

母系统

<!DOCTYPE html>
<html><head><title>母系统登录</title>
</head><body><h1>母系统登录</h1><input type="text" id="username" placeholder="用户名" value="admin"><input type="password" id="password" placeholder="密码" value="123456"><button onclick="login()">登录</button><p id="message"></p><div id="subsystems" style="display:none;"><h3>子系统链接</h3><a href="subsystemA.html">子系统A</a><a href="subsystemB.html">子系统B</a></div><script>function login() {const username = document.getElementById('username').value;const password = document.getElementById('password').value;fetch('http://localhost:8080/sso/login?username=' + username + '&password=' + password, {method: 'POST'}).then(response => {console.log("111")if (!response.ok) throw new Error('登录失败');return response.text();}).then(token => {localStorage.setItem('sso_token', token);document.getElementById('message').textContent = '登录成功!';document.getElementById('subsystems').style.display = 'block';}).catch(() => {document.getElementById('message').textContent = '登录失败';});}// 检查是否已有tokenif (localStorage.getItem('sso_token')) {document.getElementById('subsystems').style.display = 'block';document.getElementById('message').textContent = '您已登录';}</script>
</body></html>

子系统A

<!DOCTYPE html>
<html><head><title>子系统A</title>
</head><body><div id="login-form" style="display:none;"><h1>子系统A登录</h1><input type="text" id="username" placeholder="用户名" value="user"><input type="password" id="password" placeholder="密码" value="sub123"><button onclick="localLogin()">本地登录</button><p id="message"></p></div><div id="system-content" style="display:none;"><h1>欢迎访问子系统A</h1><p id="welcome-message"></p><button onclick="accessResource()">访问资源</button><p id="resource-message"></p></div><script>// 页面加载时检查SSO令牌window.onload = function () {const ssoToken = localStorage.getItem('sso_token');if (ssoToken) {// 验证SSO令牌fetch('http://localhost:8080/sso/validate?token=' + ssoToken).then(response => response.json()).then(valid => {if (valid) {// 令牌有效,直接进入系统showSystemContent('SSO');} else {// 令牌无效,清除并显示本地登录localStorage.removeItem('sso_token');showLoginForm();}});} else {// 无SSO令牌,显示本地登录showLoginForm();}};function showLoginForm() {document.getElementById('login-form').style.display = 'block';}function showSystemContent(loginType) {document.getElementById('system-content').style.display = 'block';document.getElementById('welcome-message').textContent =`您已通过${loginType}方式登录`;}function localLogin() {const username = document.getElementById('username').value;const password = document.getElementById('password').value;fetch('http://localhost:8080/subsystemA/login?username=' + username + '&password=' + password, {method: 'POST'}).then(response => {if (!response.ok) throw new Error('登录失败');return response.text();}).then(token => {localStorage.setItem('subsystemA_token', token);showSystemContent('本地');}).catch(() => {document.getElementById('message').textContent = '登录失败';});}function accessResource() {const ssoToken = localStorage.getItem('sso_token');const localToken = localStorage.getItem('subsystemA_token');const token = ssoToken || localToken;fetch('http://localhost:8080/subsystemA/sso-access?token=' + token).then(response => response.text()).then(result => {document.getElementById('resource-message').textContent = result;}).catch(() => {document.getElementById('resource-message').textContent = '访问资源失败';});}</script>
</body></html>

子系统B

<!DOCTYPE html>
<html><head><title>子系统A</title>
</head><body><div id="login-form" style="display:none;"><h1>子系统A登录</h1><input type="text" id="username" placeholder="用户名" value="user"><input type="password" id="password" placeholder="密码" value="sub123"><button onclick="localLogin()">本地登录</button><p id="message"></p></div><div id="system-content" style="display:none;"><h1>欢迎访问子系统A</h1><p id="welcome-message"></p><button onclick="accessResource()">访问资源</button><p id="resource-message"></p></div><script>// 页面加载时检查SSO令牌window.onload = function () {const ssoToken = localStorage.getItem('sso_token');if (ssoToken) {// 验证SSO令牌fetch('http://localhost:8080/sso/validate?token=' + ssoToken).then(response => response.json()).then(valid => {if (valid) {// 令牌有效,直接进入系统showSystemContent('SSO');} else {// 令牌无效,清除并显示本地登录localStorage.removeItem('sso_token');showLoginForm();}});} else {// 无SSO令牌,显示本地登录showLoginForm();}};function showLoginForm() {document.getElementById('login-form').style.display = 'block';}function showSystemContent(loginType) {document.getElementById('system-content').style.display = 'block';document.getElementById('welcome-message').textContent =`您已通过${loginType}方式登录`;}function localLogin() {const username = document.getElementById('username').value;const password = document.getElementById('password').value;fetch('http://localhost:8080/subsystemA/login?username=' + username + '&password=' + password, {method: 'POST'}).then(response => {if (!response.ok) throw new Error('登录失败');return response.text();}).then(token => {localStorage.setItem('subsystemA_token', token);showSystemContent('本地');}).catch(() => {document.getElementById('message').textContent = '登录失败';});}function accessResource() {const ssoToken = localStorage.getItem('sso_token');const localToken = localStorage.getItem('subsystemA_token');const token = ssoToken || localToken;fetch('http://localhost:8080/subsystemA/sso-access?token=' + token).then(response => response.text()).then(result => {document.getElementById('resource-message').textContent = result;}).catch(() => {document.getElementById('resource-message').textContent = '访问资源失败';});}</script>
</body></html>

演示流程

1

2

3

 4 单独使用系统A登录

http://www.xdnf.cn/news/195751.html

相关文章:

  • 通过DeepSeek大语言模型控制panda机械臂,听懂人话,拟人性回答。智能机械臂助手又进一步啦
  • 大模型在肝硬化腹水风险预测及临床方案制定中的应用研究
  • AWS虚拟专用网络全解析:从基础到高级实践
  • 【Spark入门】Spark架构解析:组件与运行机制深度剖析
  • vim粘贴代码格式错乱 排版错乱 缩进错乱 解决方案
  • 【软件工程】需求分析详解
  • 24体育NBA足球直播M28模板体育赛事直播源码
  • 介绍下Nginx的作用与请求转发机制
  • Windows操作系统核心知识解析
  • C++ 表达式求值优先级、结合律与求值顺序(五十九)
  • 关于https请求丢字符串导致收到报文解密失败问题
  • 第二章:Agent System
  • RestRequest ,newtonsoft解析
  • 大模型(LLMs)强化学习—— PPO
  • 【angular19】入门基础教程(一):项目的搭建与启动
  • 如何查看电脑电池使用情况
  • 北京市延庆区“禅苑茶事“非遗项目挂牌及茶事院正式启用
  • Adobe Lightroom Classic v14.3.0.8 一款专业的数字摄影后期处理软件
  • 测试反馈陷入死循环?5大策略拆解新旧Bug难题
  • if consteval
  • 多模态大型模型,实现以人为中心的精细视频理解
  • [原创](现代Delphi 12指南):[macOS 64bit App开发]: 跨平台开发同样支持retain()引用计数器处理.
  • 【氮化镓】质子辐照对 GaN-on-GaN PiN 二极管电导调制的影响
  • 后端Web实战之登录认证,JWT令牌,过滤器Filter,拦截器Interceptor一篇文章so easy!!!
  • 【python】-基础语法1
  • 颖儿生活提案:用海信璀璨505U6真空冰箱重建都市鲜食自由
  • 蓝桥杯 3. 压缩字符串
  • 树莓派5+edge-tts 语音合成并进行播放测试
  • EtherCAT转EtherNet/IP网关CEI-382实现罗克韦尔PLC与和利时伺服电机通讯
  • FFmpeg源码学习---ffmpeg