java springboot+redis实现微信扫码登录

微信扫码登录网站

  • 代码篇
    • maven
    • application.yml
    • WeChatProperties.java
    • SHA1.java
    • WeChatUtils.java
    • WeChatService.java
    • ApiWeChatController.java
  • 详细讲解篇
    • 官方文档
      • 获取access_token
      • 生成带参数的二维码
      • 长链接转短连接
      • 接收事件推送-扫描带参数二维码事件
      • 微信公众平台接口调试工具
    • 微信公众号配置
  • 测试
  • 有待后续更新

代码篇

maven

		
        <dependency>
            <groupId>org.dom4jgroupId>
            <artifactId>dom4jartifactId>
            <version>2.1.1version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-websocketartifactId>
            <version>2.3.3.RELEASEversion>
        dependency>

application.yml

#微信配置
wechat:
  appId: 开发者id
  appSecret: 开发者秘钥
  token: 自定义token,例如:mytoken
  accessTokenUrl: https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s
  ticketUrl: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s
  qrCodeUrl: https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s
  shortUrl: https://api.weixin.qq.com/cgi-bin/shorturl?access_token=%s

WeChatProperties.java

package com.qcn.common.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 读取微信配置文件
 */
@Component
@ConfigurationProperties("wechat")
public class WeChatProperties {
     
    /**
     * 开发者id
     */
    private String appId;

    /**
     * 开发者密码
     */
    private String appSecret;

    /**
     * 微信公众号校验自定义设置的token
     */
    private String token;

    /**
     * 获取accessToken的url
     */
    private String accessTokenUrl;

    /**
     * 获取ticket的url
     */
    private String ticketUrl;

    /**
     * 获取二维码url
     */
    private String qrCodeUrl;

    /**
     * 长链接转短连接url
     */
    private String shortUrl;

    public String getAppId() {
     
        return appId;
    }

    public void setAppId(String appId) {
     
        this.appId = appId;
    }

    public String getAppSecret() {
     
        return appSecret;
    }

    public void setAppSecret(String appSecret) {
     
        this.appSecret = appSecret;
    }

    public String getToken() {
     
        return token;
    }

    public void setToken(String token) {
     
        this.token = token;
    }

    public String getAccessTokenUrl() {
     
        return accessTokenUrl;
    }

    public void setAccessTokenUrl(String accessTokenUrl) {
     
        this.accessTokenUrl = accessTokenUrl;
    }

    public String getTicketUrl() {
     
        return ticketUrl;
    }

    public void setTicketUrl(String ticketUrl) {
     
        this.ticketUrl = ticketUrl;
    }

    public String getQrCodeUrl() {
     
        return qrCodeUrl;
    }

    public void setQrCodeUrl(String qrCodeUrl) {
     
        this.qrCodeUrl = qrCodeUrl;
    }

    public String getShortUrl() {
     
        return shortUrl;
    }

    public void setShortUrl(String shortUrl) {
     
        this.shortUrl = shortUrl;
    }
}

SHA1.java

package com.qcn.framework.api.wechat;

import java.security.MessageDigest;

/**
 * 加密
 */
public class SHA1 {
     

    private static final char[] HEX_DIGITS = {
     '0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    private static String getFormattedText(byte[] bytes) {
     
        int len = bytes.length;
        StringBuilder buf = new StringBuilder(len * 2);
        for (int j = 0; j < len; j++) {
     
            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
        }
        return buf.toString();
    }

    public static String encode(String str) {
     
        if (str == null) {
     
            return null;
        }
        try {
     
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.update(str.getBytes());
            return getFormattedText(messageDigest.digest());
        } catch (Exception e) {
     
            throw new RuntimeException(e);
        }
    }
}

WeChatUtils.java

