自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

實(shí)戰(zhàn):?jiǎn)吸c(diǎn)登錄的兩種實(shí)現(xiàn)方式,附源碼

安全 應(yīng)用安全
單點(diǎn)登錄(Single Sign-On,SSO)是一種身份驗(yàn)證服務(wù),允許用戶(hù)使用單個(gè)標(biāo)識(shí)來(lái)登錄多個(gè)應(yīng)用程序或系統(tǒng)。如下圖所示,用戶(hù)只需要用戶(hù)名/密碼登陸一次就可以訪問(wèn)系統(tǒng)A、系統(tǒng)B和系統(tǒng)C。

最近工作有點(diǎn)忙,好久沒(méi)更新文章了,正好這兩天在整理單點(diǎn)登陸相關(guān)的文檔,今天趁著小孩睡著了??,趕緊碼一篇實(shí)戰(zhàn)文交差。

概念

單點(diǎn)登錄(Single Sign-On,SSO)是一種身份驗(yàn)證服務(wù),允許用戶(hù)使用單個(gè)標(biāo)識(shí)來(lái)登錄多個(gè)應(yīng)用程序或系統(tǒng)。如下圖所示,用戶(hù)只需要用戶(hù)名/密碼登陸一次就可以訪問(wèn)系統(tǒng)A、系統(tǒng)B和系統(tǒng)C。

圖片

在傳統(tǒng)的登錄方式中,用戶(hù)必須為每個(gè)應(yīng)用程序或系統(tǒng)提供不同的憑據(jù)和密碼。如下圖所示,用戶(hù)訪問(wèn)系統(tǒng)A、系統(tǒng)B和系統(tǒng)C都必須用用戶(hù)名/密碼登陸。

圖片

這種方式既不方便也容易被攻擊者利用,而 SSO 解決了這個(gè)問(wèn)題,使得用戶(hù)只需通過(guò)一次身份驗(yàn)證就可以無(wú)縫地訪問(wèn)多個(gè)應(yīng)用程序或系統(tǒng),從而提高了用戶(hù)體驗(yàn)的便利性和安全性。

單點(diǎn)登陸的優(yōu)點(diǎn)

  • 用戶(hù)體驗(yàn)改善:用戶(hù)只需要登錄一次,就可以訪問(wèn)多個(gè)系統(tǒng)或應(yīng)用程序,不需要重復(fù)輸入用戶(hù)名和密碼。這可以大大提高用戶(hù)的工作效率。
  • 安全性增強(qiáng):?jiǎn)吸c(diǎn)登陸可以提供更高級(jí)別的安全性,因?yàn)橛脩?hù)只需要在一個(gè)系統(tǒng)中進(jìn)行身份驗(yàn)證,其他系統(tǒng)就可以共享這個(gè)身份驗(yàn)證信息。這可以有效地防止黑客入侵多個(gè)系統(tǒng)。
  • 管理更方便:?jiǎn)吸c(diǎn)登陸可以簡(jiǎn)化管理員的工作,因?yàn)樗梢约泄芾碛脩?hù)和權(quán)限。管理員可以在一個(gè)系統(tǒng)中管理多個(gè)系統(tǒng)的用戶(hù)和權(quán)限,這樣可以更方便地進(jìn)行管理和維護(hù)。

單點(diǎn)登陸的實(shí)現(xiàn)方式

  • 共享身份驗(yàn)證:多個(gè)系統(tǒng)共享一個(gè)身份驗(yàn)證系統(tǒng),用戶(hù)只需要在一個(gè)系統(tǒng)中進(jìn)行身份驗(yàn)證,就可以訪問(wèn)所有系統(tǒng)。這種方式需要建立一個(gè)共享的身份驗(yàn)證系統(tǒng),這樣可以保證用戶(hù)信息的安全性。
  • 代理身份驗(yàn)證:一個(gè)系統(tǒng)代表其他系統(tǒng)進(jìn)行身份驗(yàn)證,用戶(hù)在登錄時(shí)輸入用戶(hù)名和密碼,然后其他系統(tǒng)會(huì)代表用戶(hù)進(jìn)行身份驗(yàn)證。這種方式需要建立一個(gè)代理系統(tǒng),這樣可以保證用戶(hù)信息的安全性。
  • 基于令牌的身份驗(yàn)證:用戶(hù)在登錄后,會(huì)獲得一個(gè)令牌,這個(gè)令牌可以在多個(gè)系統(tǒng)上進(jìn)行身份驗(yàn)證。這種方式需要建立一個(gè)令牌管理機(jī)制,這樣可以保證用戶(hù)信息的安全性。

