Java实现微信登陆授权流程详解

Java实现微信登陆授权流程详解

前期准备工作就不多说了,无非就是公众号平台账号、填写相关资耐心等待审核就好。

等通过之后,就可以看到测试用的 appId 和 appSecret 了,稍后我们要用到这两个 ID
哈哈哈

接下来这里有一点要注意,在网址应用创建好之后的授权回调域填写顶级域名就好,微信文档里说的是,该域名下的所有页面都可以回调

在这里插入图片描述
微信开放平台第三方登录接口文档地址

代码实现微信授权

首先,我们要导入一些依赖,这里由于我当时找过好几个版本,依赖可能有些是多余的,都放在这里了QAQ

		<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>2.1.4</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.74</version>
		</dependency>
		<dependency>
			<groupId>commons-httpclient</groupId>
			<artifactId>commons-httpclient</artifactId>
			<version>3.1</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk16</artifactId>
			<version>1.46</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>

		<dependency>
			<groupId>org.codehaus.xfire</groupId>
			<artifactId>xfire-core</artifactId>
			<version>1.2.6</version>
		</dependency>

		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk16</artifactId>
			<version>1.46</version>
		</dependency>

然后是是工具类:

解密手机号:GetUserInfoUtil

package com.ruoyi.common.utils;

import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

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 java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;

public class GetUserInfoUtil {
    
    

    public static String  getUserInfo(String encryptedData, String sessionKey, String iv) {
    
    
        String result = "";
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
    
    
            // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
    
    
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            // 初始化
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
    
    
                result = new String(resultByte, "UTF-8");
            }
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        } catch (BadPaddingException e) {
    
    
            e.printStackTrace();
        } catch (InvalidKeyException e) {
    
    
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
    
    
            e.printStackTrace();
        } catch (InvalidParameterSpecException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
    
    
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
    
    
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
    
    
            e.printStackTrace();
        }
        return result;
    }
}

解析微信接口:

package com.ruoyi.project.system.applet;

import org.apache.commons.httpclient.HttpStatus;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;

/**
 1. HttpUtil工具类
 */
public class HttpUtil {
    
    

    public static String doGet(String urlPath, HashMap<String, Object> params)
            throws Exception {
    
    
        StringBuilder sb = new StringBuilder(urlPath);
        if (params != null && !params.isEmpty()) {
    
     // 说明有参数
            sb.append("?");

            Set<Entry<String, Object>> set = params.entrySet();
            for (Entry<String, Object> entry : set) {
    
     // 遍历map里面的参数
                String key = entry.getKey();
                String value = "";
                if (null != entry.getValue()) {
    
    
                    value = entry.getValue().toString();
                    // 转码
                    value = URLEncoder.encode(value, "UTF-8");
                }
                sb.append(key).append("=").append(value).append("&");
            }

            sb.deleteCharAt(sb.length() - 1); // 删除最后一个&
        }
        // System.out.println(sb.toString());
        URL url = new URL(sb.toString());
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(5000); // 5s超时
        conn.setRequestMethod("GET");

        if (conn.getResponseCode() == HttpStatus.SC_OK) {
    
    // HttpStatus.SC_OK ==
            // 200
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    conn.getInputStream()));
            StringBuilder sbs = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
    
    
                sbs.append(line);
            }
            // JSONObject jsonObject = new JSONObject(sbs.toString());
            return sbs.toString();
        }

        return null;
    }
}

接下来,就开始我们的表演了

首先,我们要通过前端给我们传的 code ,来调取接口获得自己需要的信息:

1、获取session_key

要获取用户的手机号,需要用到 session_key,那么这个 session_key 怎么获得呢,请看

扫描二维码关注公众号,回复: 12427875 查看本文章
@RestController
@RequestMapping("/login")
@Api(value = "微信登录模块")
public class AppletLogin implements Serializable{
    
    
    private static final Logger log = LoggerFactory.getLogger(AppletLogin.class);

    @Autowired
    private ILoggerUserService loggerUserService;

