单点登录

SSO,单点登录是指多个系统中间,一个系统登录了,所有系统就登录了;一个系统退出了,所有的系统就退出了

有多种实现方式


机密客户端

适用于能够安全存储客户端密钥的应用类型。这种模式通常用于服务器到服务器的通信,其中客户端(如后端服务或API)需要代表用户或自身访问受保护的资源

例如:要在系统A实现访问系统B时免密登录

措施:在系统A的客户端中保存一个系统B提供的账号和密码,系统B要提供生成Token的接口给系统A,系统A在访问系统B时,首先携带相关信息(系统B的账号密码)去访问系统B生成token的接口,生成一个用来访问系统B的token,然后系统A在携带这个token去访问系统B,这样就实现了系统A对系统B的免密登录。如果有相关限制,要同时是系统A和系统B的用户才能直接访问系统B,则需要在系统A也维护一个系统B的基础用户信息表,用来进行相关的验证

实现思路:这是基于我遇到的一个需求写的

先获取请求中的当前登录人员的组织id、用户id、以及是否以新选项卡的方式弹出

判定登录用户的组织是否和xXInfo中的组织相同(当前登录用户是否属于xx这个组织)

—属于xx这个组织

​ 根据用户id查询tb_xxuser数据库表中是否有这个用户

​ —有这个用户

​ 向认证服务发起认证请求,并接收响应解析出响应体

​ —响应成功,状态码为200

​ —如果响应体中包含数据

​ 使用xxInfo.getIndexUrl()作为基础URL并附加从响应体中获取的token字段的值来构造一个新的URL tmpUrl

​ —如果变量 isNewTab 不为 null 并且等于字符串 “1”,则打开一个新的浏览器标签页并导航到 tmpUrl

​ —否则直接重定向到tmpUrl

​ —xx用户不存在:您的账号无法访问!

—xxInfo中的组织信息与传递过来的组织信息不匹配:orgId为空或您当前orgId无权限访问!


OAuth 2.0 实现单点登录

基本概念

  • 资源拥有者:希望访问受保护资源的用户
  • 客户端:代表用户请求访问资源的应用程序
  • 授权服务器:负责认证用户身份并发放访问令牌的服务器
  • 资源服务器:存储受保护资源的服务器,根据访问令牌提供资源

流程

  1. 用户访问客户端:用户尝试访问客户端应用程序。
  2. 客户端重定向到授权服务器:如果用户未认证,客户端将用户重定向到授权服务器。
  3. 用户登录:用户在授权服务器上进行登录。
  4. 授权决定:用户同意授权后,授权服务器发放授权码或访问令牌。
  5. 客户端获取访问令牌:客户端使用授权码交换访问令牌。
  6. 客户端访问资源服务器:客户端使用访问令牌请求资源服务器上的资源。
  7. 资源服务器返回资源:资源服务器验证访问令牌并返回请求的资源。

授权模式

OAuth 2.0定义了四种授权模式,适用于不同的应用场景:

  • 授权码模式(Authorization Code Grant):最常用的模式,适用于可以安全存储客户端密钥的服务器端应用程序。
  • 隐式授权模式(Implicit Grant):适用于无服务端组件的客户端应用程序,如JavaScript Web应用程序。
  • 密码模式(Resource Owner Password Credentials Grant):适用于用户对客户端高度信任的情况,用户直接提供用户名和密码。
  • 客户端模式(Client Credentials Grant):适用于服务器间认证,客户端代表自己而不是资源拥有者请求访问令牌。

令牌类型

  • 访问令牌(Access Token):授权服务器发放的令牌,用于访问受保护的资源。
  • 刷新令牌(Refresh Token):用于获取新的访问令牌,延长用户会话的生命周期

实现步骤

  1. 配置OAuth 2.0客户端:在客户端应用程序中配置授权服务器的URI、客户端ID和客户端密钥。
  2. 用户认证:用户访问客户端时,如果未认证,则重定向到授权服务器的登录页面。
  3. 用户授权:用户登录后,授权服务器展示授权页面,用户同意授权后发放授权码。
  4. 获取访问令牌:客户端使用授权码向授权服务器请求访问令牌。
  5. 访问资源:客户端使用访问令牌请求资源服务器上的资源。
  6. 刷新令牌:在访问令牌过期后,使用刷新令牌获取新的访问令牌
