新聞中心
在前面的設(shè)計和實現(xiàn)中,我們的微服務(wù)開發(fā)平臺通過JustAuth來實現(xiàn)第三方授權(quán)登錄,通過集成公共組件,著實減少了很多工作量,大多數(shù)的第三方登錄直接通過配置就可以實現(xiàn)。而在第三方授權(quán)登錄中,微信小程序授權(quán)登錄和APP微信授權(quán)登錄是兩種特殊的第三方授權(quán)登錄。
??JustAuth之所以能夠?qū)⒍喾N第三方授權(quán)登錄服務(wù)整合在一起,抽象公共組件的原因是大多數(shù)的授權(quán)登錄服務(wù)器都是遵循OAuth2.0協(xié)議開發(fā),雖然略有不同但可通過適配器進行轉(zhuǎn)換為統(tǒng)一接口。微信小程序授權(quán)登錄和APP的微信授權(quán)登錄也是OAutn2.0協(xié)議的授權(quán)登錄,但在對接的流程中不是完整的OAuth2.0對接流程。
??通常的第三方授權(quán)登錄過程中,獲取token的state和code是在回調(diào)客戶端url中獲取的,而微信小程序授權(quán)登錄和APP的微信授權(quán)登錄獲取token的state和code是使用微信提供的特定方法獲取到的,然后通過微信傳給客戶端,客戶端拿到code之后到后臺取獲取openid等微信用戶信息。然后,再進行系統(tǒng)登錄相關(guān)操作。