    /**
     * 登录
     * @param code
     * @return
     */
    @ApiOperation(value = "登录")
    @PostMapping("/appletLogin")
    public AjaxResult appletLogin(@Param("code")@RequestParam String code)
    {
    
    
        //校验请求参数
        if(StringUtils.isEmpty(code)){
    
    
            return AjaxResult.error("请求参数存在空值");
        }

        String AppId = "前面获取的AppId";
        String AppSecret = "前面获取的AppSecret";

        //请求微信服务器,用code换取openid、sessionKey
        String result="";
        try{
    
    
            result = HttpUtil.doGet(
                    "https://api.weixin.qq.com/sns/jscode2session?appid="
                            + AppId + "&secret="
                            + AppSecret + "&js_code="
                            + code
                            + "&grant_type=authorization_code", null);
        }
        catch (Exception e) {
    
    
            e.printStackTrace();
        }

        //解析从微信服务器上获取到的json字符串
        JSONObject jsonObj = JSONObject.parseObject(result);
        if(jsonObj == null){
    
    
            return AjaxResult.error("服务器请求微信接口获取参数失败");
        }
        //封装返回的数据
        HashMap<String,String> returnHashMap = new HashMap<>();
        String sessionKey = jsonObj.get("session_key").toString();
        String openId = jsonObj.get("openid").toString();

        //GetUserInfoUtil.getUserInfo()
        returnHashMap.put("sessionKey",sessionKey);
        returnHashMap.put("openId",openId);

        return AjaxResult.success("用户信息",returnHashMap);
    }
}

这里的 AppId、AppSecret 都是一开始就知道的,我们直接放到里面,用前端传的 code 值,调用接口,返回 sessionKey、openId,这里的两个值我们下面都要用到。

2、解密手机号

解密手机号,我们要用到三个参数,sessionKey、encryptedData、iv,已知 sessionKey 我们有了,那么剩下两个呢?别急,这两个参数是前端给我们传的,我们直接拿过来用就可以了

/**
     * 根据opendid 添加用户手机号
     * @param sessionKey
     * @param encryptedData
     * @param iv
     * @return
     */
    @ApiOperation(value = "添加用户手机号")
    @GetMapping ("/phone")
    public AjaxResult phone(String sessionKey, String encryptedData, String iv){
    
    

        if(StringUtils.isEmpty(sessionKey) || StringUtils.isEmpty(encryptedData) || StringUtils.isEmpty(iv)){
    
    
            return AjaxResult.error("请求参数存在空值");
        }
        //  微信小程序--手机号解密
        String json = AES.wxDecrypt(encryptedData, sessionKey, iv);
        //json:{"phoneNumber":"17865181175","purePhoneNumber":"17865181175","countryCode":"86","watermark":{"timestamp":1587460231,"appid":"wxe493cddc6e0d931c"}}
        if(StringUtils.isEmpty(json)){
    
    
            return AjaxResult.error("未获取到手机号");
        }
        Map maps = (Map)JSON.parse(json);
        String phoneNumber = (String)maps.get("phoneNumber");

        //返回用户信息
        HashMap<String,Object> returnHashMap = new HashMap<>();
        returnHashMap.put("phoneNumber",phoneNumber);
        return AjaxResult.success("用户信息",returnHashMap);
    }

注意,这里的 AES.wxDecrypt() 方法,所用到的工具类就是我们前面有提到的,解密手机号的工具类,当然了,也可以直接写在接口里,我只是封装了一下,不然代码看起来就比较乱了。

到了这一步,用户的手机号,我们就拿到了,大功告成!

3、那么用户信息呢?

用户的昵称、头像、地址等个人信息(不包括手机号),这些我们怎么拿到呢?这里就不得不说一下微信比较坑的地方了,以前没有改版的时候,通过上面的方法,我们可以直接获取到用户的个人信息和手机号,现在不行了,要么就按上面的步骤只能拿手机号,要么就按下面的步骤,拿用户信息,请看

@RestController
@RequestMapping("/login")
@Api(value = "微信登录模块")
public class AppletLogin implements Serializable{
    
    
    private static final Logger log = LoggerFactory.getLogger(AppletLogin.class);

    @Autowired
    private ILoggerUserService loggerUserService;

    /**
     * 登录
     * @param code
     * @return
     */
    @ApiOperation(value = "登录")
    @PostMapping("/appletLogin")
    public AjaxResult appletLogin(@Param("accessToken")@RequestParam String accessToken, @Param("openId")@RequestParam String openId)
    {
    
    
        //校验请求参数
        if(StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)){
    
    
            return AjaxResult.error("请求参数存在空值");
        }

        //请求微信服务器,用code换取openid
        String result="";
        try{
    
    
            result = HttpUtil.doGet(
                    "https://api.weixin.qq.com/sns/userinfo?access_token="
                            + accessToken + "&openid="
                            + openId + "&lang=zh_CN", null);
        }
        catch (Exception e) {
    
    
            e.printStackTrace();
        }

        //解析从微信服务器上获取到的json字符串
        JSONObject userinfo= JSONObject.parseObject(result);
        if(userinfo== null){
    
    
            return AjaxResult.error("服务器请求微信接口获取参数失败");
        }
        /*{   
		  "openid":" OPENID",
		  "nickname": NICKNAME,
		  "sex":"1",
		  "province":"PROVINCE",
		  "city":"CITY",
		  "country":"COUNTRY",
		  "headimgurl":"https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
		  "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
		  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
		}*/

