关键词:
单点登录原理及JWT实现
波波烤鸭
一、单点登录效果
首先我们看通过一个具体的案例来加深对单点登录的理解。案例地址:https://gitee.com/xuxueli0323/xxl-sso?_from=gitee_search 把案例代码直接导入到IDEA中
然后分别修改下server和samples中的配置信息
在host文件中配置
127.0.0.1 sso.server.com
127.0.0.1 client1.com
127.0.0.1 client2.com
然后分别启动server和两个simple服务。
访问测试:
其中一个节点登录成功后其他节点就可以访问了
自行测试。
二、单点登录实现
清楚了单点登录的效果后,我们就可以自己来创建一个单点登录的实现了。来加深下单点登录的理解了。
1.创建项目
通过Maven创建一个聚合工程,然后在工程中创建3个子模块,分别为认证服务和客户端模块。
引入相同的依赖
2.client1
我们先在client1中来提供相关的接口。我们提供一个匿名访问的接口和一个需要认证才能访问的接口。
@Controller
public class UserController
@ResponseBody
@GetMapping("/hello")
public String hello()
return "hello";
@GetMapping("/queryUser")
public String queryUser(Model model)
model.addAttribute("list", Arrays.asList("张三","李四","王五"));
return "user";
user.html中的代码为:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
<h1>用户管理:</h1>
<ul>
<li th:each="user:$list">
[[$user]]
</li>
</ul>
</body>
</html>
访问测试:
没有认证就能访问,所以得加上验证的逻辑。
@GetMapping("/queryUser")
public String queryUser(Model model, HttpSession session)
Object userLogin = session.getAttribute("userLogin");
if(userLogin != null)
// 说明登录过了,直接放过
model.addAttribute("list", Arrays.asList("张三","李四","王五"));
return "user";
// 说明没有登录,需要跳转到认证服务器认证 为了能在登录成功后跳回到当前页面,传递参数
return "redirect:http://sso.server:8080/loginPage?redirect=http://client1.com:8081/queryUser";
可以看到当我们访问queryUser请求的时候,因为没有登录所以会重定向到认证服务中的服务,做登录处理。这时就需要进入到server服务中处理
3.server服务
在服务端我们需要提供两个接口,一个调整到登录界面,一个处理认证逻辑以及一个登录页面
@Controller
public class LoginController
/**
* 跳转到登录界面的逻辑
* @return
*/
@GetMapping("/loginPage")
public String loginPage(@RequestParam(value = "redirect" ,required = false) String url, Model model)
model.addAttribute("url",url);
return "login";
/**
* 处理登录请求
* @return
*/
@PostMapping("/ssoLogin")
public String login(@RequestParam("userName") String userName,
@RequestParam("password") String password,
@RequestParam(value = "url",required = false) String url)
if("zhangsan".equals(userName) && "123".equals(password))
// 登录成功
return "redirect:"+url;
// 登录失败重新返回登录页面
return "redirect:loginPage";
登录页面代码逻辑
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>sso-server-login</title>
</head>
<body>
<h1>Server登录页面</h1>
<form action="/ssoLogin" method="post" >
账号:<input type="text" name="userName" ><br/>
密码:<input type="password" name="password"><br/>
<input type="hidden" name="url" th:value="$url">
<input type="submit" value="提交">
</form>
</body>
</html>
然后当我们在client1中访问需要认证的服务的时候就会跳转到登录界面
提交登录操作。当我们提交登录成功的情况,应该要重定向会原来的访问地址,但实际情况和我们所想的有点出入:
原来的queryUser中的逻辑为:
4. 认证凭证
上面的问题是我们在认证服务登录成功了,但是client1中并不知道登录成功了,所以认证成功后需要给client1一个认证成功的凭证。也就是Token信息。
/**
* 处理登录请求
* @return
*/
@PostMapping("/ssoLogin")
public String login(@RequestParam("userName") String userName,
@RequestParam("password") String password,
@RequestParam(value = "url",required = false) String url)
if("zhangsan".equals(userName) && "123".equals(password))
// 通过UUID生成Token信息
String uuid = UUID.randomUUID().toString().replace("-","");
// 把生成的信息存储在Redis服务中
redisTemplate.opsForValue().set(uuid,"zhangsan");
// 登录成功
return "redirect:"+url+"?token="+uuid;
// 登录失败重新返回登录页面
return "redirect:loginPage";
生成的Token同步保存在了Redis中,然后在重定向的地址中携带了token信息。然后在client1中处理
@GetMapping("/queryUser")
public String queryUser(Model model,
HttpSession session,
@RequestParam(value = "token",required = false) String token)
if(token != null)
// token有值 说明认证了
// TODO 基于token 去服务器获取用户信息
session.setAttribute("userLogin","张三");
Object userLogin = session.getAttribute("userLogin");
if(userLogin != null)
// 说明登录过了,直接放过
model.addAttribute("list", Arrays.asList("张三","李四","王五"));
return "user";
// 说明没有登录,需要跳转到认证服务器认证 为了能在登录成功后跳回到当前页面,传递参数
return "redirect:http://sso.server.com:8080/loginPage?redirect=http://client1.com:8081/queryUser";
然后我们就可以来访问client1中的服务了
5. client2
控制器逻辑:
@Controller
public class OrderController
@GetMapping("/order")
public String getOrder(HttpSession session, Model model)
Object userLogin = session.getAttribute("userLogin");
if(userLogin != null)
// 说明认证了
model.addAttribute("list", Arrays.asList("order1","order2","order3"));
return "order";
return "redirect:http://sso.server.com:8080/loginPage?redirect=http://client2.com:8082/order";
order.html页面内容:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
<h1>订单管理:</h1>
<ul>
<li th:each="order:$list">
[[$order]]
</li>
</ul>
</body>
</html>
通过前面的介绍我们可以发现clent1认证后可以访问了,但是client2提交请求的时候还是会跳转到server服务,做认证的处理。
造成这个的原因是client1认证成功后在Session中保存了认证信息,但是在client2是获取不到的,这时我们可以在Server服务登录成功后在浏览器的Cookie中存储一个token信息,然后在其他服务跳转到要进入登录页面之前的接口服务中判断Cookie中是否有值,如果有则认为是其他服务登录过的,直接放过。
提交请求的时候校验
搞定
三、JWT实现
1.JWT介绍
1.1 什么是JWT
官方:JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA .
JSON Web 令牌(JWT)是一种开放标准(RFC 7519) ,它定义了一种紧凑和自包含的方式,用于作为 JSON 对象在各方之间安全地传输信息。可以验证和信任此信息,因为它是数字签名的。JWTs 可以使用 secret (使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
通俗的解释:JWT简称 JSON Web Token,也就是JSON形式作为Web应用中的令牌信息,用于在各方之间安全的将信息作为JSON对象传输,在数据传输过程中可以完成数据加密,签名等操作。
1.2 基于Session认证
我们最先接触到的认证方式就是基于Session的认证方式,每一个会话在服务端都会存储在HttpSession中,相当于一个Map,然后通过Cookie的形式给客户端返回一个jsessionid,然后每次访问的时候都需要从HttpSession中根据jsessionid来获取,通过这个逻辑来判断是否是认证的状态。
存在的问题:
- 每个用户都需要做一次记录,而Session一般情况下都会存在内存中,增大了服务器的开销
- 集群环境下Session需要同步,或者分布式Session来处理
- 因为是基于Cookie来传输的,如果Cookie被解惑,用户容易受到CSRF攻击。
- 前后端分离项目中会更加的麻烦
1.3 基于JWT的认证
具体流程如下:
认证的流程:
- 用户通过表单把账号密码提交到后端服务后,如果认证成功就会生成一个对应的Token信息
- 之后用户请求资源都会携带这个Token值,后端获取到后校验通过放行,校验不通过拒绝
jwt的优势:
- 简介:可以通过URL,POST参数或者HTTP header发送,因为数据量小,传输速度快。
- 自包含:负载中包含了所有用户所需的信息,避免多次查询数据
- 夸语音:以JSON形式保存在客户端。
- 不需要服务端保存信息,适合分布式环境。
1.4 JWT的结构
令牌的组成:
- 标头(Header)
- 有效载荷(Payload)
- 签名(Signature)
因此JWT的格式为: xxxx.yyyy.zzzz Header.Payload.Signature
Header:
header通常由两部分组成:令牌的类型【JWT】和所使用的签名算法。例如HMAC、SHA256或者RSA,它会使用 Base64 编码组成 JWT结构的第一部分。注意:Base64是一种编码,是可以被翻译回原来的样子的。
"alg":"HS256",
"typ":"JWT"
Payload:
令牌的第二部分是有效负载,其中包含声明,声明是有关实体(通常是用户信息)和其他数据的声明,它会使用Base64来编码,组成JWT结构的第二部分。
"userId":"123",
"userName":"波波烤鸭",
"admin":true
因为会通过Base64编码,所以不要把敏感信息写在Payload中。
Signature:
签名部分,前面两部分都是使用 Base64 进行编码的,即前端可以解开header和payload中的信息,Signature需要使用编码后的 header 和 payload 以及我们提供的一个秘钥,然后使用 header 中指定的前面算法(HS256) 进行签名,签名的作用是保证 JWT 没有被篡改过
2.JWT实现
2.1 JWT基本实现
生成Token令牌
/**
* 生成Token信息
*/
@Test
void generatorToke()
Map<String,Object> map = new HashMap<>();
map.put("alg","HS256");
map.put("typ","JWT");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND,60);
String token = JWT.create()
.withHeader(map) // 设置header
.withClaim("userid", 666) // 设置 payload
// 设置过期时间
.withExpiresAt(calendar.getTime())
.withClaim("username", "波波烤鸭") // 设置 payload
.sign(Algorithm.HMAC256("qwaszx")); // 设置签名 保密
System.out.println(token);
根据Token来验证是否正确。
/**
* 验证Token信息
*/
@Test
public void verifier()
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTMwNTE5ODUsInVzZXJpZCI6NjY2LCJ1c2VybmFtZSI6IuazouazoueDpOm4rSJ9.0LW5MFihMeYNfRfez0a68ncaKQ13j5pSnVZTB7m1CDw";
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("qwaszx")).build();
DecodedJWT verify = jwtVerifier.verify(token);
System.out.println(verify.getClaim("userid").asInt());
System.out.println(verify.getClaim("username").asString());
验证中场景的异常信息:
- SignatureVerificationException 签名不一致异常
- TokenExpiredException Token过期异常
- AlgorithmMismatchException 算法不匹配异常
- InvalidClaimException 失效的payload异常
2.2 JWT封装
为了简化操作我们可以对上面的操作进一步封装来简化处理
package com.bobo.jwt.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
/**
* JWT操作的工具类
*/
public class JWTUtils
private static final String SING = "123qwaszx";
/**
* 生成Token header.payload.sing 组成
* @return
*/
public static String getToken(Map<String,String> map)
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7); // 默认过期时间 7天
JWTCreator.Builder builder = JWT.create();
// payload 设置
map.forEach((k,v)->
builder.withClaim(k,v);
);
// 生成Token 并返回
return builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));
/**
* 验证Token
* @return
* DecodedJWT 可以用来获取用户信息
*/
public static DecodedJWT verify(String token)
// 如果不抛出异常说明验证通过,否则验证失败
return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
2.3 SpringBoot应用
首先是在登录方法中,如果登录成功,我们需要生成对应的Token信息,然后将Token信息响应给客户端。
@PostMapping("/login")
public Map<String,Object> login(User user)
Map<String,Object> res = new HashMap<>();
if第十一篇springsecurity基于jwt实现token的处理(代码片段)
SpringSecurity基于JWT实现Token的处理 前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。一、认证思路分析 SpringSe... 查看详情
jwt实现单点登录(代码片段)
一写jwt的配置类publicclassJwtUtils//定义两个常量publicstaticfinallongEXPIRE=1000*60*60*24;//设置token过期时间publicstaticfinalStringAPP_SECRET="ukc8BDbRigUDaY6pZFfWus2jZWLPHO";//密钥,随便写,做加密操作//生成token字符串的方法publicstatic 查看详情
简单轻松实现单点登录(sso)(代码片段)
前言:本文虽然使用PHP语言讲单点登录,但是更多讲的是一种实现思想,不仅限于PHP。该单点登录方案完全实现跨域名。JWT的使用只是生成一个唯一token字符串,也可以不利用JWT。重要存储Token方式,Cookie与Redi... 查看详情
深入浅出,jwt单点登录实例+原理(代码片段)
深入浅出,JWT单点登录实例案例演示:首先引入jwt相关依赖Controller:@Check自定义注解:JwtUtils工具类:Interceptor拦截器:UserUtil用户工具类ThreadLocalUtil用户工具类好开始测试(请带入角色)!... 查看详情
初识单点登录及jwt实现(代码片段)
单点登录多系统,单一位置登录,实现多系统同时登录的一种技术(三方登录:某系统使用其他系统的用户,实现本系统登录的方式。如微信登录、支付宝登录)单点登录一般是用于互相授信的系统,实现单一位置登录,全系统... 查看详情
flask初识,第十篇,flask中的装饰器before_requestafter_request(代码片段)
Flask我们已经学习很多基础知识了,现在有一个问题我们现在有一个Flask程序其中有3个路由和视图函数,如下: 简单的小程序如果登陆了,就可以访问index和home页面,如果没登录就跳转到login登录要怎么解决呢,session对,用session... 查看详情
第十一篇springsecurity基于jwt实现token的处理
SpringSecurity基于JWT实现Token的处理 前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。一、认证思路分析 SpringSe... 查看详情
sso单点登录的原理详解,及shiro同时支持session和jwttoken两种认证方式,和session和jwt整合方案
1. 单点登录是什么?单点登录全称SingleSignOn(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。 2. 单点登录... 查看详情
springsecurity+jwt实现单点登录,还有谁不会??
本文我们来看下SpringSecurity+JWT实现单点登录操作,本文2W字,预计阅读时间30min,文章提供了代码骨架,建议收藏。一、什么是单点登陆单点登录(SingleSignOn),简称为SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的... 查看详情
单点登录原理与简单实现(代码片段)
一、单系统登录机制1、http无状态协议web应用采用browser/server架构,http作为通信协议。http是无状态协议,浏览器的每一次请求,服务器会独立处理,不与之前或之后的请求产生关联,这个过程用下图说明,三次请求/响应对之间... 查看详情
c++从青铜到王者第十篇:stl之vector类的模拟实现(代码片段)
系列文章目录文章目录系列文章目录前言一、vector深度剖析及模拟实现1.vector的核心接口模拟实现2.vector的核心接口测试3.使用memcpy拷贝问题4.动态二维数组理解总结前言一、vector深度剖析及模拟实现1.vector的核心接口模拟实现names... 查看详情
springboot+security+jwt实现单点登录
本次整合实现的目标:1、SSO单点登录2、基于角色和springsecurity注解的权限控制。 整合过程如下: 1、使用maven构建项目,加入先关依赖,pom.xml如下:<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/... 查看详情
深入浅出spring原理及实战「开发实战系列」springsecurity与jwt实现权限管控以及登录认证指南(代码片段)
...种生成加密用户身份信息的Token,特别适用于分布式单点登陆的场景,无需在服务端保存用户的认证信息,而是直接对Token进行校验获取用户信息,使单点登录更为简单灵活。系统搭建环境管控SpringBoot版本:2.1.6Sprin... 查看详情
第十篇io流技术(代码片段)
packagecom.zzp.commons;importjava.io.File;importorg.apache.commons.io.FileUtils;/****大小*@authorjava**/publicclassCIOTest01publicstaticvoidmain(String[]args)//文件的大小longlen=FileUtils.sizeOf(newFile("s 查看详情
jwt实现授权认证(代码片段)
...于数据持久层。这样的方式,对于结构维护成本大,实现单点登录较为复杂,且没有分布式架构,无法支持横向扩展,风险较大(如果持久层失败,整个认证体系都会挂掉)。JWT则无须持久化会话数据,是以加密签名的方式实现了... 查看详情
sso单点登录总结(php)(代码片段)
...理解的知识整理汇总,如有不足之处,请大家多多指正。单点登录(SSO——SingleSignOn)的应用是很普遍的,尤其在大型网站系统中,比如百度,登录百度账号和,再转到百度经验、百度贴吧等是不用重新登录的。本文将从cookie和... 查看详情
第十篇flowable中的候选人组(代码片段)
当候选人很多的情况下,我们可以分组来处理。先创建组,然后把用户分配到这个组中。候选人组1.管理用户和组1.1用户管理 我们需要先单独维护用户信息。后台对应的表结构是ACT_ID_USER./***维护用户*/@Testpublic... 查看详情
单点登录实现原理和单点登录服务器设计
sso单点登录是什么java实现SSO什么是SSOSSO(SingleSignOn)单点登录是实现多个系统之间统一登录的验证系统,简单来说就是:有A,B,C三个系统,在A处登录过后,再访问B系统,B系统就已经处于了... 查看详情