實(shí)戰(zhàn)一

架構(gòu)圖

圖片

  1. 用戶(hù)輸入用戶(hù)名/密碼登陸 ServiceA 系統(tǒng);
  2. 用戶(hù)點(diǎn)擊 ServiceA 系統(tǒng)中的某個(gè)按鈕跳轉(zhuǎn)到 ServiceB 系統(tǒng),在跳轉(zhuǎn)時(shí)需要帶上 ServiceA 系統(tǒng)頒發(fā)的 ticket 票據(jù);
  3. ServiceB 系統(tǒng)拿 ServiceA 系統(tǒng)的 ticket 去獲取 ServiceA 系統(tǒng)的用戶(hù)信息;
  4. ServiceA 系統(tǒng)會(huì)校驗(yàn)該 ticket 票據(jù),然后將用戶(hù)信息返回給 ServiceB 系統(tǒng);
  5. ServiceB 系統(tǒng)根據(jù)用戶(hù)信息生成 token 并附帶重定向地址返回給 ServiceA 系統(tǒng);
  6. ServiceA 系統(tǒng)就可以拿著獲取的 token 去訪問(wèn) ServiceB 系統(tǒng)的資源信息了。

代碼實(shí)現(xiàn)

數(shù)據(jù)庫(kù)

首先是初始化數(shù)據(jù)庫(kù),用戶(hù)、公司等表依據(jù)自己的具體業(yè)務(wù)而定,此處不再贅述。提供公共的單點(diǎn)登陸信息表。

