java微信小程序登录、解密

一。流程说明

https://developers.weixin.qq.com/miniprogram/dev/api/api-login.html 官方有个文档

简单来说,就是小程序端执行wx.login()方法获取临时登录凭证code,带着code访问我们的接口,接口用code访问微信的一个接口得到openid和秘钥session_key;

小程序端可以调用wx.getUserInfo这个方法 进行微信授权,用户点击同意授权后,可以获得一些用户数据,比如省市,性别等等,但其中一些数据是加密的。需要我们服务端写接口解密,解密就需要用到秘钥session_key

二。实现

我只写了一个接口,参数有code和敏感信息,通过临时登录凭证code获取openid,和秘钥,再通过敏感信息和秘钥解密。

1.用到的pom

 <!-- JSON -->
	   <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.28</version>
        </dependency>
        
         <!-- 微信解密 -->
		<dependency>
		    <groupId>org.bouncycastle</groupId>
		    <artifactId>bcprov-jdk16</artifactId>
		    <version>1.46</version>
		</dependency>
		 <dependency>
		    <groupId>commons-codec</groupId>
		    <artifactId>commons-codec</artifactId>
		    <version>1.4</version>
		</dependency>
		<dependency> 
		    <groupId>net.sf.json-lib</groupId> 
		    <artifactId>json-lib</artifactId> 
		    <version>2.2.3</version> 
		    <classifier>jdk15</classifier> 
		</dependency>

2.小程序端

需要自己下载微信开发者工具,工具中有自己的appid和secret(后面要用到),在项目设置中,调整成不校验url,

调用wx.login() 成功后再调用wx.getUserInfo 用户同意后,再携带得到的数据访问我们的接口

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: 'http://localhost:8080/dl/weixinlogin',
                url: 'http://192.168.1.108:8080/dl/weixinlogin',
                data: {
                  code: loginRes.code,//临时登录凭证
                  rawData: infoRes.rawData,//用户非敏感信息
                  signature: infoRes.signature,//签名
                  encrypteData: infoRes.encryptedData,//用户敏感信息
                  iv: infoRes.iv,//解密算法的向量
                  appId:'写你自己的appid'
                },
                success: function (res) {
                 
                },
                fail: function (error) {
                  //调用服务端登录接口失败
                  // that.showInfo('调用接口失败');
                  console.log(error);
                }
              });
            }
          });
        } else {

        }
      }
    });
  },

3.接口

在其中得到了openid和加密数据,可以自己打印出来

	@ApiImplicitParams({ 
		@ApiImplicitParam(name = "code", value = "code", dataType = "string", paramType = "query"),
		@ApiImplicitParam(name = "rawData", value = "非敏感信息\"", dataType = "string", paramType = "query"),
		@ApiImplicitParam(name = "signature", value = "签名", dataType = "string", paramType = "query"),
		@ApiImplicitParam(name = "encrypteData", value = "敏感加密信息", dataType = "string", paramType = "query"),
		@ApiImplicitParam(name = "iv", value = "偏移向量", dataType = "string", paramType = "query"),
		@ApiImplicitParam(name = "appId", value = "appId", dataType = "string", paramType = "query")
	})
	@ApiOperation(value="微信登录获取用户微信信息",notes="微信登录获取用户微信信息")
	@ApiResponses(value = { @ApiResponse(code = 200, message = "Success") })
	@RequestMapping(value="/weixinlogin",method=RequestMethod.GET)
	public Message<String> doLogin(
                  @RequestParam(value = "code",required = false) String code,
                  @RequestParam(value = "rawData",required = false) String rawData,
                  @RequestParam(value = "signature",required = false) String signature,
                  @RequestParam(value = "encrypteData",required = false) String encrypteData,
                  @RequestParam(value = "iv",required = false) String iv,
                  @RequestParam(value="appId",required=true)String appId){
	 
	    JSONObject rawDataJson = JSON.parseObject( rawData );
	    JSONObject SessionKeyOpenId = getSessionKeyOrOpenId( code );
	    String openid = SessionKeyOpenId.getString("openid" );
	    String sessionKey = SessionKeyOpenId.getString( "session_key" );
	    //解密敏感信息
	    String decrypt = WXCore.decrypt(appId, encrypteData, sessionKey, iv);
	    JSONObject userInfo = JSON.parseObject(decrypt);
	    
	
	    return Message.ok(decrypt);

	}

