WeChat 애플릿의 Java 로그인 프로세스를 구현하는 방법
미니 프로그램 클라이언트
doLogin:function(callback = () =>{}){ let that = this; wx.login({ success:function(loginRes){ if(loginRes){ //获取用户信息 wx.getUserInfo({ withCredentials:true,//非必填 默认为true success:function(infoRes){ console.log(infoRes,'>>>'); //请求服务端的登录接口 wx.request({ url: api.loginUrl, data:{ code:loginRes.code,//临时登录凭证 rawData:infoRes.rawData,//用户非敏感信息 signature:infoRes.signature,//签名 encrypteData:infoRes.encryptedData,//用户敏感信息 iv:infoRes.iv//解密算法的向量 }, success:function(res){ console.log('login success'); res = res.data; if(res.result==0){ that.globalData.userInfo = res.userInfo; wx.setStorageSync('userInfo',JSON.stringify(res.userInfo)); wx.setStorageSync('loginFlag',res.skey); console.log("skey="+res.skey); callback(); }else{ that.showInfo('res.errmsg'); } }, fail:function(error){ //调用服务端登录接口失败 // that.showInfo('调用接口失败'); console.log(error); } }); } }); }else{ } } }); } 复制代码
WeChat 애플릿은 로그인 요청을 시작하며 전달되는 주요 매개변수는 다음과 같습니다:
code:loginRes.code,//临时登录凭证 rawData:infoRes.rawData,//用户非敏感信息 signature:infoRes.signature,//签名 encrypteData:infoRes.encryptedData,//用户敏感信息 iv:infoRes.iv//解密算法的向量 复制代码
매개변수 설명: code:loginRes.code,//임시 로그인 자격 증명: 전달되어야 하며, 코드는 백그라운드 sessionKey 및 openId rawData:infoRes.rawData,//사용자의 민감하지 않은 정보 서명:infoRes.signature,/와 교환하는 데 사용됩니다. /서명 encrypteData: infoRes.encryptedData,//사용자의 민감한 정보 iv: infoRes.iv//복호화 알고리즘의 벡터
서명,//서명, 암호화된 데이터,//사용자 중요 정보, iv //복호화 알고리즘 벡터:
이 세 가지 매개변수는 전화번호 및 기타 정보와 같은 민감한 사용자 정보를 디코딩하는 데 사용됩니다.
필요한 데이터에는 주로 사용자의 고유성을 표시하는 데 사용되는 skey가 포함됩니다.
3. 자바 백엔드
/** * 登陆接口 */ @RequestMapping("/login") @ApiResponses({ @ApiResponse(code = 404, message = "服务器未找到资源"), @ApiResponse(code = 200, message = "请求成功"), @ApiResponse(code = 500, message = "服务器错误"), @ApiResponse(code = 401, message = "没有访问权限"), @ApiResponse(code = 403, message = "服务器拒绝访问"), }) @ApiOperation(value = "小程序登录", httpMethod = "POST", notes = "小程序登录") public ResponseEntity<LoginDataResult> login( @ApiParam(required = true, value = "临时登录凭证code", name = "code") String code, @ApiParam(required = true, value = "用户非敏感信息", name = "rawData") @RequestParam(value = "rawData", required = true) String rawData, @ApiParam(required = true, value = "签名", name = "signature") @RequestParam(value = "signature", required = true) String signature, @ApiParam(required = true, value = "用户敏感信息", name = "encrypteData") @RequestParam(value = "encrypteData", required = true) String encrypteData, @ApiParam(required = true, value = "解密算法的向量", name = "iv") @RequestParam(value = "iv", required = true) String iv ) { ObjectMapper mapper = new ObjectMapper(); logger.info("signature============================================================="+signature); logger.info("encrypteData=========================================================="+encrypteData); logger.info("iv========================================================================"+iv); RawData data = null; WxMaJscode2SessionResult session = null; String openid = null; String sessionKey = null; String phoneNumber = null; try { if (rawData != null && !"".equals(rawData)) { //1、获取用户非敏感信息 data = mapper.readValue(rawData, RawData.class); } session = this.wxService.getUserService().getSessionInfo(code); //获取到openid和sessionkey openid = session.getOpenid(); sessionKey = session.getSessionKey(); logger.info("sessionkey========================================================="+sessionKey); /* //2、获取用户手机号 phoneNumber = phone(code, signature, rawData, encrypteData, iv); logger.info("phoneNumber========================================="+phoneNumber); */ } catch (IOException e) { e.printStackTrace(); logger.info("获取用户信息失败"); LoginDataResult loginDataResult = new LoginDataResult(); loginDataResult.setCode("2"); loginDataResult.setMsg("请求失败"); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(loginDataResult); } catch (WxErrorException e) { e.printStackTrace(); logger.info("获取用户信息失败"); } //3、向数据库插入用户信息 String skey = insertUser(data, openid, phoneNumber); //4、缓存openid, sessionKey, skey到redis redisCache(openid, sessionKey, skey); //5、把新的skey返回给小程序 LoginDataResult loginDataResult = new LoginDataResult(); loginDataResult.setSkey(skey); loginDataResult.setCode("1"); loginDataResult.setMsg("请求成功"); return ResponseEntity.status(HttpStatus.OK).body(loginDataResult); } /** * 缓存openid,sessionKey,skey等信息 * @param openid 小程序用户唯一标志 * @param sessionKey 小程序会话标志 * @param skey 后台生成的用户唯一标志,会话管理 */ private void redisCache(String openid, String sessionKey, String skey) { //根据openid查询skey是否存在 String skey_redis = jedisClient.hget("WEXIN_USER_OPENID_SKEY", openid); if (StringUtils.isNotBlank(skey_redis)) { //存在 删除 skey 重新生成skey 将skey返回 jedisClient.hdel("WEXIN_USER_OPENID_SKEY", openid); jedisClient.hdel("WEIXIN_USER_SKEY_OPENID", skey_redis); jedisClient.hdel("WEIXIN_USER_SKEY_SESSIONKEY", skey_redis); } // 缓存一份新的 jedisClient.hset("WEXIN_USER_OPENID_SKEY", openid, skey); jedisClient.expire("WEXIN_USER_OPENID_SKEY",432000);//设置5天过期 jedisClient.hset("WEIXIN_USER_SKEY_OPENID", skey, openid); jedisClient.expire("WEIXIN_USER_SKEY_OPENID",432000);//设置5天过期 jedisClient.hset("WEIXIN_USER_SKEY_SESSIONKEY", skey, sessionKey); jedisClient.expire("WEIXIN_USER_SKEY_SESSIONKEY",432000);//设置5天过期 } /** * 将用户信息插入到数据库 * @param data 用户信息 * @param openid * @param phoneNumber 手机号 * @return */ private String insertUser(RawData data, String openid, String phoneNumber) { //判断用户数据库是否存在,不存在,入库。 Member user = userService.selectUserByOpenid(openid); //uuid生成唯一key String skey = UUID.randomUUID().toString(); if (user == null) { //入库 user = new Member(); user.setId(skey); user.setCountry(data.getCountry()); user.setCreatedate(new Date()); user.setDf(1); user.setGender(data.getGender().equals("1") ? 1 : 2);//1为男,2为女 user.setHeadimg(data.getAvatarUrl()); user.setNickname(data.getNickName()); user.setOpenid(openid); user.setCitycode(data.getCity()); user.setProvincecode(data.getProvince()); user.setMobileno(phoneNumber); //插入到数据库 userService.insertUser(user); } else { //已存在 logger.info("用户openid已存在,不需要插入"); return user.getId();//返回用户唯一标志skey } return skey; } /** * 获取用户板绑定的手机号 * @param sessionKey 小程序session * @param signature 签名 * @param rawData 用户信息 * @param encryptedData 小程序加密数据 * @param iv 小程序向量 * @return */ @ApiOperation(value = "用户手机号获取", httpMethod = "GET", notes = "用户手机号获取") public String phone(String sessionKey, String signature, String rawData, String encryptedData, String iv) { String phoneNumber = null; try { byte[] bytes = WxMiniappUtils.decrypt(Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv), Base64.decodeBase64(encryptedData)); String phone = new String(bytes, "UTF8"); logger.info("phone====================================="+phone); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } 复制代码
위 코드를 분석해 보겠습니다.
3.1 openid 및 sessionKey 가져오기
session = this.wxService.getUserService().getSessionInfo(code); //获取到openid和sessionkey openid = session.getOpenid(); sessionKey = session.getSessionKey(); 复制代码
이 코드는 매우 간결합니까? 여기에서는 자세한 데모에 표시된 것처럼 타사 SDK(weixin-java-tools)를 사용하여 openid 및 sessionKey를 매우 쉽게 얻을 수 있습니다.
물론, 타사 SDK를 사용하고 싶지 않다면 직접 구현할 수도 있습니다. 구현 코드는 다음과 같습니다.
public static JSONObject getSessionKeyOrOpenId(String code){ //微信端登录code String wxCode = code; String requestUrl = "https://api.weixin.qq.com/sns/jscode2session"; Map<String,String> requestUrlParam = new HashMap<String, String>( ); requestUrlParam.put( "appid","你的小程序appId" );//小程序appId requestUrlParam.put( "secret","你的小程序appSecret" ); requestUrlParam.put( "js_code",wxCode );//小程序端返回的code requestUrlParam.put( "grant_type","authorization_code" );//默认参数 //发送post请求读取调用微信接口获取openid用户唯一标识 JSONObject jsonObject = JSON.parseObject( UrlUtil.sendPost( requestUrl,requestUrlParam )); return jsonObject; } 复制代码
3.2 사용자 정보를 얻기 위해 사용자의 민감한 데이터를 해독합니다
3.2.1컨트롤러
이 부분에서는 사용자의 휴대폰 번호를 얻어야 하기 때문에 사용자의 정보를 복호화해야 하는 부분이 많습니다.
rreee3.2.2복호화 도구 클래스
도구 클래스 WxMiniappUtils.decrypt가 여기에서 호출됩니다. 도구 클래스는 다음과 같습니다.
/** * 获取用户板绑定的手机号 * @param sessionKey 小程序session * @param signature 签名 * @param rawData 用户信息 * @param encryptedData 小程序加密数据 * @param iv 小程序向量 * @return */ @ApiOperation(value = "用户手机号获取", httpMethod = "GET", notes = "用户手机号获取") public String phone(String sessionKey, String signature, String rawData, String encryptedData, String iv) { String phoneNumber = null; try { byte[] bytes = WxMiniappUtils.decrypt(Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv), Base64.decodeBase64(encryptedData)); String phone = new String(bytes, "UTF8"); logger.info("phone====================================="+phone); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } 复制代码
여기서 사용되는 Cipher 클래스는 javax.crypto 클래스입니다.
3.2.3 질문
하지만 여기서 이 복호화 도구 클래스를 사용할 때 많은 문제에 직면했습니다.
첫째: AES 암호 해독에서 javax.crypto.BadPaddingException 오류가 보고됩니다: 패드 블록 손상됨
이 문제는 Cipher.getInstance("AES/CBC/PKCS5Padding")를 사용하는 도구 클래스로 인해 발생합니다.
해결 방법: 암호 암호 = Cipher.getInstance("AES/ECB/ZeroBytePadding");.
두 번째: java.security.InvalidAlgorithmParameterException: 잘못된 IV 길이: 16이어야 합니다. 이 문제는 디코딩된 iv가 16비트가 아니기 때문에 15비트인 것 같습니다.
해결책: 이 문제를 해결하는 방법은 무엇입니까? 해결책을 찾지 못한 사람이 있으면 알려주십시오.
내 해결 방법: 실제로 이 문제는 이 도구 클래스의 문제가 아니라는 것을 알았습니다. 하루 동안 고생한 끝에 이 도구 클래스는 휴대폰 번호를 해독할 수 없다는 것을 발견했습니다. 그리고 일부는 휴대폰 번호를 파싱할 수 없습니다. 일반적인 정보만 있으므로 이는 위챗 사용자가 휴대폰 번호로 등록했는지 여부에 따른 것이므로 일부는 파싱할 수 있고 일부는 파싱할 수 없는 것으로 생각됩니다. 혹시 다른 방법 있으신 분 계시면 알려주세요!
3.2.4 성공적인 데이터 구문 분석
/** * 解密用户手机号算法 * @param sessionkey 小程序登录sessionKey * @param iv 向量 * @param encryptedData * @return * @throws NoSuchPaddingException * @throws NoSuchAlgorithmException * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException * @throws BadPaddingException * @throws IllegalBlockSizeException */ public static byte[] decrypt(byte[] sessionkey, byte[] iv, byte[] encryptedData) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException { AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(sessionkey, "AES"); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); return cipher.doFinal(encryptedData); } 复制代码
이러한 json 데이터가 구문 분석되면 성공한 것입니다.
3.2.5 또 다른 솔루션
{"phoneNumber":"13880684012","purePhoneNumber":"13880684012","countryCode":"86","watermark":{"timestamp":1519460296,"appid":"wx6ede2086ee29a89f"}} 复制代码
3.2.6 타사 SDK 방법
public class AES { public static final AES instance = new AES(); public static boolean initialized = false; /** * AES解密 * @param content 密文 * @return * @throws InvalidAlgorithmParameterException * @throws NoSuchProviderException */ public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException { initialize(); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); Key sKeySpec = new SecretKeySpec(keyByte, "AES"); cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化 byte[] result = cipher.doFinal(content); return result; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void initialize(){ if (initialized) return; Security.addProvider(new BouncyCastleProvider()); initialized = true; } //生成iv public static AlgorithmParameters generateIV(byte[] iv) throws Exception{ AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); params.init(new IvParameterSpec(iv)); return params; } } 复制代码
위 내용은 WeChat 애플릿의 Java 로그인 프로세스를 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Java의 난수 생성기 안내. 여기서는 예제를 통해 Java의 함수와 예제를 통해 두 가지 다른 생성기에 대해 설명합니다.

Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

자바의 암스트롱 번호 안내 여기에서는 일부 코드와 함께 Java의 Armstrong 번호에 대한 소개를 논의합니다.

Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다
