SSM框架下的微信登录

主宰稳场 提交于 2020-01-31 14:07:36

typora-root-url: D:\编程项目\typora文档\图片目录

SSM框架下的微信登录

首先看官方给出的登录过程,然后按步骤一个个来

img

1.获取CODE

首先用微信提供的方法获取code,使用wx.login()方法,还顺便封装了一个微信请求的发起方法

这里封装了俩个方法,在调用登录的时候异步执行

//获取code换session
  getCode(){
    var that=this;
    //回调函数
    return new Promise(function(resolve, reject){
      wx.login({
          success(res) {
              console.log('code:'+res.code);
              //把获取的code存到本地data以便发送到服务器
              that.data.loginData.code = res.code;
              resolve();
          }
      })
    })
  },
  //微信请求封装
  getRequest(url,data){
    var that=this;
    return new Promise(function(resolve,reject){
      wx.request({
        url: 'http://localhost/BookKeeping/'+url,
        //请求数据
        data: data,
        method: 'POST',
        //token验证时携带的数据(暂时不需要)
        header: {
          Authorization: that.data.token
        }, 
        success: function(res){
          resolve(res)
        },
        fail: function() {
          reject()
        },
        complete: function() {
          //complete
        }
      })
    })
  },
//这样调用就可以把code发出去
  userLogin(e){
    console.log(e);
    var that=this;
    this.getCode()
    .then(()=>{
      console.log(this.data.loginData);
      return this.getRequest("api/login",this.data.loginData.code)           
    })
  }

下面就交给服务器处理吧

2.服务器处理发送过来的Code

服务器处理时,我们需要一个工具,向微信服务器发送我们的请求数据

请求的数据内容如下

在这里插入图片描述

请求的地址是

GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

那后端该怎么发送这个请求呢?

在这之前,需要导入maven依赖,以支持发起请求对应的包

<!--Https请求依赖-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.3</version>
        </dependency>

在原先的ssm框架下,新建一个包,用来存放我们的工具类

这次使用到的是发送http请求的工具,HttpUtil.java

package com.BookKeeping.util;

