2024年最全实战:单点登录的两种实现方式,附源码_前端单点登录,蚂蚁金服内推四面

最后

如果你已经下定决心要转行做编程行业,在最开始的时候就要对自己的学习有一个基本的规划,还要对这个行业的技术需求有一个基本的了解。有一个已就业为目的的学习目标,然后为之努力,坚持到底。如果你有幸看到这篇文章,希望对你有所帮助,祝你转行成功。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

		.queryMap(data)
		.connectTimeout(Duration.ofSeconds(120))
		.readTimeout(Duration.ofSeconds(120))
		.execute()
		.asValue(new TypeReference<WrapperResult<SsoRespDto>>() {
		});
log.info("请求ServiceB 结果:{}", JsonUtils.toPrettyString(ssoRespDto));
return WrapperResult.success(ssoRespDto.getData().getRedirectUrl());

}


## B获取票据,并请求A获取用户信息



java复制代码/**

  • com.itaq.cheetah.serviceB.controller.SsoController#sso
  • 获取票据,并请求ServiceA 获取用户信息
  • @param ticket 票据
  • @return 返回地址供sso跳转
  • @throws JsonProcessingException 异常
    */
    @GetMapping(“/url”)
    public WrapperResult sso(@RequestParam(“ticket”) String ticket) throws JsonProcessingException {
    log.info(“收到票据:{}”, ticket);
    //1.根据ticket换取ServiceA用户信息
    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 ssoUserInfoWrapperResult = objectMapper.readValue(s, new TypeReference<WrapperResult>() {
    });
    log.info(“ticket登录结果:{}”, new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(ssoUserInfoWrapperResult));
    //2.获取到用户信息之后同步到本地数据库
    log.info(“获取用户信息同步数据库”);
    //3.生成token
    log.info(“生成token”);
    SsoRespDto respDto = new SsoRespDto();
    //4、将ServiceA要跳转的地址返给ServiceA并携带 ServiceB 的token
    respDto.setRedirectUrl(“http://localhost:8082/index?token=123456”);
    WrapperResult success = WrapperResult.success(respDto);
    log.info(new ObjectMapper().writeValueAsString(success));
    return success;
    }

## A提供的获取用户信息接口



java复制代码/**

  • com.itaq.cheetah.serviceA.controller.PortalController#loginByTicket

  • 根据票据获取用户信息

  • @param ticket 票据信息

  • @return 用户信息
    */
    @ApiOperation(“根据ticket获取用户信息”)
    @GetMapping(“/getUser”)
    public WrapperResult loginByTicket(@RequestParam(“ticket”) String ticket) {
    log.info(“收到票据:{}”, ticket);
    UserInfo userInfo = (UserInfo) redisTemplate.opsForValue().get(RedisConstants.TICKET_PREFIX + ticket);
    if (Objects.isNull(userInfo)) {
    return WrapperResult.faild(“无法识别的票据信息”);
    }
    //可能 userInfo 中只有少量的用户信息,此处省略了根据用户id查询用户和企业信息的过程,自行编写逻辑代码即可

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


测试结果


![](https://img-blog.csdnimg.cn/img_convert/311f9a41cc744e62ac79546dc02e4989.webp?x-oss-process=image/format,png)


### 实战二


#### 架构图


![](https://img-blog.csdnimg.cn/img_convert/8777cad86eb54c7bce1ec58820e9331e.webp?x-oss-process=image/format,png)


这次我们用 ServiceB 系统单点登陆 ServiceA 方式:


* 用户输入用户名/密码登陆 ServiceB 系统;
* 用户点击 ServiceB 系统中的某个按钮跳转到 ServiceA 系统,在跳转时需要带上 ServiceB 系统加密后的用户信息;
* ServiceA 系统拿到 ServiceB 系统加密后的用户信息后进行验签和解密操作;
* ServiceA 系统将用户信息保存到本地并生成 token 返回给 ServiceB 系统;
* ServiceB 系统拿到 ServiceA 系统返回的 token 就可以访问 ServiceA 系统的资源信息了;


## 代码实现


此种方式就用到了上边提到的数据库中的encrypt\_type、public\_key字段,其中 public\_key 是 ServiceA 给 ServiceB 提供的。为了演示方便直接在application.yml中进行配置。


## B的配置



yml复制代码#本服务的appId和appSecret信息,该配置由serviceA提供
appId: A9mQUjun
appSecret: Y6at4LexY5tguevJcKuaIioZ1vS3SDaULwOtXW63buBK4w2e1UEgrKmscjEq

encrypt:
#加密方式 RSA | AES
type: RSA
#该配置是serviceA单点登陆serviceB用到的,此处是serviceB单点serviceA,所以用不到
#如果选择非对称加密,需要使用该配置;本服务的公私钥信息,该配置由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

#如果选择非对称加密,则需要使用该配置,该配置由serviceA提供
serviceA:
rsa:
publicKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2BF9EZCscKNXYADtulIDNHaMnoxV5Yu91jpv+LiWabW2EO51b8Sx8+Ei59EebM4r+SMal0k4L2Z+cNagQSP4Wvpss82/MkGO8bnAFSxS2SOKw+a+c2PxByWUxvHo4pbyYGFVWAGDXLiI+IqiO/fEFfpy6rYQzMLDnfgMFngdS4AZmRyTdMKbQs8mWqBE5nC0PoU39o/lFowfgelEjHE9vhjtTha67KhYY3n+ueuxsYdRQ40Mg7aQ0+Kt/qKoSn9yRWyx09DheFAkYl4ZCQfd0sMotLQ4BZtk0YWMNHOc1w+fL1bOumaj7AaJi6nM/VvwylLJyia2GjJIDrdTfHiOnwIDAQAB


## A的配置



yml复制代码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跳转A并加密用户信息



java复制代码/**

  • com.itaq.cheetah.serviceB.controller.ToServiceAController#redirectToServiceA

  • 跳转 ServiceA 服务

  • @return ServiceA返回的重定向链接
    */
    @GetMapping
    public WrapperResult redirectToServiceA() {
    //1、构建用户信息
    SsoUserInfo data = buildSsoUserInfo();

    Long timestamp = System.currentTimeMillis();
    String flowId = UUID.randomUUID().toString();
    String businessId = “sso”;
    String dataEncrypt;
    String encryptType = configProperties.getEncryptType();
    //2、根据配置选择哪种方式加密
    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、将以下信息进行签名
    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、构建请求体
    ToServiceAReq req = ToServiceAReq.builder()
    .platformId(configProperties.getAppId())
    .businessId(“sso”)
    .flowId(flowId)
    .timestamp(timestamp)
    .sign(sign)
    .data(dataEncrypt)
    .build();

    //5、跳转A的操作
    String s = HttpRequest.post(“http://localhost:8081/serviceA”)
    .bodyString(JsonUtils.toString(req))
    .execute()
    .asString();
    log.info(“结果:{}”, s);
    return WrapperResult.success(s);
    }


## A获取用户信息后续操作



java复制代码/**

  • com.itaq.cheetah.serviceA.controller.ServiceAController#sso

  • @return
    */
    @PostMapping
    public WrapperResult sso(@VerifySign ToServiceAReq req) {
    log.info(“收到单点登录ServiceA的请求:{}”, JsonUtils.toPrettyString(req));
    //同步用户信息

    //模拟登陆生成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);
    }


你可能会好奇,验签解密的逻辑去哪了?


此处我们通过注解的方式实现自动验签和解密的逻辑


## 测试




### 文末
篇幅有限没有列举更多的前端面试题,小编把整理的前端大厂面试题PDF分享出来,一共有269页

![](https://img-blog.csdnimg.cn/img_convert/04273c2f50e58b6822663c5b2f379206.png)  

![](https://img-blog.csdnimg.cn/img_convert/081b5236ebd9ff24af676bcd4a51c99e.png)

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值