草庐IT

Spring Security整合企业微信的扫码登录,企微的API震惊到我了

码农小胖哥 2023-06-11 原文

本文代码: https://gitee.com/felord/spring-security-oauth2-tutorial/tree/wwopen/

现在很多企业都接入了企业微信,作为私域社群工具,企业微信开放了很多API,可以打通很多自有的应用。既然是应用,那肯定需要做登录。正好企业微信提供了企业微信扫码授权登录功能,而且号称使用了OAuth,正好拿这个检验一下Spring Security OAuth2专栏的威力。

正当我兴致勃勃打开文档学习的时候,脸上笑容逐渐消失,这确定是OAuth的吗?

参数都变了,跟OAuth(不管是1.0还是2.0)规定不一样,然而这还不是最离谱的。按正常OAuth2的要求,拿到code之后就可以换access_token了是吧?企业微信的access_token居然和上面扫码获取code这一步完全无关,甚至获取access_token才是第一步!

而且这个access_token接口,你还不能频繁调用,要缓存起来公用。

那费了半天劲儿去拿code有啥用呢?

居然这个code是拿用户信息的,不得不说,我服了!OAuth2的123流程被整成了213。这也就算了,命名上能不能走点心,一会儿下划线,一会儿驼峰:

{
   "errcode": 0,
   "errmsg": "ok",
   "OpenId":"OPENID",
   "DeviceId":"DEVICEID",
   "external_userid":"EXTERNAL_USERID"
}

这个JSON风格,果然是大厂,讲究!一个JSON要三个人来写才体面!反序列化的时候我还得给你写一个兼容,这是要拉满我的KPI是吧?算了,忍忍吧,老板就要这个功能,它就是一坨翔,做开发的也得含泪吃下去,干!

环境准备

准备一个内网穿透

开发微信相关的应用都需要搞一个内网穿透,在我往期的文章都有介绍。搞一个映射域名出来,就像下面这样:

http://invybj.natappfree.cc -> 127.0.0.1:8082 

invybj.natappfree.cc会映射到我本地的8082端口,也就是我本地要开发应用的端口。

创建应用

首先去企业微信管理后台创建一个应用,如图:

图里的参数AgentIdSecret要记下来备用。

还有一个企业微信的corpid,你可以从下面这个位置拿到,也要记下来备用。

配置内网穿透域名

创建应用这一页往下拉到页面底端,你会看到:

点击已启用进入下面这个页面:

这里配置你授权登录应用生产的正式域名或者上面内网穿透的域名,注意只配置域名,而且不能使用localhost

其实我感觉改写hosts文件也能用啊,你可以试一试。

到这里环境就搞定了,接下来就开始写Spring Security兼容代码吧。

Spring Security兼容企业微信扫码登录

写起来太恶心了,不过对比文档和OAuth2的流程之后其实也没那么麻烦。我先放出我调试好的配置:

spring:
  security:
    oauth2:
      client:
        registration:
          work-wechat-scan:
            # client-id为企业微信 的企业ID
            # 下面client-id是假的,你用你自己的企业ID
            client-id: wwaxxxxxx
            # client-secret企业微信对应应用的secret,
            # 每个企业微信应用都有独立的secret,不要搞错
            # 下面client-secret假的,你用你自己创建的企业微信应用secret
            client-secret:  nvzGI4Alp3zxxxxxxxKbnfTEets5W8
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
        provider:
          work-wechat-scan:
            authorization-uri: https://open.work.weixin.qq.com/wwopen/sso/qrConnect
            token-uri: https://qyapi.weixin.qq.com/cgi-bin/gettoken
            user-info-uri: https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo

这里client-id使用你企业微信的企业IDclient-secret使用上面创建应用的secret值。

这里的work-wechat-scan是客户端的registrationId

封装企业微信拉起二维码URL

我们期望的是保持Spring Security OAuth2的风格,当我访问:

http://invybj.natappfree.cc/oauth2/authorization/work-wechat-scan

会重定向到企业微信扫码登录链接,格式为:

https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE

这个和以前胖哥实现微信网页授权的原理差不多,都是通过改造OAuth2AuthorizationRequestResolver接口来实现,只需要实现一个Consumer<OAuth2AuthorizationRequest.Builder>就行了。

逻辑是:client_id替换为appid,增加一个agentid参数,连带redirect_uristate四个参数之外的其它OAuth2参数全干掉,拼接成上面的URL

这么写:

把这个Consumer配置到DefaultOAuth2AuthorizationRequestResolver就行了。

适配OAuth2获取access_token

经过这一步扫码拿到code就不成问题了,按照OAuth2该拿access_token了,需要自定义一个函数式接口:

Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>>

也就是利用OAuth2AuthorizationCodeGrantRequest生成RestTemplate需要的请求对象RequestEntity<?>。按照企业微信获取access_token的文档,这样自定义:

把这个配置到DefaultAuthorizationCodeTokenResponseClient就行了。

access_token的缓存,我放在了下一步进行解决。

适配获取用户信息

codeaccess_token都拿到了,最后一步获取用户的信息。这里是比较麻烦的因为获取access_token后并没有直接提供将code传递给OAuth2UserService的方法。最后发现OAuth2AccessTokenResponseadditionalParameters属性可以传递到OAuth2UserService,于是就利用代理模式改造了OAuth2AccessTokenResponseClient来实现:

自定义企业微信OAuth2UserService

这个和微信网页授权我封装的差不多,改下参数封装成URI交给RestTemplate请求企业微信API。恶心的是要反序列化兼容三个微信研发工程师写的一个JSON

@Data
public class WorkWechatOAuth2User implements OAuth2User {
    private Set<GrantedAuthority> authorities;
    private Integer errcode;
    private String errmsg;
    @JsonAlias("OpenId")
    private String openId;
    @JsonAlias("UserId")
    private String userId;
}

收尾

拿到用户信息后,就结束了,你实现一个AuthenticationSuccessHandler来保证登录凭证和你平台一致,无论是cookie还是JWT,最后把它配置到这里:

httpSecurity.oauth2Login()
    .successHandler(AuthenticationSuccessHandler successHandler)

试一下效果

务必使用域名进行访问,不要使用localhost或者IP

访问http://invybj.natappfree.cc/login这里是内网穿透域名,出现:

企业微信扫码登录的地址其实就是http://invybj.natappfree.cc/oauth2/authorization/work-wechat-scan。点击跳转到扫码页面:

然后用你对应的企业微信APP扫码企业和用户要和申请应用的一致。扫码后:

这个就是Spring Security 封装的用户认证信息Authentication对象,是真正的登录,这里我没有注入权限,你需要在企业微信的OAuth2UserService实现中注入权限和更多的信息。

总结

没有实现不了的,只要把原理和流程搞清楚就行。不过如果上游微信把代码写规范一些,下游何必写这么多冗余的代码。

关注公众号:码农小胖哥,获取更多资讯

个人博客:https://felord.cn

有关Spring Security整合企业微信的扫码登录,企微的API震惊到我了的更多相关文章

  1. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  2. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  3. ruby-on-rails - Mandrill API 模板 - 2

    我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h

  4. 微信小程序通过字典表匹配对应数据 - 2

    前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立

  5. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  6. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  7. ruby-on-rails - 在 Ruby (on Rails) 中使用 imgur API 获取图像 - 2

    我正在尝试使用Ruby2.0.0和Rails4.0.0提供的API从imgur中提取图像。我已尝试按照Ruby2.0.0文档中列出的各种方式构建http请求,但均无济于事。代码如下:require'net/http'require'net/https'defimgurheaders={"Authorization"=>"Client-ID"+my_client_id}path="/3/gallery/image/#{img_id}.json"uri=URI("https://api.imgur.com"+path)request,data=Net::HTTP::Get.new(path

  8. ruby-on-rails - 使用 HTTParty 的非常基本的 Rails 4.1 API 调用 - 2

    Rails相对较新。我正在尝试调用一个API,它应该向我返回一个唯一的URL。我的应用程序中捆绑了HTTParty。我已经创建了一个UniqueNumberController,并且我已经阅读了几个HTTParty指南,直到我想要什么,但也许我只是有点迷路,真的不知道该怎么做。基本上,我需要做的就是调用API,获取它返回的URL,然后将该URL插入到用户的数据库中。谁能给我指出正确的方向或与我分享一些代码? 最佳答案 假设API为JSON格式并返回如下数据:{"url":"http://example.com/unique-url"

  9. ruby-on-rails - 是否使用 API - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我的公司有一个巨大的数据库,该数据库接收来自多个来源的(许多)事件,用于监控和报告目的。到目前为止,数据中的每个新仪表板或图形都是一个新的Rails应用程序,在巨大的数据库中有额外的表,并且可以完全访问数据库内容。最近,有一个想法让外部(不是我们公司,而是姊妹公司)客户访问我们的数据,并且决定我们应该公开一个只读的RESTfulAPI来查询我们的数据。我的观点是-我们是否也应该为我们的自己

  10. ruby - Ruby 中的必应搜索 API - 2

    我读了"BingSearchAPI-QuickStart"但我不知道如何在Ruby中发出这个http请求(Weary)如何在Ruby中翻译“Stream_context_create()”?这是什么意思?"BingSearchAPI-QuickStart"我想使用RubySDK,但我发现那些已被弃用前(Rbing)https://github.com/mikedemers/rbing您知道Bing搜索API的最新包装器(仅限Web的结果)吗? 最佳答案 好吧,经过一个小时的挫折,我想出了一个办法来做到这一点。这段代码很糟糕,因为它是

随机推荐