import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpUtil {

    public static String doGet(String uri) {
        //1:创建一个HttpClient的实例
        CloseableHttpClient httpclient = HttpClients.createDefault();
        //2:创建一个get请求实例
        HttpGet httpGet = new HttpGet(uri);
        //请求的响应:
        CloseableHttpResponse response1 = null;
        try {
            //3:使用HttpClient的实例执行get请求
            response1 = httpclient.execute(httpGet);
            //http请求的状态:404 500 200
            //System.out.println(response1.getStatusLine());
            int statusCode = response1.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                //请求成功:
                HttpEntity entity1 = response1.getEntity();
                String result = EntityUtils.toString(entity1, "utf-8");
                return result;
            } else {
                //请求失败
                System.out.println("请求失败......:"+statusCode);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public JSONObject domain(String type, String code){
        JSONObject jo = JSONObject.parseObject("{'message':'Error',}");
        if(type.equals("getSession_key")){
            System.out.println("获取session和openid");
            String result=doGet("https://api.weixin.qq.com/sns/jscode2session?appid=自己的appid&secret=自己的secret&js_code="+code+"&grant_type=authorization_code");
            jo = JSONObject.parseObject(result);
            return jo;
        }else if(type.equals("getUserData")){
            System.out.println("获取用户信息");
        }
        return jo;
    }
}

上面封装的domain方法可以按照传入的方法和code,返回一个请求结果(因为微信返回结果是一个json格式)

这里把返回结果直接转json了

请求结果示例:

在这里插入图片描述

3.控制器层处理

直接贴代码

    @RequestMapping(value = "/login",method = RequestMethod.POST)
    @ResponseBody				//使返回的数据生成json格式
    public Result login(@RequestBody String code){
        Result rs=new Result();	 //自定义的结果返回值(详细查看上一篇)
        Login result=new Login();//登录数据的实体类,存放登录信息等
        
        System.out.println(code);
        //获取微信session和生成自定义token
        HttpUtil hrs=new HttpUtil();

        //获取session_key和openid
        JSONObject session_key=hrs.domain("getSession_key",code);
        String session=session_key.getString("session_key");
        String openid=session_key.getString("openid");

        result.setSession(session);
	   //把参数放入
        rs.setData(result);
        return rs;
    }

Login的实体类,注意要右键Generate自动生成get和set和tostring,详见上篇

package com.BookKeeping.entity;

public class Login {
    private String encryptedData;

    private String iv;

    private String code;

    private String token;

    private String session;
}

到这里,我们可以在在微信开发者工具测试这个接口了:

在这里插入图片描述

注意这里的session要存起来,微信官方说是有20分钟有效期,后面要使用这个来解密数据

4.获取用户的加密数据

要获取用户加密数据,需要以下数据:

在这里插入图片描述

小程序前端调用wx.getuserinfo获得,其中的 加密数据 encrytedData加密向量iv需要存起来,给服务器解密

对应的js代码为:

  //获取用户数据
  getUserInfo() {
    var that=this;		//否则wx方法内无法访问this
    return new Promise(function(resolve, reject){
      wx.getUserInfo({
        success: function(e) {
          console.log(e)
          //存储相关数据
          app.globalData.userInfo = e.userInfo
          that.data.loginData.iv=e.iv;
          that.data.loginData.encryptedData=e.encryptedData;
          resolve();
        }
      })  
    }) 
  },

调用登录的函数改写为下面:

  //用户登录操作
  userLogin(e){
    console.log(e);
    var that=this;
    this.getCode()
    .then(()=>{
      return this.getUserInfo()
    })
    .then(()=>{
      console.log(this.data.loginData);
      return this.getRequest("api/login",this.data.loginData.code)           
    })
    .then((res)=>{
      console.log(res)
      that.data.token=res.data.data.token;
      that.data.loginData.code=res.data.data.session;
      return this.getRequest("api/getUserData",this.data.loginData)
      
    }).then((res)=>{
      that.data.userInfo=res.data.data;
      that.data.hasUserInfo=true;
    })  
  },

这下,后端就会收到前端发来的三个数据:

  1. 加密数据
  2. 加密向量
  3. session_key

有了这些,我们就开始解密操作

5.解密数据,回发

在这之前,需要导入maven依赖,以支持解密对应的包

<!--微信AES-128-CBC解密依赖-->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk16</artifactId>
            <version>1.46</version>
        </dependency>
        <dependency>
            <groupId>org.codehaus.xfire</groupId>
            <artifactId>xfire-core</artifactId>
            <version>1.2.6</version>
        </dependency>

同上,导入一个加解密的工具类Aes.java

package com.BookKeeping.common;

import com.alibaba.fastjson.JSONObject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;


import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;

/**
 * AES加解密算法
 */

public class Aes {
    public static String KEY_ALGORITHM = "AES";
    //数据填充方式
    String algorithmStr = "AES/CBC/PKCS7Padding";
    //避免重复new生成多个BouncyCastleProvider对象,因为GC回收不了,会造成内存溢出
    //只在第一次调用decrypt()方法时才new 对象
    public static boolean initialized = false;

    /**
     * AES加密
     */
    public byte[] encrypt(byte[] originalContent, byte[] encryptKey, byte[] ivByte) {
        initialize();
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec skeySpec = new SecretKeySpec(encryptKey, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(ivByte));
            byte[] encrypted = cipher.doFinal(originalContent);
            return encrypted;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * AES解密
     * 填充模式AES/CBC/PKCS7Padding
     * 解密模式128
     */
    public String decrypt(String encryptedData, String sessionKey, String ivs) {
        initialize();
        //转换为byte类型以解密
        byte[] content= Base64.decode(encryptedData);
        byte[] aesKey= Base64.decode(sessionKey);
        byte[] ivBytes= Base64.decode(ivs);
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
            Key sKeySpec = new SecretKeySpec(aesKey, "AES");
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivBytes));// 初始化
            byte[] result = cipher.doFinal(content);

            return new String(result);
        } catch (Exception e) {
            System.out.println("解密失败");
            throw new RuntimeException(e);
        }
        //return "Error";
    }

    /**BouncyCastle作为安全提供,防止加密解密时候因为jdk内置的不支持改模式运行报错。**/
    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;
    }

    public JSONObject domain(String encryptedData, String sessionKey, String ivs){
        JSONObject jo = JSONObject.parseObject(decrypt(encryptedData, sessionKey, ivs));
        return jo;
    }
}

同上,domain为解密的具体实现,返回的同样被转换为json格式

接下来控制层这样写

    @RequestMapping(value = "/getUserData",method = RequestMethod.POST)
    public Result getUserData(@RequestBody Login login, @RequestHeader("Authorization") String token){
        System.out.println("获取用户数据");

        Result rs=new Result();
        //调用服务层处理
        User us=loginService.getUserData(login.getEncryptedData(),login.getCode(),login.getIv());
        System.out.println(us.toString());
        rs.setData(us);
        return rs;
    }

服务层

    @Override
    public User getUserData(String EncryptedData,String session,String ivs) {
        Aes aes=new Aes();
        User us=new User();		//具体实体类参考上一篇

        //使用session_key获取用户数据
        JSONObject userData=aes.domain(EncryptedData,session,ivs);
        us.setAvatarUrl(userData.getString("avatarUrl"));
        us.setGender(userData.getInteger("gender"));
        us.setNickName(userData.getString("nickName"));
        us.setOpenId(userData.getString("openId"));
        return us;
    }

至此,解密工作完成,可以看到这接口返回如下数据

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!