CREATE TABLE `sso_client_detail` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `platform_name` varchar(64) DEFAULT NULL COMMENT '應(yīng)用名稱(chēng)',
  `platform_id` varchar(64) NOT NULL COMMENT '應(yīng)用標(biāo)識(shí)',
  `platform_secret` varchar(64) NOT NULL COMMENT '應(yīng)用秘鑰',
  `encrypt_type` varchar(32) NOT NULL DEFAULT 'RSA' COMMENT '加密方式:AES或者RSA',
  `public_key` varchar(1024) DEFAULT NULL COMMENT 'RSA加密的應(yīng)用公鑰',
  `sso_url` varchar(128) DEFAULT NULL COMMENT '單點(diǎn)登錄地址',
  `remark` varchar(1024) DEFAULT NULL COMMENT '備注',
  `create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
  `create_by` varchar(64) DEFAULT NULL COMMENT '創(chuàng)建人',
  `update_date` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時(shí)間',
  `update_by` varchar(64) DEFAULT NULL COMMENT '更新人',
  `del_flag` bit(1) NOT NULL DEFAULT b'0' COMMENT '刪除標(biāo)志,0:正常;1:已刪除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='單點(diǎn)登陸信息表'

插入測(cè)試數(shù)據(jù)

INSERT INTO cheetah.sso_client_detail
(id, platform_name, platform_id, platform_secret, encrypt_type, public_key, sso_url, remark, create_date, create_by, update_date, update_by, del_flag)
VALUES(1, 'serviceA', 'A9mQUjun', 'Y6at4LexY5tguevJcKuaIioZ1vS3SDaULwOtXW63buBK4w2e1UEgrKmscjEq', 'RSA', NULL, 'http://127.0.0.1:8081/sso/url', NULL, '2023-05-23 16:55:26', 'system', '2023-05-30 13:16:16', NULL, 0);
  • platform_id和platform_secret,阿Q是使用 apache 的 commons-lang3 包下的RandomStringUtils.randomAlphanumeric()方法生成的。
  • sso_url就是上邊提到的 ServiceB 系統(tǒng)的地址。
  • encrypt_type、public_key在此方式中未使用,可以忽略。

細(xì)心的阿Q還給大家準(zhǔn)備了一個(gè)接口,只需要傳入 platformName 和 ssoUrl 就可以自動(dòng)生成單點(diǎn)登陸信息。

圖片

接下來(lái)我們就進(jìn)入真正的代碼部分了,回復(fù)“sso”即可獲取實(shí)戰(zhàn)源碼。

A跳轉(zhuǎn)B

/**
 * com.itaq.cheetah.serviceA.controller.PortalController#jump
 * title:跳轉(zhuǎn) ServiceB
 * <pre>
 * 1. 前端點(diǎn)擊Jump鏈接觸發(fā)此接口調(diào)用
 * 2. 此接口生成ticket并攜帶著請(qǐng)求 ServiceB
 *      2.1 ServiceB拿著ticket請(qǐng)求我方服務(wù)獲取用戶(hù)信息
 *      2.2 ServiceB獲取到我方用戶(hù)信息并進(jìn)行數(shù)據(jù)同步
 *      2.3 ServiceB返回一個(gè)鏈接,連接中帶 token
 * 3. 重定向到返回的鏈接實(shí)現(xiàn)登錄
 * </pre>
 *
 * @param req 單點(diǎn)跳轉(zhuǎn)請(qǐng)求體
 * @return ServiceB單點(diǎn)登錄地址
 */
@PostMapping("/jumpB")
public WrapperResult<String> jump(@RequestBody @Validated SsoJumpReq req) {
 log.debug("單點(diǎn)登錄:{}", req.getPlatformName());
 //1、判斷該平臺(tái)名稱(chēng)是否存在
 SsoClientDetail one = iSsoClientDetailService.getOne(
   new LambdaQueryWrapper<SsoClientDetail>().eq(SsoClientDetail::getPlatformName, req.getPlatformName())
 );
 if (Objects.isNull(one)) {
  return WrapperResult.faild("不存在的app");
 }
 //2、校驗(yàn)本系統(tǒng)的 token,并從中獲取用戶(hù)信息
 /*
 * 示例
 * Result<Token> result = authorizationApi.checkToken(req.getToken());
 */

 //3、生成ticket,并將用戶(hù)信息與其綁定存入redis
 String ticket = UUID.randomUUID().toString().replaceAll("-", "");
 UserInfo userInfo = new UserInfo();
 userInfo.setId(1L);
 userInfo.setUsername("阿Q");
 redisTemplate.opsForValue().set(RedisConstants.TICKET_PREFIX + ticket, userInfo, 5, TimeUnit.MINUTES);

 String ssoUrl = one.getSsoUrl();
 Map<String, Object> data = new HashMap<>(1);
 data.put("ticket", ticket);
 //4、發(fā)送http請(qǐng)求,把ticket通過(guò)設(shè)置好的ssoUrl傳給ServiceB
 WrapperResult<SsoRespDto> ssoRespDto = HttpRequest
   .get(ssoUrl)
   .queryMap(data)
   .connectTimeout(Duration.ofSeconds(120))
   .readTimeout(Duration.ofSeconds(120))
   .execute()
   .asValue(new TypeReference<WrapperResult<SsoRespDto>>() {
   });
 log.info("請(qǐng)求ServiceB 結(jié)果:{}", JsonUtils.toPrettyString(ssoRespDto));
 return WrapperResult.success(ssoRespDto.getData().getRedirectUrl());
}

B獲取票據(jù),并請(qǐng)求A獲取用戶(hù)信息

/**
 * com.itaq.cheetah.serviceB.controller.SsoController#sso
 * 獲取票據(jù),并請(qǐng)求ServiceA 獲取用戶(hù)信息
 * @param ticket 票據(jù)
 * @return 返回地址供sso跳轉(zhuǎn)
 * @throws JsonProcessingException 異常
 */
@GetMapping("/url")
public WrapperResult<SsoRespDto> sso(@RequestParam("ticket") String ticket) throws JsonProcessingException {
 log.info("收到票據(jù):{}", ticket);
 //1.根據(jù)ticket換取ServiceA用戶(hù)信息
 Map<String, Object> param = new HashMap<>(1);
 param.put("ticket", ticket);
 String ssoUrl = "http://localhost:8081/getUser";
 String s = HttpRequest
   .get(ssoUrl)
   .queryMap(param)
   .connectTimeout(Duration.ofSeconds(120))
   .readTimeout(Duration.ofSeconds(120))
   .execute()
   .asString();
 ObjectMapper objectMapper = new ObjectMapper();
 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
 WrapperResult<SsoUserInfo> ssoUserInfoWrapperResult = objectMapper.readValue(s, new TypeReference<WrapperResult<SsoUserInfo>>() {
 });
 log.info("ticket登錄結(jié)果:{}", new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(ssoUserInfoWrapperResult));
 //2.獲取到用戶(hù)信息之后同步到本地?cái)?shù)據(jù)庫(kù)
 log.info("獲取用戶(hù)信息同步數(shù)據(jù)庫(kù)");
 //3.生成token
 log.info("生成token");
 SsoRespDto respDto = new SsoRespDto();
 //4、將ServiceA要跳轉(zhuǎn)的地址返給ServiceA并攜帶 ServiceB 的token
 respDto.setRedirectUrl("http://localhost:8082/index?token=123456");
 WrapperResult<SsoRespDto> success = WrapperResult.success(respDto);
 log.info(new ObjectMapper().writeValueAsString(success));
 return success;
}

A提供的獲取用戶(hù)信息接口

/**
 * com.itaq.cheetah.serviceA.controller.PortalController#loginByTicket
 * 根據(jù)票據(jù)獲取用戶(hù)信息
 * @param ticket 票據(jù)信息
 * @return 用戶(hù)信息
 */
@ApiOperation("根據(jù)ticket獲取用戶(hù)信息")
@GetMapping("/getUser")
public WrapperResult<SsoUserInfo> loginByTicket(@RequestParam("ticket") String ticket) {
 log.info("收到票據(jù):{}", ticket);
 UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(RedisConstants.TICKET_PREFIX + ticket);
 if (Objects.isNull(userInfo)) {
  return WrapperResult.faild("無(wú)法識(shí)別的票據(jù)信息");
 }
 //可能 userInfo 中只有少量的用戶(hù)信息,此處省略了根據(jù)用戶(hù)id查詢(xún)用戶(hù)和企業(yè)信息的過(guò)程,自行編寫(xiě)邏輯代碼即可

 SsoUserInfo ssoUserInfo = new SsoUserInfo();
 BeanUtil.copyProperties(userInfo,ssoUserInfo);
 return WrapperResult.success(ssoUserInfo);
}

測(cè)試結(jié)果

圖片

實(shí)戰(zhàn)二

架構(gòu)圖

圖片

這次我們用 ServiceB 系統(tǒng)單點(diǎn)登陸 ServiceA 方式:

  • 用戶(hù)輸入用戶(hù)名/密碼登陸 ServiceB 系統(tǒng);
  • 用戶(hù)點(diǎn)擊 ServiceB 系統(tǒng)中的某個(gè)按鈕跳轉(zhuǎn)到 ServiceA 系統(tǒng),在跳轉(zhuǎn)時(shí)需要帶上 ServiceB 系統(tǒng)加密后的用戶(hù)信息;
  • ServiceA 系統(tǒng)拿到 ServiceB 系統(tǒng)加密后的用戶(hù)信息后進(jìn)行驗(yàn)簽和解密操作;
  • ServiceA 系統(tǒng)將用戶(hù)信息保存到本地并生成 token 返回給 ServiceB 系統(tǒng);
  • ServiceB 系統(tǒng)拿到 ServiceA 系統(tǒng)返回的 token 就可以訪問(wèn) ServiceA 系統(tǒng)的資源信息了;

代碼實(shí)現(xiàn)

此種方式就用到了上邊提到的數(shù)據(jù)庫(kù)中的encrypt_type、public_key字段,其中 public_key 是 ServiceA 給 ServiceB 提供的。為了演示方便直接在application.yml中進(jìn)行配置。

B的配置

#本服務(wù)的appId和appSecret信息,該配置由serviceA提供
appId: A9mQUjun
appSecret: Y6at4LexY5tguevJcKuaIioZ1vS3SDaULwOtXW63buBK4w2e1UEgrKmscjEq

encrypt:
  #加密方式 RSA | AES
  type: RSA
  #該配置是serviceA單點(diǎn)登陸serviceB用到的,此處是serviceB單點(diǎn)serviceA,所以用不到
  #如果選擇非對(duì)稱(chēng)加密,需要使用該配置;本服務(wù)的公私鑰信息,該配置由serviceB自己生成,并將publicKey給serviceA
  rsa:
    publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0KLYE2Tv4qx/duxu8Qvq5ZN58yEjj/uwsxfs96pj+9iOOAUKLur8IIKjR/bi54GICUy0BHO6dzpWc0xqGK170F9NTv0bHe0qbh7jHgzq9MJrfcVD+XZAH17ho5tCGIo+z7CiC+rMWGTqmRopd/EQuzfx4Op4/85hoPlpKxdcxAfys0jpZ9tBMtROPsYKhCz01iDnHV2K95s4UwaQLbbx0VALVaXv1/4Yjw/PW4xK0syW/nqUtVqpfwPuX+fHf+bJ2s4kLnFBNwYAKFSU6znGmtJuq6aoxCunu2PbzI8xc7SYxHEfDqG8Zp29wtZcTJecWSDMBmywlaXjkXLzapvE7QIDAQAB
    privateKey: MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQotgTZO/irH927G7xC+rlk3nzISOP+7CzF+z3qmP72I44BQou6vwggqNH9uLngYgJTLQEc7p3OlZzTGoYrXvQX01O/Rsd7SpuHuMeDOr0wmt9xUP5dkAfXuGjm0IYij7PsKIL6sxYZOqZGil38RC7N/Hg6nj/zmGg+WkrF1zEB/KzSOln20Ey1E4+xgqELPTWIOcdXYr3mzhTBpAttvHRUAtVpe/X/hiPD89bjErSzJb+epS1Wql/A+5f58d/5snaziQucUE3BgAoVJTrOcaa0m6rpqjEK6e7Y9vMjzFztJjEcR8Oobxmnb3C1lxMl5xZIMwGbLCVpeORcvNqm8TtAgMBAAECggEBAKMhoQfRFYxMSkIHbluFcP5eyKylDbRoHOp726pvDUx/L/x3XFYBIHCfFOKRFSvk6SQ0WFFe176f27a9Wfu/sh7kVYNcflZw+YsvFXCKsy/70KZ/lr24izy8KHuPSyf6+E/WkW32Ah9fkNtzTFdfIzDv9m1hiIijq0x9l5C87KjNELnbvC0I6vwFOx0ak+JBbpaJ7IRjZxKZup7UIPvt9nbLzcbKelI83An2JUe8HNhrfWxH9UIyMOBoAY+bKCuAbUtHqSlImPiWyiCwE2/Fh7dmPSOAYYp9aZelnhd25jlR+eh4yaUoIID9ubmYVYbjcPW5SSNdfSZMfQ3oa79QeRUCgYEA6K4L+VLRiX8Dg7NCO1fM2+FTv2csTkPX6n7z/uu7kh0+wQDws+/C6Q906OtizvJBIJqFm2jPACNQCvnRixY1srgMJJlH/Rpeb4LtZGwdM1k0jAZIYQcBlGfaq3RaRI/+6+T0xdsh+7VF5A/smp/VXdK2xI3+JbLQ2wm9uN+3yZcCgYEA5Yvly7veDJYf2+8HIQkRhjWrWm1y5lCSe+HG+1ktfqnhN8YEOiPa71u0TXealL0T8EoKsqhWEjomxZ7n0jLigogz7OxxsGAE6HXAiKX0REINNYrq+1qNaqmkfLrhAJyg3JNgTSlb0xd56w7FSqOBttVL9INawGb1P98kYc5OzhsCgYBEfIY1urTGPcZxC2BhSzSXO7mEyv91ge6ZrQhwbj5lgYopEPfIXrgGFXCZ5j7NHu0ghZrx5WWYasxyjpmo0L65fgbE9wEDdLF7LRRmzJPDu2wGEwtW09MZNYBdmv++0ot8L4YEfr1/8xlBSZag5I7O8Oiu7gRyYDGtZy6are7QvQKBgQCaUZnUhOF7/rU+a4yUZf9VBeHD8k7LjaFdDWVzdvmB7P1PPJ185Lv8LN+jMORIWHD+GxjkEQ2ERXnpY7If+zuSW7Tk8/Reib7i9L7SXxc/iFRPCax9/NuTuKavgAdiHOp8P8v/M+3alS7OmuiCDDhZTT46DNDHBrCcFwzjgAo0vwKBgECBs6hEUVsYU74Uc64he8Zgkvj7wZ/yxnFlWmRRERprfBsuiY/y+DAf5ehezSRFpHXUrAkpeVXq2ydnr9BKTs6TV3AxlDMBNSndXsUYHENncR7tEHCSGRFTTu5jxdYA+k47R865Jh+2vQvPaPaXsEKSkDegvcFeUVR/yi5AsDub

#如果選擇非對(duì)稱(chēng)加密,則需要使用該配置,該配置由serviceA提供
serviceA:
  rsa:
    publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2BF9EZCscKNXYADtulIDNHaMnoxV5Yu91jpv+LiWabW2EO51b8Sx8+Ei59EebM4r+SMal0k4L2Z+cNagQSP4Wvpss82/MkGO8bnAFSxS2SOKw+a+c2PxByWUxvHo4pbyYGFVWAGDXLiI+IqiO/fEFfpy6rYQzMLDnfgMFngdS4AZmRyTdMKbQs8mWqBE5nC0PoU39o/lFowfgelEjHE9vhjtTha67KhYY3n+ueuxsYdRQ40Mg7aQ0+Kt/qKoSn9yRWyx09DheFAkYl4ZCQfd0sMotLQ4BZtk0YWMNHOc1w+fL1bOumaj7AaJi6nM/VvwylLJyia2GjJIDrdTfHiOnwIDAQAB

A的配置

serviceA:
  rsa:
    privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYEX0RkKxwo1dgAO26UgM0doyejFXli73WOm/4uJZptbYQ7nVvxLHz4SLn0R5sziv5IxqXSTgvZn5w1qBBI/ha+myzzb8yQY7xucAVLFLZI4rD5r5zY/EHJZTG8ejilvJgYVVYAYNcuIj4iqI798QV+nLqthDMwsOd+AwWeB1LgBmZHJN0wptCzyZaoETmcLQ+hTf2j+UWjB+B6USMcT2+GO1OFrrsqFhjef6567Gxh1FDjQyDtpDT4q3+oqhKf3JFbLHT0OF4UCRiXhkJB93Swyi0tDgFm2TRhYw0c5zXD58vVs66ZqPsBomLqcz9W/DKUsnKJrYaMkgOt1N8eI6fAgMBAAECggEAA5f23o3rcEwnLd+WFJ08lGjMWe63lwPF+oQqTJa1Wbi9+HYe2ecJlqbN79EYknKzZIdi79U17APmYnYPYEX64Xh8yljHr0xL1lVijneYQShILI3v6PdmkNndKZnoZ6xfB59WzgnoZ2hiTs/vdtPeHQd3VdQFX4J1wnDXsp/4zMKi1fDPt7rhqWrP5W6PXcoGGKIkN9zBlqrd1RBdnKXcwfFoHcFf2ikk6g3Kn50YMRe324eiHMm8z7W34Y3iSvZYHcKBMgsDklFerw1WOGHTN61oMr+8/NTtCsy1AnCH4PrwX/ryO17mh5xNzo/ZSZRRezR92/hmwUIuOO+3FWIE4QKBgQD05wYMVlGKn1fm+sn4hn+ErC6NifXj3MkNdjs8oSHzLrYr6ea6xIvbxesZvqzqz1Fh68bHjpJPOBKwgFnl7+dLXYLNmKjry1iK0o/MMZTtrGUwMEnWHRrpmxXH6B0cnBecZUReuJ9XfKZIfd9ksHHsUY7IGv1CHcblVP/IhrpnxwKBgQDh2/n0cAh1jygGevlXGK/rxuRSlbVgtxJWLAtY8Yolf2BklSiTwmqtp7nzNn8sxRvgfQCZaLqpjC/o/wtC3Ba5b4StJQejoXkCNhVmRdLbIQ2tUxwAElPjFhWf3C5/4B6uBeLyC9izp4wTSYbNbPKxcUGkkfpPbWdHsFZOG4gSaQKBgA/me/cLF6o3ZD6j478WBGt5vmAEKAnOSONt3LS4BXtDeiJpwkg4AJiZRgVa4uEv6qm/5B0KvacVDemVu8B5DfxPqvFsSvNcNXh16U4pnfC8c6loSTL0ms21+vkKsfEslT/bN1ArDnVgq28jdQCVkB/2v51wWycSxdoX5a+AR9P7AoGAMvTwZefI4M0VmLCyBKZ7OlS7Oq6wJ0vmhS6WuNB1/JPKaacFaqDYdKl82JSZCL7H1VQeiH4KbypDvOud3M3PCrNQWcga+x35MTiGh3aFZg8FCO/RR2rbJkbbRh/lFdC420ZUt4tYrt/ESK20DjDgaIxG5RxSPw1N2ey87A5mGtECgYEAlA12yuxBb6qmG3OUSlacSfcKnxZIC3L1IMqxlXL8eG3MB4dI6QYesc3odmaxmy9csgHs+pTyLfM3yB9Ocl572OW5WcEnod5o1EIup9hxB4IG/xSECYVFHlGKfIgbd/JhWtqloYZrwx+kVX/Iw02z18R32DRqBtK4MQ3klOYH86s=

B跳轉(zhuǎn)A并加密用戶(hù)信息

/**
 * com.itaq.cheetah.serviceB.controller.ToServiceAController#redirectToServiceA
 * 跳轉(zhuǎn) ServiceA 服務(wù)
 *
 * @return ServiceA返回的重定向鏈接
 */
@GetMapping
public WrapperResult<String> redirectToServiceA() {
 //1、構(gòu)建用戶(hù)信息
 SsoUserInfo data = buildSsoUserInfo();
    
 Long timestamp = System.currentTimeMillis();
 String flowId = UUID.randomUUID().toString();
 String businessId = "sso";
 String dataEncrypt;
 String encryptType = configProperties.getEncryptType();
 //2、根據(jù)配置選擇哪種方式加密
 switch (encryptType) {
  case "AES":
   AES aes = new AES(configProperties.getAppSecret().getBytes(StandardCharsets.UTF_8));
   dataEncrypt = aes.encryptBase64(JsonUtils.toString(data), StandardCharsets.UTF_8);
   break;
  case "RSA":
   RSA rsa = new RSA(AsymmetricAlgorithm.RSA_ECB_PKCS1.getValue(), null, configProperties.getServiceAPublicKey());
   dataEncrypt = rsa.encryptBase64(JsonUtils.toString(data), StandardCharsets.UTF_8, KeyType.PublicKey);
   break;
  default:
   return WrapperResult.faild("未配置加密方式");
 }
 //3、將以下信息進(jìn)行簽名
 SsoSignSource build = SsoSignSource.builder()
   .platformId(configProperties.getAppId())
   .platformSecret(configProperties.getAppSecret())
   .businessId(businessId)
   .data(dataEncrypt)
   .flowId(flowId)
   .timestamp(timestamp)
   .build();
 String sign = build.sign();
 log.info("sign source={}", JsonUtils.toPrettyString(build));

 //4、構(gòu)建請(qǐng)求體
 ToServiceAReq req = ToServiceAReq.builder()
   .platformId(configProperties.getAppId())
   .businessId("sso")
   .flowId(flowId)
   .timestamp(timestamp)
   .sign(sign)
   .data(dataEncrypt)
   .build();

    //5、跳轉(zhuǎn)A的操作
 String s = HttpRequest.post("http://localhost:8081/serviceA")
   .bodyString(JsonUtils.toString(req))
   .execute()
   .asString();
 log.info("結(jié)果:{}", s);
 return WrapperResult.success(s);
}

A獲取用戶(hù)信息后續(xù)操作

/**
 * com.itaq.cheetah.serviceA.controller.ServiceAController#sso
 *
 * @return
 */
@PostMapping
public WrapperResult<SsoRespDto> sso(@VerifySign ToServiceAReq req) {
 log.info("收到單點(diǎn)登錄ServiceA的請(qǐng)求:{}", JsonUtils.toPrettyString(req));
 //同步用戶(hù)信息

 //模擬登陸生成token

 //返回拼接的url?token=xxx
 //返回拼接的url?token=xxx
    String url ="127.0.0.1:8081/index?token=xxx";
    SsoRespDto ssoRespDto = new SsoRespDto();
    ssoRespDto.setRedirectUrl(url);
    return WrapperResult.success(ssoRespDto);
}

你可能會(huì)好奇,驗(yàn)簽解密的邏輯去哪了?

此處我們通過(guò)注解的方式實(shí)現(xiàn)自動(dòng)驗(yàn)簽和解密的邏輯,至于具體的邏輯,大家可以回復(fù)“sso”獲取源碼自行解讀,當(dāng)然后續(xù)阿Q還會(huì)推出新的文章進(jìn)行詳細(xì)的講解,點(diǎn)擊關(guān)注【阿Q說(shuō)代碼】進(jìn)行預(yù)約吧!

測(cè)試

圖片

補(bǔ)充知識(shí)

本文中用到的 RSA 的密鑰是通過(guò)在線網(wǎng)站https://www.bchrt.com/tools/rsa/生成的,當(dāng)然大家也可以使用 hutool 中的 RSA 類(lèi)來(lái)生成,也可以使用 java 自帶的 security 來(lái)生成。

責(zé)任編輯:武曉燕 來(lái)源: 阿Q說(shuō)代碼
相關(guān)推薦

2021-06-24 08:52:19

單點(diǎn)登錄代碼前端

2010-07-14 10:30:26

Perl多線程

2021-12-08 10:47:35

RabbitMQ 實(shí)現(xiàn)延遲

2024-01-29 12:48:00

Jenkins監(jiān)控運(yùn)維

2009-06-15 15:02:48

Spring定時(shí)器

2022-06-08 15:12:34

前端前端截圖

2011-03-03 10:26:04

Pureftpd

2021-05-27 10:57:01

TCP定時(shí)器網(wǎng)絡(luò)協(xié)議

2020-05-11 13:03:03

SR-TEIP路由器

2010-07-13 14:54:15

Perl面向?qū)ο缶幊?/a>

2009-06-25 13:43:00

Buffalo AJA

2010-10-21 16:24:18

sql server升

2010-08-06 09:38:11

Flex讀取XML

2023-03-29 13:06:36

2010-09-28 15:12:27

Javascript

2015-10-09 09:51:29

Web API認(rèn)證

2010-09-07 11:09:59

2024-03-29 11:33:23

轉(zhuǎn)換[]bytestring

2021-06-30 07:19:34

SpringBoot定時(shí)任務(wù)

2010-07-27 15:03:37

Flex ArrayC
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)