創(chuàng)新互聯(lián)建站服務(wù)項目包括峽江網(wǎng)站建設(shè)、峽江網(wǎng)站制作、峽江網(wǎng)頁制作以及峽江網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,峽江網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到峽江省份的部分城市,未來相信會繼續(xù)擴大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
一、微信小程序授權(quán)登錄、注冊、綁定流程設(shè)計說明
- 微信小程序授權(quán)登錄、注冊、綁定流程圖:
- 微信小程序授權(quán)登錄、注冊、綁定流程說明:
- 用戶進入小程序。
- 小程序前端通過從緩存中獲取是否有token來判定用戶是否登錄。
- 如果未登錄,那么跳轉(zhuǎn)到小程序登錄頁。
- 小程序前端執(zhí)行微信登錄方法wx.login獲取微信登錄的code(此時并未進行微信授權(quán)登錄)。
- 小程序前端通過code向業(yè)務(wù)后臺發(fā)送請求獲取用戶唯一的openid。
- 業(yè)務(wù)系統(tǒng)根據(jù)openid或者unionid判斷該用戶是否綁定了業(yè)務(wù)用戶,并將是否綁定信息返回給前臺。
- 如果沒有綁定過,那么前端展示微信授權(quán)登錄按鈕。
- 用戶點擊“授權(quán)登錄”按鈕之后,小程序前端會獲取到加密的用戶信息。
- 小程序前端將加密的用戶信息傳到業(yè)務(wù)后臺進行解密。
- 業(yè)務(wù)后臺收到加密用戶信息后,通過請求微信服務(wù)器解密用戶信息,并將用戶信息存儲到業(yè)務(wù)系統(tǒng)表。
- 后臺將解密后的用戶信息(非私密信息)返回到小程序前臺。
- 如果是沒有綁定的,那么小程序前臺彈出是否獲取當(dāng)前用戶手機號的彈出框。
- 用戶選擇是否獲取微信綁定的手機號來注冊或綁定到業(yè)務(wù)系統(tǒng)的用戶。
- 當(dāng)用戶點擊統(tǒng)一獲取手機號時,微信會返回加密后的手機號,然后前端將加密后的手機號發(fā)送到業(yè)務(wù)后臺解密。
- 業(yè)務(wù)后臺獲取到手機號碼之后,會根據(jù)手機號碼在系統(tǒng)用戶表中進行匹配,如果匹配到用戶,那么直接返回小程序用戶信息。
- 當(dāng)用戶不同意獲取手機號時,那么小程序跳轉(zhuǎn)到輸入賬號密碼進行綁定頁面。
- 當(dāng)綁定操作執(zhí)行成功之后,微信小程序調(diào)用第三方登錄獲取token方式,向業(yè)務(wù)后臺獲取token。
- 用戶小程序授權(quán)登錄、注冊、綁定成功。
二、微信小程序授權(quán)登錄、注冊、綁定業(yè)務(wù)后臺功能實現(xiàn)
??微信通過其開放平臺提供小程序登錄功能接口,我們的業(yè)務(wù)服務(wù)可以通過小程序的登錄接口方便地獲取微信提供的用戶身份標(biāo)識,進而將業(yè)務(wù)自身用戶體系和微信用戶相結(jié)合,從而更完美地在微信小程序中實現(xiàn)業(yè)務(wù)功能。
??微信小程序提供了對接登錄的SDK,我們只需要按照其官方文檔對接開發(fā)即可。同時也有很多開源組件將SDK再次進行封裝,在業(yè)務(wù)開發(fā)中可以更快速的集成小程序各個接口的調(diào)用。
??出于快速開發(fā)的原則,同時也少走彎路、少踩坑,我們可以選擇一款實現(xiàn)比較完善的組件進行微信小程序的對接。weixin-java-miniapp是集成微信小程序相關(guān)SDK操作的工具包,我們在項目中集成此工具包來實現(xiàn)微信小程序授權(quán)登錄。
1、引入weixin-java-miniapp相關(guān)maven依賴,目前發(fā)布版本為4.4.0正式版。
??一般在選擇開源工具包時,我們不會選擇最新版,而是選擇穩(wěn)定版本,但是微信的開放接口經(jīng)常變動,這里為了能夠兼容最新的微信小程序接口,我們在引用包的時候一定要選擇更新版本,否則會影響部分接口的調(diào)用。
......
......
4.4.0
......
com.github.binarywang
weixin-java-miniapp
${weixin-java-miniapp.version}
......
2、在配置文件application-dev.yml、application-test.yml、application-prod.yml中新增微信小程序需要的配置項。
??關(guān)于小程序如何注冊,appid和appsecret如何獲取,這里不展開講,微信開放平臺有詳細的說明文檔。
wx:
miniapp:
configs:
- appid: #微信小程序appid
secret: #微信小程序secret
token: #微信小程序消息服務(wù)器配置的token
aesKey: #微信小程序消息服務(wù)器配置的EncodingAESKey
msgDataFormat: JSON
3、將weixin-java-miniapp配置類文件WxMaConfiguration.java和WxMaProperties.java添加到我們的工程中。
- WxMaConfiguration.java關(guān)鍵代碼
......
@Data
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxMaProperties {
private Listconfigs;
@Data
public static class Config {
/**
* 設(shè)置微信小程序的appid
*/
private String appid;
/**
* 設(shè)置微信小程序的Secret
*/
private String secret;
/**
* 設(shè)置微信小程序消息服務(wù)器配置的token
*/
private String token;
/**
* 設(shè)置微信小程序消息服務(wù)器配置的EncodingAESKey
*/
private String aesKey;
/**
* 消息格式,XML或者JSON
*/
private String msgDataFormat;
}
}
......
- WxMaProperties.java關(guān)鍵代碼
......
private final WxMaProperties properties;
@Autowired
public WxMaConfiguration(WxMaProperties properties) {
this.properties = properties;
}
@Bean
public WxMaService wxMaService() {
Listconfigs = this.properties.getConfigs();
if (configs == null) {
throw new WxRuntimeException("配置錯誤!");
}
WxMaService maService = new WxMaServiceImpl();
maService.setMultiConfigs(
configs.stream()
.map(a -> {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
config.setAppid(a.getAppid());
config.setSecret(a.getSecret());
config.setToken(a.getToken());
config.setAesKey(a.getAesKey());
config.setMsgDataFormat(a.getMsgDataFormat());
return config;
}).collect(Collectors.toMap(WxMaDefaultConfigImpl::getAppid, a -> a, (o, n) -> o)));
return maService;
}
......
4、新建WxMaUserController.java用于實現(xiàn)微信小程序請求的相關(guān)接口。
- 實現(xiàn)小程序登錄的login接口,此接口會根據(jù)微信小程序前端傳來的code進行獲取用戶session_key、openid/unionid,我們的業(yè)務(wù)系統(tǒng)會根據(jù)openid/unionid結(jié)合第三方登錄表進行用戶匹配,如果存在該用戶則返回給小程序前臺已存在的用戶信息;如果不存在,說明該用戶是第一次微信小程序登錄,那么我們將獲取到的微信唯一身份標(biāo)識加密,并返回微信小程序前臺進行下一步綁定賬戶或注冊操作。
/**
* 登陸接口
*/
@ApiOperation(value = "小程序登錄接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "code", value = "小程序code", dataType="String", paramType = "query"),
})
@GetMapping("/login")
public Result> login(@PathVariable String appid, String code) {
if (StringUtils.isBlank(code)) {
return Result.error("code 不能為空");
}
if (!wxMaService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到對應(yīng)appid=[%s]的配置,請核實!", appid));
}
WeChatMiniAppLoginDTO weChatMiniAppLoginDTO = new WeChatMiniAppLoginDTO();
try {
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);
weChatMiniAppLoginDTO.setOpenid(session.getOpenid());
weChatMiniAppLoginDTO.setUnionid(session.getUnionid());
// 通過openId獲取在系統(tǒng)中是否是已經(jīng)綁定過的用戶,如果沒有綁定,那么返回到前臺,提示需要綁定或者注冊用戶
LambdaQueryWrappersocialLambdaQueryWrapper = new LambdaQueryWrapper<>();
// 如果微信開通了開放平臺,那么各個渠道(小程序、公眾號等)都會有統(tǒng)一的unionid,如果沒開通,就僅僅使用openId
if (StringUtils.isBlank(session.getUnionid()))
{
socialLambdaQueryWrapper.eq(JustAuthSocial::getOpenId, session.getOpenid())
.eq(JustAuthSocial::getSource, "WECHAT_MINI_APP");
}
else
{
socialLambdaQueryWrapper.eq(JustAuthSocial::getUnionId, session.getUnionid())
.and(e -> e.eq(JustAuthSocial::getSource, "WECHAT_MINI_APP")
.or().eq(JustAuthSocial::getSource, "WECHAT_OPEN")
.or().eq(JustAuthSocial::getSource, "WECHAT_MP")
.or().eq(JustAuthSocial::getSource, "WECHAT_ENTERPRISE")
.or().eq(JustAuthSocial::getSource, "WECHAT_APP"));
}
JustAuthSocial justAuthSocial = justAuthSocialService.getOne(socialLambdaQueryWrapper, false);
if (null == justAuthSocial)
{
weChatMiniAppLoginDTO.setUserInfoAlready(false);
weChatMiniAppLoginDTO.setUserBindAlready(false);
justAuthSocial = new JustAuthSocial();
justAuthSocial.setAccessCode(session.getSessionKey());
justAuthSocial.setOpenId(session.getOpenid());
justAuthSocial.setUnionId(session.getUnionid());
justAuthSocial.setSource("WECHAT_MINI_APP");
justAuthSocialService.save(justAuthSocial);
} else {
justAuthSocial.setAccessCode(session.getSessionKey());
justAuthSocialService.updateById(justAuthSocial);
}
// 將socialId進行加密返回,用于前端進行第三方登錄,獲取token
DES des = new DES(Mode.CTS, Padding.PKCS5Padding, secretKey.getBytes(), secretKeySalt.getBytes());
// 這里將source+uuid通過des加密作為key返回到前臺
String socialKey = "WECHAT_MINI_APP" + StrPool.UNDERLINE + (StringUtils.isBlank(session.getUnionid()) ? session.getOpenid() : session.getUnionid());
// 將socialKey放入緩存,默認有效期2個小時,如果2個小時未完成驗證,那么操作失效,重新獲取,在system:socialLoginExpiration配置
redisTemplate.opsForValue().set(AuthConstant.SOCIAL_VALIDATION_PREFIX + socialKey, String.valueOf(justAuthSocial.getId()), socialLoginExpiration,
TimeUnit.SECONDS);
String desSocialKey = des.encryptHex(socialKey);
weChatMiniAppLoginDTO.setBindKey(desSocialKey);
// 查詢是否綁定用戶
// 判斷此第三方用戶是否被綁定到系統(tǒng)用戶
Result
- 實現(xiàn)獲取/解密微信小程序用戶信息的info接口
??當(dāng)微信小程序前端獲取到用戶授權(quán)可以獲取用戶信息時,微信小程序前端將加密的用戶信息發(fā)送到業(yè)務(wù)后臺,業(yè)務(wù)后臺請求微信服務(wù)器將用戶信息解密并保存到我們的第三方用戶登錄表內(nèi)。
/**
* 獲取用戶信息接口
*/
@ApiOperation(value = "小程序獲取用戶信息接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "socialKey", value = "加密的登錄key,用于綁定用戶", required = true, dataType="String", paramType = "query"),
@ApiImplicitParam(name = "signature", value = "使用 sha1( rawData + sessionkey ) 得到字符串,用于校驗用戶信息", required = true, dataType="String", paramType = "query"),
@ApiImplicitParam(name = "rawData", value = "不包括敏感信息的原始數(shù)據(jù)字符串,用于計算簽名", required = true, dataType="String", paramType = "query"),
@ApiImplicitParam(name = "encryptedData", value = "包括敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù)", required = true, dataType="String", paramType = "query"),
@ApiImplicitParam(name = "iv", value = "加密算法的初始向量", required = true, dataType="String", paramType = "query")
})
@GetMapping("/info")
public Result> info(@PathVariable String appid, String socialKey,
String signature, String rawData, String encryptedData, String iv) {
if (!wxMaService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到對應(yīng)appid=[%s]的配置,請核實!", appid));
}
// 查詢第三方用戶信息
JustAuthSocial justAuthSocial = this.getJustAuthSocial(socialKey);
if (StringUtils.isBlank(justAuthSocial.getAccessCode()))
{
throw new BusinessException("登錄狀態(tài)失效,請嘗試重新進入小程序");
}
// 用戶信息校驗
if (!wxMaService.getUserService().checkUserInfo(justAuthSocial.getAccessCode(), rawData, signature)) {
WxMaConfigHolder.remove();//清理ThreadLocal
return Result.error("user check failed");
}
// 解密用戶信息
WxMaUserInfo userInfo = wxMaService.getUserService().getUserInfo(justAuthSocial.getAccessCode(), encryptedData, iv);
WxMaConfigHolder.remove();//清理ThreadLocal
justAuthSocial.setAvatar(userInfo.getAvatarUrl());
justAuthSocial.setUnionId(userInfo.getUnionId());
justAuthSocial.setNickname(userInfo.getNickName());
justAuthSocialService.updateById(justAuthSocial);
return Result.data(userInfo);
}
- 當(dāng)在小程序前端綁定或注冊賬號時,可以在用戶允許的前提下獲取微信用戶的手機號,同樣,手機號和用戶信息都是需要傳到業(yè)務(wù)后臺,然后再請求微信服務(wù)器進行解密。獲取到手機號之后,業(yè)務(wù)后臺根據(jù)手機號和系統(tǒng)用戶進行匹配,如果存在,那么直接將微信賬號綁定到我們業(yè)務(wù)系統(tǒng)的當(dāng)前手機號用戶。如果不存在,那么返回微信小程序不存在綁定用戶的信息,由小程序前端繼續(xù)進行綁定或者注冊操作。
/**
* 獲取用戶綁定手機號信息
*/
@ApiOperation(value = "小程序獲取用戶綁定手機號信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "socialKey", value = "加密的登錄key,用于綁定用戶", required = true, dataType="String", paramType = "query"),
@ApiImplicitParam(name = "encryptedData", value = "包括敏感數(shù)據(jù)在內(nèi)的完整用戶信息的加密數(shù)據(jù)", required = true, dataType="String", paramType = "query"),
@ApiImplicitParam(name = "iv", value = "加密算法的初始向量", required = true, dataType="String", paramType = "query")
})
@GetMapping("/phone")
public Result> phone(@PathVariable String appid, String socialKey, String encryptedData, String iv) {
if (!wxMaService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到對應(yīng)appid=[%s]的配置,請核實!", appid));
}
// 查詢第三方用戶信息
JustAuthSocial justAuthSocial = this.getJustAuthSocial(socialKey);
if (StringUtils.isBlank(justAuthSocial.getAccessCode()))
{
throw new BusinessException("登錄狀態(tài)失效,請嘗試重新進入小程序");
}
// 解密
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(justAuthSocial.getAccessCode(), encryptedData, iv);
WxMaConfigHolder.remove();//清理ThreadLocal
// 不帶區(qū)號的手機,國外的手機會帶區(qū)號
String phoneNumber = phoneNoInfo.getPurePhoneNumber();
// 查詢用戶是否存在,如果存在,那么直接調(diào)用綁定接口
LambdaQueryWrapperlambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getMobile, phoneNumber);
User userInfo = userService.getOne(lambdaQueryWrapper);
Long userId;
// 判斷返回信息
if (null != userInfo && null != userInfo.getId()) {
userId = userInfo.getId();
}
else {
// 如果用戶不存在,那么調(diào)用新建用戶接口,并綁定
CreateUserDTO createUserDTO = new CreateUserDTO();
createUserDTO.setAccount(phoneNumber);
createUserDTO.setMobile(phoneNumber);
createUserDTO.setNickname(StringUtils.isBlank(justAuthSocial.getNickname()) ? phoneNumber : justAuthSocial.getNickname());
createUserDTO.setPassword(StringUtils.isBlank(justAuthSocial.getUnionId()) ? justAuthSocial.getOpenId() : justAuthSocial.getUnionId());
createUserDTO.setStatus(GitEggConstant.UserStatus.ENABLE);
createUserDTO.setAvatar(justAuthSocial.getAvatar());
createUserDTO.setEmail(justAuthSocial.getEmail());
createUserDTO.setStreet(justAuthSocial.getLocation());
createUserDTO.setComments(justAuthSocial.getRemark());
CreateUserDTO resultUserAdd = userService.createUser(createUserDTO);
if (null != resultUserAdd && null != resultUserAdd.getId()) {
userId = resultUserAdd.getId();
} else {
// 如果添加失敗,則返回失敗信息
return Result.data(resultUserAdd);
}
}
// 執(zhí)行綁定操作
justAuthService.userBind(justAuthSocial.getId(), userId);
return Result.success("賬號綁定成功");
}
- 提供綁定當(dāng)前登錄賬號接口,由微信小程序前端進行調(diào)用綁定
/**
* 綁定當(dāng)前登錄賬號
*/
@ApiOperation(value = "綁定當(dāng)前登錄賬號")
@ApiImplicitParams({
@ApiImplicitParam(name = "socialKey", value = "加密的登錄key,用于綁定用戶", required = true, dataType="String", paramType = "query")
})
@GetMapping("/bind")
public Result> bind(@PathVariable String appid, @NotBlank String socialKey, @CurrentUser GitEggUser user) {
if (!wxMaService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到對應(yīng)appid=[%s]的配置,請核實!", appid));
}
if (null == user || (null != user && null == user.getId())) {
throw new BusinessException("用戶未登錄");
}
// 查詢第三方用戶信息
JustAuthSocial justAuthSocial = this.getJustAuthSocial(socialKey);
if (StringUtils.isBlank(justAuthSocial.getAccessCode()))
{
throw new BusinessException("賬號綁定失敗,請嘗試重新進入小程序");
}
// 執(zhí)行綁定操作
justAuthService.userBind(justAuthSocial.getId(), user.getId());
return Result.success("賬號綁定成功");
}
- 提供解綁接口,除了綁定接口外,我們系統(tǒng)服務(wù)應(yīng)提供微信小程序解綁功能,這里實現(xiàn)解綁接口
/**
* 解綁當(dāng)前登錄賬號
*/
@ApiOperation(value = "解綁當(dāng)前登錄賬號")
@GetMapping("/unbind")
public Result> unbind(@PathVariable String appid, @CurrentUser GitEggUser user) {
if (!wxMaService.switchover(appid)) {
throw new IllegalArgumentException(String.format("未找到對應(yīng)appid=[%s]的配置,請核實!", appid));
}
if (null == user || (null != user && null == user.getId())) {
throw new BusinessException("用戶未登錄");
}
LambdaQueryWrapperqueryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(JustAuthSocialUser::getUserId, user.getId());
justAuthSocialUserService.remove(queryWrapper);
return Result.success("賬號解綁成功");
}
??通過以上接口的功能,基本實現(xiàn)了微信小程序前端進行綁定、注冊以及獲取用戶信息、用戶手機號所需要的接口,下面來實現(xiàn)小程序前端具體的業(yè)務(wù)實現(xiàn)。
三、微信小程序授權(quán)登錄、注冊、綁定小程序前端功能實現(xiàn)。
??微信小程序前端開發(fā)有多種方式,可以使用微信小程序官方開發(fā)方式,也可以使用第三方的開發(fā)方式。因為大多數(shù)前端都會使用Vue.js開發(fā),而mpvue可以使用開發(fā)Vue.js的方式來開發(fā)微信小程序,所以這里我們選擇使用mpvue來開發(fā)微信小程序。這里不詳細講解mpvue框架的搭建過程,只詳細說明微信小程序授權(quán)登錄相關(guān)功能,有需要的可以參考mpvue官方文檔。
1、定義微信小程序授權(quán)登錄相關(guān)接口文件login.js,將我們業(yè)務(wù)后臺實現(xiàn)的接口統(tǒng)一管理和調(diào)用。
??因為我們的開發(fā)框架是支持多租戶的,同時也是支持多個小程序的,為了同一套后臺可以支持多個微信小程序,這里選擇在發(fā)布的微信小程序中配置appId,由微信小程序前端參數(shù)來確定具體的微信小程序。
import fly from '@/utils/requestWx'
// 獲取用戶信息
export function getOpenId (params) {
return fly.get(`/wx/user/${params.appId}/login`, params)
}
// 獲取用戶信息
export function getUserInfo (params) {
return fly.get(`/wx/user/${params.appId}/info`, params)
}
// 獲取用戶手機號
export function getUserPhone (params) {
return fly.get(`/wx/user/${params.appId}/phone`, params)
}
// 綁定微信賬號
export function bindWeChatUser (params) {
return fly.get(`/wx/user/${params.appId}/bind`, params)
}
// 解綁微信賬號
export function unbindWeChatUser (params) {
return fly.get(`/wx/ 分享標(biāo)題:「SpringCloud」微信小程序授權(quán)登錄流程設(shè)計和實現(xiàn)
文章來源:http://www.dlmjj.cn/article/dpddshe.html


咨詢
建站咨詢
