java微信小程序授权+获取用户手机号 + jwt登录 !!!
前言
前几篇文章小编分享过:
有小伙伴私聊小编问“微信小程序授权获取用户手机号及jwt登录怎么做?”;小编也是工作之余分享此文
一、微信小程序授权、获取用户手机号
1、什么是微信公众号授权登录 ?
官网:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
当我们进入某个“小程序”时,可能出现上图界面(获取用户信息和获取用户手机号的授权),那具体是怎么实现的呢?
别急,小编在这里和大家浅聊微信小程序授权
二、小程序授权+获取用户手机号 + jwt登录 实操
★★★:项目全貌
- 项目架构:springboot(2.2.2) + mybatis
- 数据库: mysql
- 开发环境:jdk8、idea
- 授权:小程序授权
1、controller层
@Slf4j
@CrossOrigin
@RestController
@RequestMapping("/operation")
public class RefuelManagerOperationController {
@Resource
private OAuthService oAuthService; //service层
/**
* 第一步: 获取会话秘钥(session_key),给前段返回openID
*/
@ApiOperation(value = "授权获取会话秘钥", response = Code2SessionRespDTO.class)
@ApiResponses({@ApiResponse(code = 0, message = "Success"), @ApiResponse(code = 500, message = "failed")})
@PostMapping(value = "/getSessionKey", consumes = "application/json", produces = "application/json")
public RespResult<Code2SessionRespDTO> getWXOAuthCode2Session(@RequestBody @Valid GetWXOAuthCode2SessionReqDTO getWXOAuthCode2SessionReqDTO){
try {
//调用service层
return oAuthService.getWXOAuthCode2Session(getWXOAuthCode2SessionReqDTO.getCode());
} catch (Exception e) {
log.error("getWXOAuthCode2Session error: ", e);
return RespResult.error(RespResultEnum.ERROR.getCode(), e.getMessage());
}
}
/**
* 第二步: 根据加密数据、初始向量 获取用户手机号
*/
@ApiOperation(value = "授权获取用户手机号", response = UserInfoDTO.class)
@ApiResponses({@ApiResponse(code = 0, message = "Success"), @ApiResponse(code = 500, message = "failed")})
@PostMapping(value = "/getUserMobile", consumes = "application/json", produces = "application/json")
public RespResult<UserInfoDTO> getUserMobile(@RequestBody @Valid WeiXinOAuthGetUserMobileReqDTO weiXinOAuthGetUserMobileReqDTO) {
try {
//调用service层
return oAuthService.getUserMobile(weiXinOAuthGetUserMobileReqDTO);
} catch (Exception e) {
log.error("getUserMobile error: ", e);
return RespResult.error(RespResultEnum.ERROR);
}
}
2、service层
public interface OAuthService {
RespResult<Code2SessionRespDTO> getWXOAuthCode2Session(String js_code);
RespResult<UserInfoDTO> getUserMobile(WeiXinOAuthGetUserMobileReqDTO weiXinOAuthGetUserMobileReqDTO);
}
3、serviceImpl层
/**
* Create By LB on 2020/6/29.
*/
@Slf4j
@Service
public class OAuthServiceImpl implements OAuthService {
@Resource
private CommonRestTemplateService templateService; //自己封装的restTemplate(在最后)
@Resource
private WeChatAuthorizeUtil weChatAuthorizeUtil;//授权工具类(在最后)
@Resource
private RefuelUserMapper refuelUserMapper;// 用户mapper层
@Resource
private JwtUtils jwtUtils; //jwt工具类(在最后)
@Resource
private RedisProperties redisProperties; //redis 配置(在最后)
@Resource
private RedisTemplate redisTemplate; //redisTemplate(在最后)
@Resource
private HttpServletResponse response;
@Override
public RespResult<Code2SessionRespDTO> getWXOAuthCode2Session(String js_code) {
Code2SessionRespDTO respDTO = new Code2SessionRespDTO();
//使用restTemplate调用调用微信提供的接口(auth.code2Session接口)
ResponseEntity<String> responseEntity =
templateService.getForEntity(weChatAuthorizeUtil.getCode2SessionUrl(js_code));
if (responseEntity.getStatusCode().value() != HttpStatus.OK.value()) {
return RespResult.error(RespResultEnum.JS_CODE_REQ_ERROR);
}
//获取微信“auth.code2Session接口”返回的信息,并封装到自己的DTO中(DTO在最后)
WeiXinOAuthCode2SessionRespDTO code2SessionRespDTO =
JSON.parseObject(responseEntity.getBody(), WeiXinOAuthCode2SessionRespDTO.class);
if (StringUtils.isBlank(code2SessionRespDTO.getOpenid())) {
return RespResult.error(RespResultEnum.SESSION_KEY_ERROR.getCode(),
RespResultEnum.SESSION_KEY_ERROR.getmsg() + code2SessionRespDTO.getErrmsg());
}
//把从微信获取到的 会话密钥 session_key 放到redis中
//(因为微信官方特别提醒:为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。)
redisTemplate.opsForValue().set(redisProperties.getSessionKey() + code2SessionRespDTO.getOpenid(),
code2SessionRespDTO.getSession_key());
RefuelUserDO userDO = getUserByOpenId(code2SessionRespDTO.getOpenid());
if( userDO == null){
respDTO.setIsMiniUser(UserLoginMarkEnum.FIRST_LOGIN.getLoginMark());
}else {
respDTO.setIsMiniUser(UserLoginMarkEnum.LAST_LOGIN.getLoginMark());
//直接使用 jwt 签发 token
String token = jwtUtils.generateJwt(userDO.getUserId(),
UserTypeEnum.WX_USER.getUserType());
UserInfoDTO userInfoDTO = new UserInfoDTO();
BeanUtils.copyProperties(userDO,userInfoDTO);
userInfoDTO.setToken(token);
userInfoDTO.setUserId(String.valueOf(userDO.getUserId()));
userInfoDTO.setCreateTime(String.valueOf(userDO.getCreateTime().getTime()));
respDTO.setUserInfo(userInfoDTO);
}
respDTO.setOpenid(code2SessionRespDTO.getOpenid());
return RespResult.success(respDTO);
}
@Override
public RespResult<UserInfoDTO> getUserMobile(WeiXinOAuthGetUserMobileReqDTO reqDTO) {
UserInfoDTO respDTO = new UserInfoDTO();
//使用restTemplate调用调用微信提供的接口(获取手机号接口)
WeiXinOAuthUserInfoRespDTO weiXinUserInfo =
JSON.parseObject(reqDTO.getUserInfo(), WeiXinOAuthUserInfoRespDTO.class);
String sessionKey = (String) redisTemplate.opsForValue().
get(redisProperties.getSessionKey() + weiXinUserInfo.getOpenid());
//根据前段传的 加密数据(encryptedData)、初始向量(iv)
// 和 redis中的 会话密钥 session_key 三者来解密获得用户手机号(解密util在最后)
String mobileStr = WeChatMobileDecryptUtil.mobileDecrypt(sessionKey,
reqDTO.getIv(), reqDTO.getEncryptedData());
if (mobileStr == null) {
return RespResult.error(RespResultEnum.DECRYPT_MOBILE_ERROR);
}
WeiXinOAuthGetUserMobileRespDTO mobileInfo =
JSON.parseObject(mobileStr, WeiXinOAuthGetUserMobileRespDTO.class);
RefuelUserDO userInfo =
getUserByMobile(mobileInfo.getPhoneNumber());
RefuelUserDO refuelUserDO;
if (userInfo == null) {
refuelUserDO = insertRefuelUser(weiXinUserInfo, mobileInfo); //插入用户信息
}else { // 用户 通过APP注册过
refuelUserDO = updateRefuelUser(userInfo, weiXinUserInfo);//更新用户信息
}
BeanUtils.copyProperties(refuelUserDO, respDTO);
respDTO.setUserId(String.valueOf(refuelUserDO.getUserId()));
respDTO.setCreateTime(String.valueOf(refuelUserDO.getCreateTime().getTime()));
//直接使用 jwt 签发 token
String token = jwtUtils.generateJwt(refuelUserDO.getUserId(),
UserTypeEnum.WX_USER.getUserType());
respDTO.setToken(token);
return RespResult.success(respDTO);
}
private RefuelUserDO getUserByOpenId(String openId) {
QueryWrapper<RefuelUserDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(ColumnName.OPEN_ID, openId);
return refuelUserMapper.selectOne(queryWrapper);
}
private RefuelUserDO getUserByMobile(String mobile) {
QueryWrapper<RefuelUserDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(ColumnName.MOBILE, mobile);
return refuelUserMapper.selectOne(queryWrapper);
}
4、WeChatAuthorizeUtil
@Slf4j
@Component
public class WeChatAuthorizeUtil {
private WeChatAuthorizeUtil(){
}
@Resource
private WechatConfig wechatConfig;
//调用 auth.code2Session 接口(也就是下面的URL),换取 用户唯一标识 OpenID 和 会话密钥 session_key。
public String getCode2SessionUrl(String js_code) {
return WeiXinOAuthColumn.OAUTH_CODE2SESSION_URI
.replace("APPID", wechatConfig.getAppId())
.replace("SECRET", wechatConfig.getSecretKey())
.replace("JSCODE", js_code);
}
// 此为公众号授权url (小编上篇文章有讲过公众号授权)
// 微信授权 用户无感知 url(也就是用户不用点授权也能获取openID(不包含昵称、地区等),前提:在公众号内)
public String getOAuthCodeUrl_BASE() throws UnsupportedEncodingException {
return WeiXinOAuthColumn.OAUTH_CODE_URI
.replace("APPID", wechatConfig.getAppId())
.replace("REDIRECT_URI", URLEncoder.encode(wechatConfig.getFirstCodeRedirectUrl(), ENCODE))
.replace("SCOPE", WeiXinOAuthColumn.SNSAPI_BASE)
.replace("STATE", WeiXinOAuthColumn.STATE);
}
// 此为公众号授权url(小编上篇文章有讲过公众号授权)
// 微信授权 用户有感知 url(也就是需要用户点击授权才行)
public String getOAuthCodeUrl_USER() throws UnsupportedEncodingException {
return WeiXinOAuthColumn.OAUTH_CODE_URI
.replace("APPID", wechatConfig.getAppId())
.replace("REDIRECT_URI", URLEncoder.encode(wechatConfig.getLoginCodeRedirectUrl(), ENCODE))
.replace("SCOPE", WeiXinOAuthColumn.SNSAPI_USERINFO)
.replace("STATE", WeiXinOAuthColumn.STATE);
}
5、JwtUtils
// jwt 加密和解密工具类
@Slf4j
@Component
public class JwtUtils {
private JwtUtils() {
}
@Resource
private JwtConfig jwtConfig; // jwt 配置(加签 和 过期时间)
/**
* 加密
* @param userId
* @return
*/
public String generateJwt(Long userId, String userType) {
byte[] encode = Base64.getEncoder().encode(jwtConfig.getSecurityKey().getBytes());
Map<String, Object> claims = Maps.newHashMap();
claims.put(ColumnName.USER_ID, userId); //用户userId
claims.put(ColumnName.USER_TYPE, userType); //用户角色
return Jwts.builder()
.addClaims(claims) //内容
.setIssuedAt(TimeUtil.now()) //发行时间
.setExpiration(TimeUtil.getExpiration(jwtConfig.getOutTime())) //超时时间
.signWith(SignatureAlgorithm.HS512, encode)
.compact();
}
/**
* 解密
* @param token
* @return
*/
public Claims extractJwt(String token) {
try {
byte[] encode = Base64.getEncoder().encode(jwtConfig.getSecurityKey().getBytes());
return Jwts.parser()
.setSigningKey(encode)
.parseClaimsJws(token)
.getBody();
} catch (Exception ex) {
log.error("parseJWT error for {}", token);
return null;
}
}
6、Code2SessionRespDTO
// 授权获取会话秘钥 返回给前段DTO
@Data
public class Code2SessionRespDTO {
@ApiModelProperty(value = "0-不是,1-是(是否为小程序用户)", required = true)
private String isMiniUser;
@ApiModelProperty(value = "openid", required = true)
private String openid;
@ApiModelProperty(value = "用户信息(isMiniUser=1时有数据)", required = false)
private UserInfoDTO userInfo;
}
7、WeiXinOAuthGetUserMobileReqDTO
// 获取用户手机号 请求DTO
@Data
public class WeiXinOAuthGetUserMobileReqDTO {
@NotEmpty
@ApiModelProperty(value = "完整用户信息的加密数据", required = true)
private String encryptedData;
@NotEmpty
@ApiModelProperty(value = "加密算法的初始向量", required = true)
private String iv;
@ApiModelProperty(value = "敏感数据对应的云 ID", required = false)
private String cloudID;
@ApiModelProperty(value = "用户信息", required = true)
private String userInfo;
}
★★ 注意 ★★
- 由于授权相关代码比较多,小编就不挨个粘贴了,还望小伙伴多多谅解;
- 若需完整 “ 源码 ” 请按照下面方式获取
★★ 推荐 ★★
若有需要源码的小伙伴,可以扫描下面公众号,回复:小程序授权 小编直接发百度网盘↓↓↓
也可以加小编微信:CodeCow-6666 私信小编,切记:不懂一定要问,被骂也值
《 生活如海,宽容作舟,泛舟于海,方知海之宽阔 —— 凌晨之余写下此文!! 》