package com.qcn.framework.api.wechat;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WeChatUtils {
     
    private static final Logger log = LoggerFactory.getLogger(WeChatUtils.class);
    /**
     * 向指定 URL 发送GET方法的请求
     * @param url 发送请求的完整URL,应该是 http://127.0.0.1?name1=value1&name2=value2 的形式
     * @return
     */
    public static String sendGet(String url)
    {
     
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try
        {
     
            log.info("开始get请求: {}", url);
            URL realUrl = new URL(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();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
            String line;
            while ((line = in.readLine()) != null)
            {
     
                result.append(line);
            }
            log.info("get请求成功 : {}", result);
        }
        catch (ConnectException e)
        {
     
            log.error("调用WeChatUtils.sendGet ConnectException, url=" + url, e);
        }
        catch (SocketTimeoutException e)
        {
     
            log.error("调用WeChatUtils.sendGet SocketTimeoutException, url=" + url, e);
        }
        catch (IOException e)
        {
     
            log.error("调用WeChatUtils.sendGet IOException, url=" + url, e);
        }
        catch (Exception e)
        {
     
            log.error("调用WeChatUtils.sendGet Exception, url=" + url, e);
        }
        finally
        {
     
            try
            {
     
                if (in != null)
                {
     
                    in.close();
                }
            }
            catch (Exception ex)
            {
     
                log.error("调用in.close Exception, url=" + url, ex);
            }
        }
        return result.toString();
    }

    /**
     * 向指定 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;
        StringBuilder result = new StringBuilder();
        try
        {
     
            String urlNameString = url;
            log.info("开始post请求: {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            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)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
            String line;
            while ((line = in.readLine()) != null)
            {
     
                result.append(line);
            }
            log.info("post请求成功 : {}", result);
        }
        catch (ConnectException e)
        {
     
            log.error("调用WeChatUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
     
            log.error("调用WeChatUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
     
            log.error("调用WeChatUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
     
            log.error("调用WeChatUtils.sendPost Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
     
            try
            {
     
                if (out != null)
                {
     
                    out.close();
                }
                if (in != null)
                {
     
                    in.close();
                }
            }
            catch (IOException ex)
            {
     
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

    /**
     * xml解析
     * @param request
     * @return
     * @throws Exception
     */
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
     
        Map<String, String> map = new HashMap<>();
        InputStream inputStream = request.getInputStream();
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        Element root = document.getRootElement();
        List<Element> elementList = root.elements();
        for (Element e : elementList)
            map.put(e.getName(), e.getText());
        inputStream.close();
        return map;
    }

    /**
     * 校验签名
     * @param signature
     * @param timestamp
     * @param nonce
     * @param token
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {
     
        String[] str = new String[]{
     token, timestamp, nonce};
        //排序
        Arrays.sort(str);
        //拼接字符串
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < str.length; i++) {
     
            buffer.append(str[i]);
        }
        //进行sha1加密
        String temp = SHA1.encode(buffer.toString());
        //与微信提供的signature进行匹对
        return signature.equals(temp);
    }
}

WeChatService.java

package com.qcn.framework.api.wechat;

import com.alibaba.fastjson.JSONObject;
import com.qcn.common.config.WeChatProperties;
import com.qcn.common.constant.CardConstants;
import com.qcn.common.core.redis.RedisCache;
import com.qcn.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.concurrent.TimeUnit;

/**
 * 微信服务处理
 */
@Component
public class WeChatService {
     
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private WeChatProperties weChatProperties;

    /**
     * 获取AccessToken,写入redis,比官方失效时间expires_in提前10分钟
     *
     * @return
     */
    public String getAccessToken() {
     
        String accessToken = redisCache.getCacheObject(CardConstants.ACCESS_TOKEN_KEY);
        if (StringUtils.isBlank(accessToken)) {
     
            String url = String.format(weChatProperties.getAccessTokenUrl(), weChatProperties.getAppId(), weChatProperties.getAppSecret());
            String result = WeChatUtils.sendGet(url);
            JSONObject jsonObject = JSONObject.parseObject(result);
            accessToken = jsonObject.getString("access_token");
            Integer expiresIn = jsonObject.getInteger("expires_in");
            redisCache.setCacheObject(CardConstants.ACCESS_TOKEN_KEY, accessToken, expiresIn - 600, TimeUnit.SECONDS);
        }
        return accessToken;
    }

    /**
     * 获取二维码地址url,写入redis,比官方失效时间expires_in提前10分钟
     * @return
     */
    public String getQrCodeUrl() {
     
        String qrCodeUrl = redisCache.getCacheObject(CardConstants.QR_CODE_URL_KEY);
        if (StringUtils.isBlank(qrCodeUrl)) {
     
            //----获取的二维码ticket----
            String accessToken = getAccessToken();
            String url = String.format(weChatProperties.getTicketUrl(), accessToken);
            //{"expire_seconds": 604800, "action_name": "QR_SCENE", "action_info": {"scene": {"scene_id": 123}}}
            JSONObject params = new JSONObject();
            params.put("expire_seconds", 604800);//二维码有效时间(秒),最大不能超过30天
            params.put("action_name", "QR_SCENE");//二维码类型,QR_SCENE为临时的整型参数值
            JSONObject action_info = new JSONObject();//二维码详细信息
            JSONObject scene = new JSONObject();
            scene.put("scene_id", 7);//场景值ID,临时二维码时为32位非0整型
            action_info.put("scene", scene);
            params.put("action_info", action_info);
            String result = WeChatUtils.sendPost(url, params.toJSONString());
            JSONObject jsonObject = JSONObject.parseObject(result);
            String ticket = jsonObject.getString("ticket");
            Integer expireSeconds = jsonObject.getInteger("expire_seconds");//该二维码有效时间,以秒为单位
            //---凭借此ticket可以在有效时间内换取二维码, https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET,TICKET记得进行UrlEncode
            try {
     
                String encodeTicket = URLEncoder.encode(ticket, "UTF-8");
                String longUrl = String.format(weChatProperties.getQrCodeUrl(), encodeTicket);
                qrCodeUrl= getShortUrl(accessToken,longUrl);
                redisCache.setCacheObject(CardConstants.QR_CODE_URL_KEY,qrCodeUrl, expireSeconds - 600, TimeUnit.SECONDS);
            } catch (UnsupportedEncodingException e) {
     
                e.printStackTrace();
            }
        }
        return qrCodeUrl;
    }
    private String getShortUrl(String accessToken, String longUrl) {
     
        if (StringUtils.isNotBlank(accessToken) && StringUtils.isNotBlank(longUrl)) {
     
            String url = String.format(weChatProperties.getShortUrl(), accessToken);
            JSONObject params = new JSONObject();
            params.put("action", "long2short");//固定值
            params.put("long_url", longUrl);
            String result = WeChatUtils.sendPost(url, params.toJSONString());
            JSONObject jsonObject = JSONObject.parseObject(result);
            if (jsonObject.getInteger("errcode") == 0) {
     
                return jsonObject.getString("short_url");
            }
        }
        return longUrl;
    }
}

ApiWeChatController.java

package com.qcn.web.controller.api;

import com.qcn.common.config.WeChatProperties;
import com.qcn.framework.api.wechat.WeChatService;
import com.qcn.framework.api.wechat.WeChatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Map;

/**
 * 微信相关接口
 */
@RestController
@RequestMapping("/weChat")
public class ApiWeChatController {
     

    private static final Logger log = LoggerFactory.getLogger(ApiWeChatController.class);
    @Autowired
    private WeChatProperties weChatProperties;
    @Autowired
    private WeChatService weChatService;

    /**
     * token校验
     *
     * @param request
     * @param response
     * @throws UnsupportedEncodingException
     */
    @GetMapping("/msgCallback")
    public void login(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
     
        log.debug("开始校验token……");
        request.setCharacterEncoding("UTF-8");
        String signature = request.getParameter("signature");//微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
        String timestamp = request.getParameter("timestamp");//时间戳
        String nonce = request.getParameter("nonce");//随机数
        String echoStr = request.getParameter("echostr");//随机字符串
        PrintWriter out = null;
        try {
     
            out = response.getWriter();
            if (WeChatUtils.checkSignature(signature, timestamp, nonce, weChatProperties.getToken())) {
     
                out.write(echoStr);//校验成功原数据返回
            }
        } catch (IOException e) {
     
            e.printStackTrace();
        } finally {
     
            out.close();
        }
    }

    /**
     * 消息回调
     *
     * @param request
     * @throws Exception
     */
    @PostMapping("/msgCallback")
    public void scanCodeCallback(HttpServletRequest request) throws Exception {
     
        log.debug("微信消息回调……");
        request.setCharacterEncoding("UTF-8");
        Map<String, String> map = WeChatUtils.parseXml(request);
        log.info("回参map : {}", map);
        String toUserName = map.get("ToUserName");//开发者微信号
        String openId = map.get("FromUserName");//发送方帐号(一个OpenID)
        String createTime = map.get("CreateTime");//消息创建时间
        String msgType = map.get("MsgType");//消息类型,event
        String event = map.get("Event");//事件类型,未关注:subscribe;已关注:SCAN
        String eventKey = map.get("EventKey");//未关注:事件KEY值,qrscene_为前缀,后面为二维码的参数值;已关注:事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
        String ticket = map.get("Ticket");//二维码的ticket,可用来换取二维码图片
        if ("subscribe".equals(event)) {
     
            //1. 用户未关注时,进行关注后的事件推送
            //业务逻辑处理
            log.info("新关注");
        } else if ("SCAN".equals(event)) {
     
            //2. 用户已关注时的事件推送
            //业务逻辑处理
            log.info("已关注");
        }
    }

    /**
     * 获取二维码
     * @return
     */
    @GetMapping("/getQrCodeUrl")
    public String getQrCodeUrl() {
     
        log.debug("开始获取二维码url……");
        String url = weChatService.getQrCodeUrl();
        log.debug("二维码url:" + url);
        return url;
    }
}

详细讲解篇

官方文档

先吐槽一下,做过微信对接的人,看微信文档应都是口吐芬芳吧…………………………

获取access_token

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

生成带参数的二维码

https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html

长链接转短连接

https://developers.weixin.qq.com/doc/offiaccount/Account_Management/URL_Shortener.html

接收事件推送-扫描带参数二维码事件

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html

微信公众平台接口调试工具

http://mp.weixin.qq.com/debug/

微信公众号配置

微信扫码登录有两种方式:
1.微信扫一扫直接登录(这里是通过微信开放平台实现,请看另一篇博文)
2.微信扫一扫关注公众号登录(这里通过微信公众号实现,就是这篇博文的实现方式)

正确配置最终图:

java springboot+redis实现微信扫码登录_第1张图片
一步一步操作:
java springboot+redis实现微信扫码登录_第2张图片
服务器配置:

  1. 启用
  2. 修改配置
  1. URL:项目的部署地址+controller的token校验方法路由,也就是token校验的完整访问路径
  2. Token:自定义字符,例如mytoken,一定要与项目yml配置保持一致即可
  3. EncodingAESKey:用默认的随机生成即可
  4. 加解密方式:明文(在熟练之后再改为加密吧)

java springboot+redis实现微信扫码登录_第3张图片

测试

由于买的域名已被上线项目占用,所以买了内网穿透natapp 官网,可以直接在本地测试

获取二维码:http://你的域名/weChat/getQrCodeUrl

java springboot+redis实现微信扫码登录_第4张图片

展示二维码,微信客户端扫一扫,关注公众号微信回调

java springboot+redis实现微信扫码登录_第5张图片

有待后续更新

  1. 微信回调后实际业务处理
  2. 接入WebSocket,实现前后端分离通信

你可能感兴趣的:(springboot,java,微信对接,java,spring,boot,后端)