访问微信接口 得到openid和秘钥session_key。  需要写自己开发者工具的appid和secret

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
	    requestUrlParam.put( "secret","  " );
	    requestUrlParam.put( "js_code",wxCode );//小程序端返回的code
	    requestUrlParam.put( "grant_type","authorization_code" );//默认参数
	 
	    //发送post请求读取调用微信接口获取openid用户唯一标识
	    JSONObject jsonObject = JSON.parseObject( UrlUtil.sendPost( requestUrl,requestUrlParam ));
	    return jsonObject;
	}

用到的工具类:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

/**
 * 
 * @author Melo
 *
 */
public class UrlUtil {
	private static Logger log = Logger.getLogger(UrlUtil.class);

	
    /**
	 * 向指定 URL 发送POST方法的请求
	 * 
	 * @param url 发送请求的 URL
	 * @param param 请求参数
	 * @return 所代表远程资源的响应结果
	 */
	public static String sendPost(String url, Map<String, ?> paramMap) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        
        String param = "";
		Iterator<String> it = paramMap.keySet().iterator();
		
		while(it.hasNext()) {
			String key = it.next();
			param += key + "=" + paramMap.get(key) + "&";
		}
		
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
        	log.error(e.getMessage(), e);
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
	}
	/**
     * 向指定URL发送GET方法的请求
     * 
     * @param url
     *            发送请求的URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     * 
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!"+e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }    
}
package com.jubao.dling.common.util.weixin;


import org.apache.commons.codec.binary.Base64;
 
import net.sf.json.JSONObject;
 
 
/**
 * 封装对外访问方法
 * @author liuyazhuang
 *
 */
public class WXCore {
	
	private static final String WATERMARK = "watermark";
	private static final String APPID = "appid";
	/**
	 * 解密数据
	 * @return
	 * @throws Exception
	 */
	public static String decrypt(String appId, String encryptedData, String sessionKey, String iv){
		String result = "";
		try {
			AES aes = new AES();  
		    byte[] resultByte = aes.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));  
		    if(null != resultByte && resultByte.length > 0){  
		        result = new String(WxPKCS7Encoder.decode(resultByte));  
		    	JSONObject jsonObject = JSONObject.fromObject(result);
		    	String decryptAppid = jsonObject.getJSONObject(WATERMARK).getString(APPID);
		    	if(!appId.equals(decryptAppid)){
		    		result = "";
		    	}
	        }  
		} catch (Exception e) {
			result = "";
			e.printStackTrace();
		}
	    return result;
	}
	
	
	public static void main(String[] args) throws Exception{
	   String appId = "wx4f4bc4dec97d474b";
	   String encryptedData = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew==";
	   String sessionKey = "tiihtNczf5v6AKRyjwEUhQ==";
	   String iv = "r7BXXKkLb8qrSNn05n0qiA==";
       System.out.println(decrypt(appId, encryptedData, sessionKey, iv));
    }
}
package com.jubao.dling.common.util.weixin;

import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
 
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
 
/**
 * AES加密
 * @author liuyazhuang
 *
 */
public class 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;
	}
}
package com.jubao.dling.common.util.weixin;

import java.nio.charset.Charset;
import java.util.Arrays;
 
 
/**
 * 微信小程序加解密
 * @author liuyazhuang
 *
 */
public class WxPKCS7Encoder {
	private static final Charset CHARSET = Charset.forName("utf-8");
	private static final int BLOCK_SIZE = 32;
 
	/**
	 * 获得对明文进行补位填充的字节.
	 *
	 * @param count
	 *            需要进行填充补位操作的明文字节个数
	 * @return 补齐用的字节数组
	 */
	public static byte[] encode(int count) {
		// 计算需要填充的位数
		int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
		if (amountToPad == 0) {
			amountToPad = BLOCK_SIZE;
		}
		// 获得补位所用的字符
		char padChr = chr(amountToPad);
		String tmp = new String();
		for (int index = 0; index < amountToPad; index++) {
			tmp += padChr;
		}
		return tmp.getBytes(CHARSET);
	}
 
	/**
	 * 删除解密后明文的补位字符
	 *
	 * @param decrypted
	 *            解密后的明文
	 * @return 删除补位字符后的明文
	 */
	public static byte[] decode(byte[] decrypted) {
		int pad = decrypted[decrypted.length - 1];
		if (pad < 1 || pad > 32) {
			pad = 0;
		}
		return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
	}
 
	/**
	 * 将数字转化成ASCII码对应的字符,用于对明文进行补码
	 *
	 * @param a
	 *            需要转化的数字
	 * @return 转化得到的字符
	 */
	public static char chr(int a) {
		byte target = (byte) (a & 0xFF);
		return (char) target;
	}
}

猜你喜欢

转载自blog.csdn.net/u014203449/article/details/81261472
今日推荐