单方面的免密单点登录

需求:在一个系统中,点击一个菜单或者按钮后可以免密登录到第三方系统中。
实现方式:获取对面系统的访问token、拼接到对面系统要展示的页面的请求URL中,然后重定向访问这个URL即可(这里获取token的请求URL和要展示的页面请求URL都需要对面系统提供)

参考案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public void jwuser(HttpServletRequest request, HttpServletResponse response) throws IOException {
String orgId = request.getParameter("orgid"); //组织id
String staffId = request.getParameter("userid"); //用户id
String isNewTab = request.getParameter("isnewtab"); //是否以新选项卡的方式弹出

// 判断jwInfo中是否存在request中的组织ID,也就是当前登录的用户是否是建武组织的
if(jwInfo.getJzbYzOrgIds().contains(orgId)||jwInfo.getJzbJlOrgIds().contains(orgId)||jwInfo.getJzbSgOrgIds().contains(orgId)){
// 根据用户ID获取建武用户,查看我方的建武用户数据中是否有该用户
JwUser jwUser = jwUserMapper.getByStaffId(staffId);
if(jwUser != null) {
// 创建一个RestTemplate对象,用来发起http请求
RestTemplate restTemplate =new RestTemplate();
// 创建一个RequestEntity对象,指定了请求的URL、设置请求体的格式为JSON、并设置了请求体
RequestEntity<String> tokenRequest = RequestEntity.post(URI.create(jwInfo.getTokenUrl()))
.contentType(MediaType.APPLICATION_JSON)
.body("{\"userId\":\""+jwInfo.getTokenAppId()+"\",\"password\":\""+jwInfo.getTokenAppKey()+"\"}");
// 发起请求并接收响应,包含了HTTP响应的状态码、响应头和响应体(已被转换为字符串)
ResponseEntity<String> exchange = restTemplate.exchange(tokenRequest, String.class);
// 解析出响应体
JSONObject jsonA = JSONObject.parseObject(exchange.getBody());
// 响应成功
if (jsonA.get("code").toString().equals("200")) {
// 获取响应体中的数据
JSONObject jsonB = JSONObject.parseObject(jsonA.get("data").toString());
// 如果 jsonB 包含数据,则使用jwInfo.getIndexUrl()作为基础URL并附加从jsonB中获取的token字段的值来构造一个新的URL tmpUrl
if (!jsonB.isEmpty()) {
String tmpUrl = jwInfo.getIndexUrl() + jsonB.get("token").toString();
if(isNewTab!=null && isNewTab.equals("1")){
// 如果变量 isNewTab 不为 null 并且等于字符串 "1",则打开一个新的浏览器标签页并导航到 tmpUrl
response.getWriter().print("<script>window.open('" + tmpUrl + "','_blank')</script>" );
} else {
// 否则重定向到tmpUrl
response.sendRedirect(tmpUrl);
}
}
}
}
else { // 建武用户不存在
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("您的账号无法访问!");
}
} else{ // jwInfo中的组织信息与传递过来的组织信息不匹配
response.setContentType("text/html;charset=utf-8");
response.getWriter().println("orgId为空或您当前orgId无权限访问!");
}
}

先获取请求中的当前登录人员的组织id、用户id、以及是否以新选项卡的方式弹出

判定登录用户的组织是否和jwInfo中的组织相同(当前登录用户是否属于建武这个组织)

—属于建武这个组织

​ 根据用户id查询tb_jwuser数据库表中是否有这个用户

​ —有这个用户

​ 向认证服务发起认证请求,并接收响应解析出响应体

​ —响应成功,状态码为200

​ —如果响应体中包含数据

​ 使用jwInfo.getIndexUrl()作为基础URL并附加从响应体中获取的token字段的值来构造一个新的URL tmpUrl

​ —如果变量 isNewTab 不为 null 并且等于字符串 “1”,则打开一个新的浏览器标签页并导航到 tmpUrl

​ —否则直接重定向到tmpUrl

​ —建武用户不存在:您的账号无法访问!

—jwInfo中的组织信息与传递过来的组织信息不匹配:orgId为空或您当前orgId无权限访问!



单点登录
https://lzhengjy.github.io/2024/08/07/单点登录/
作者
Zheng
发布于
2024年8月7日
许可协议