        return AjaxResult.success("用户信息",userinfo);
    }
}

这是返回的参数信息
在这里插入图片描述
这里有些小伙伴就要问了,openId 我知道,是通过 code 拿到的,那这个 accessToken 是怎么获取的呢? 坑来了!!!accessToken 需要 code 通过调用另一个接口获得,那么在已知,code 只能使用一次的情况下,我们怎么通过 code 又获取 sessionKey,又获取 accessToken 呢?

不要慌,我有完美的解决方案:那就让用户授权两次呗!

让前端通过第二次授权,去获取用户的头像、昵称,然后传给我们,我们在根据手机号、openId,对用户的信息的唯一性进行确认,并存入数据库中。

4、保存用户数据

/**
     * 获取用户信息
     * @return
     */
    @ApiOperation(value = "获取用户信息")
    @PostMapping("/getUserInfo")
    public AjaxResult getUserInfo(@ApiParam("用户手机号") @RequestParam String phoneNumber,
                                  @ApiParam("openId") @RequestParam String openId,
                                  @ApiParam("微信昵称")@RequestParam String nickname,
                                  @ApiParam("头像路径")@RequestParam String headimgurl,
                                  @ApiParam(value = "上级业务员id") @RequestParam(required = false)Integer id) {
    
    
        if(StringUtils.isEmpty(phoneNumber) || StringUtils.isEmpty(openId) || StringUtils.isEmpty(nickname) || StringUtils.isEmpty(headimgurl)){
    
    
            return AjaxResult.error("请求参数存在空值");
        }

        //去数据库判断是否存在该用户
        LoggerUser loggerUser = loggerUserService.selectLoggerUserByOpenId(openId);

        //如果存在该用户
        if(StringUtils.isNotNull(loggerUser)){
    
    
            //一、用户是客户经理
            CemManager manager = managerService.selectCemManagerByMobile(phoneNumber);
            if (StringUtils.isNotNull(manager)){
    
    //设置客户经理id
                loggerUser.setHold2(manager.getManagerId().toString());
            }
            //二、用户有上级业务员,且开启
            if (id!=null){
    
    
                CemManager byId = managerService.selectCemManagerById(id.longValue());
                if (StringUtils.isNotNull(byId)){
    
    //设置上级业务员id
                    if (byId.getState()==0){
    
    
                        loggerUser.setHold1(byId.getManagerId().toString());
                    }
                    //三、用户有上级业务员,且关闭
                }
            }
            //四、用户用户有上级业务员,但已删除
            //五、用户无上级业务员

            if(StringUtils.isNotEmpty(loggerUser.getMobile())){
    
    
                //将用户手机号覆盖
                loggerUser.setMobile(phoneNumber);
            }
            //昵称
            loggerUser.setWxName(nickname);
            //图像路径
            loggerUser.setHold4(headimgurl);
            //保存用户信息
            loggerUserService.updateLoggerUser(loggerUser);
            return AjaxResult.success(loggerUser);
        }

        //如果是新用户,就添加用户到数据库中
        LoggerUser newLoggerUser = new LoggerUser();

        //一、用户是客户经理
        CemManager manager = managerService.selectCemManagerByMobile(phoneNumber);
        if (StringUtils.isNotNull(manager)){
    
    //设置客户经理id
            newLoggerUser.setHold2(manager.getManagerId().toString());
        }
        //二、用户有上级业务员,且开启
        if (id!=null){
    
    
            CemManager byId = managerService.selectCemManagerById(id.longValue());
            if (StringUtils.isNotNull(byId)){
    
    //设置上级业务员id
                if (byId.getState()==0){
    
    
                    newLoggerUser.setHold1(byId.getManagerId().toString());
                }
                //三、用户有上级业务员,且关闭
            }
        }
        //四、用户用户有上级业务员,但已删除
        //五、用户无上级业务员

        //openId
        newLoggerUser.setHold3(openId);
        //昵称
        newLoggerUser.setWxName(nickname);
        //手机号
        newLoggerUser.setMobile(phoneNumber);
        //图像路径
        newLoggerUser.setHold4(headimgurl);
        //新增用户信息
        loggerUserService.insertLoggerUserInfo(newLoggerUser);
        return AjaxResult.success(newLoggerUser);
    }

现在,我们就在用户登陆授权后,获取了一条完整的用户信息,包括手机号、昵称、头像等个人信息,当然了,可别忘记这是需要用户授权两次的,上面的步骤,就按一、二、四来就可以了,记得要跟前端小伙伴沟通好哦!


好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。

猜你喜欢

转载自blog.csdn.net/Cike___/article/details/109354988