微信扫码登录实现

一、准备工作

        1、​ ​https://open.weixin.qq.com​​​ 注册

        2、熟悉OAuth 2.0(授权机制)本次采用授权码模式

   

        3、熟悉获取access_token时序图:

二、后端开发

1、application.yml配置

wechat:
  APPID: 你的APPID
  APPSECRET: 你的APPSECRET
  REDIRECTURL: 你的REDIRECTURL
  enabled: false #密码校验是否开启

 2、微信二维码生成

/**
     * 生成微信扫描二维码
     * @return
     */
    @GetMapping("/wxUrl")
    @Functional(visible = Visible.FREEDOM)
    @ApiOperation(value = "生成微信扫描二维码")
    public ResultInfo<String> wxUrl() {
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";
        //对redirect_url进行URLEncoder编码
        String redirect_url = REDIRECTURL;
        try {
            redirect_url = URLEncoder.encode(redirect_url, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        String state = UUID.randomUUID().toString();
        //设置%s里面的值
        String url = String.format(baseUrl,APPID,redirect_url, state);
        //重定向到请求微信地址里面
        return new ResultInfo<>("redirect:"+url);
    }

    /**
     * 生成微信二维码
     * @return
     */
    @GetMapping("/wechatUrl")
    @Functional(visible = Visible.FREEDOM)
    @ApiOperation(value = "生成微信二维码")
    public ResultInfo getWxQRCodeParam() {
        HashMap<String, String> map = new HashMap<>();
        try {
            // 第一步:发送请求获取access_token
            String getAccessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
                    "&appid=" + APPID +
                    "&secret=" + APPSECRET;
            String accessTokenRes = HttpClientUtils.doGet(getAccessTokenUrl);
            String accessToken = (String) JSON.parseObject(accessTokenRes).get("access_token"); // 获取到access_token

            //生成uuid
            String state = UUID.randomUUID().toString();
            // 第二步:通过access_token和一些参数发送post请求获取二维码Ticket
            String getTicketUrl = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + accessToken;
            // 封装参数
            Map<String, Object> ticketInfo = new HashMap<>();
            ticketInfo.put("expire_seconds", 604800); // 二维码超时时间
            ticketInfo.put("action_name", "QR_STR_SCENE");
            // 放入uuid
            ticketInfo.put("action_info", new HashMap<String, HashMap>() {{
                        put("scene", new HashMap<String, String>() {{
                                    put("scene_str", state);
                                }}
                        );
                    }}
            );
            String ticketJsonInfo = JSON.toJSON(ticketInfo).toString();
            String ticketRes = HttpClientUtils.doPostJson(getTicketUrl, ticketJsonInfo);
            String ticket = (String) JSON.parseObject(ticketRes).get("ticket");
            // 第三步:通过ticket获取二维码url
            String encodeTicket = URLEncoder.encode(ticket, "utf-8"); // 编码ticket
            String getQRUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + encodeTicket;
            String QRUrl = getQRUrl; // 二维码url
            //存入map
            map.put("QRUrl",QRUrl);
            map.put("uuid",state);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ResultInfo<>(map);
    }

2.2、HttpClientUtils

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class HttpClientUtils {

  public static final int connTimeout=10000;
  public static final int readTimeout=10000;
  public static final String charset="UTF-8";
  private static HttpClient client = null;

  private static final CloseableHttpClient httpClient;

  // 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象
  static {
    RequestConfig config = RequestConfig.custom().setConnectTimeout(30000).setSocketTimeout(15000).build();
    httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
  }

  static {
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    cm.setMaxTotal(128);
    cm.setDefaultMaxPerRoute(128);
    client = HttpClients.custom().setConnectionManager(cm).build();
  }

  public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
  }

  public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
  }

  public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
      SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
  }

  public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
      SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
  }

  public static String get(String url) throws Exception {
    return get(url, charset, null, null);
  }

  public static String get(String url, String charset) throws Exception {
    return get(url, charset, connTimeout, readTimeout);
  }

  /**
   * 发送一个 Post 请求, 使用指定的字符集编码.
   *
   * @param url
   * @param body RequestBody
   * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
   * @param charset 编码
   * @param connTimeout 建立链接超时时间,毫秒.
   * @param readTimeout 响应超时时间,毫秒.
   * @return ResponseBody, 使用指定的字符集编码.
   * @throws ConnectTimeoutException 建立链接超时异常
   * @throws SocketTimeoutException  响应超时
   * @throws Exception
   */
  public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
      throws ConnectTimeoutException, SocketTimeoutException, Exception {
    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    String result = "";
    try {
      if (StringUtils.isNotBlank(body)) {
        HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
        post.setEntity(entity);
      }
      // 设置参数
      Builder customReqConf = RequestConfig.custom();
      if (connTimeout != null) {
        customReqConf.setConnectTimeout(connTimeout);
      }
      if (readTimeout != null) {
        customReqConf.setSocketTimeout(readTimeout);
      }
      post.setConfig(customReqConf.build());

      HttpResponse res;
      if (url.startsWith("https")) {
        // 执行 Https 请求.
        client = createSSLInsecureClient();
        res = client.execute(post);
      } else {
        // 执行 Http 请求.
        client = HttpClientUtils.client;
        res = client.execute(post);
      }
      result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
      post.releaseConnection();
      if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
        ((CloseableHttpClient) client).close();
      }
    }
    return result;
  }


  /**
   * 提交form表单
   *
   * @param url
   * @param params
   * @param connTimeout
   * @param readTimeout
   * @return
   * @throws ConnectTimeoutException
   * @throws SocketTimeoutException
   * @throws Exception
   */
  public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
      SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    try {
      if (params != null && !params.isEmpty()) {
        List<NameValuePair> formParams = new ArrayList<NameValuePair>();
        Set<Entry<String, String>> entrySet = params.entrySet();
        for (Entry<String, String> entry : entrySet) {
          formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
        post.setEntity(entity);
      }

      if (headers != null && !headers.isEmpty()) {
        for (Entry<String, String> entry : headers.entrySet()) {
          post.addHeader(entry.getKey(), entry.getValue());
        }
      }
      // 设置参数
      Builder customReqConf = RequestConfig.custom();
      if (connTimeout != null) {
        customReqConf.setConnectTimeout(connTimeout);
      }
      if (readTimeout != null) {
        customReqConf.setSocketTimeout(readTimeout);
      }
      post.setConfig(customReqConf.build());
      HttpResponse res = null;
      if (url.startsWith("https")) {
        // 执行 Https 请求.
        client = createSSLInsecureClient();
        res = client.execute(post);
      } else {
        // 执行 Http 请求.
        client = HttpClientUtils.client;
        res = client.execute(post);
      }
      return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
    } finally {
      post.releaseConnection();
      if (url.startsWith("https") && client != null
          && client instanceof CloseableHttpClient) {
        ((CloseableHttpClient) client).close();
      }
    }
  }




  /**
   * 发送一个 GET 请求
   *
   * @param url
   * @param charset
   * @param connTimeout  建立链接超时时间,毫秒.
   * @param readTimeout  响应超时时间,毫秒.
   * @return
   * @throws ConnectTimeoutException   建立链接超时
   * @throws SocketTimeoutException   响应超时
   * @throws Exception
   */
  public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
      throws ConnectTimeoutException,SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpGet get = new HttpGet(url);
    String result = "";
    try {
      // 设置参数
      Builder customReqConf = RequestConfig.custom();
      if (connTimeout != null) {
        customReqConf.setConnectTimeout(connTimeout);
      }
      if (readTimeout != null) {
        customReqConf.setSocketTimeout(readTimeout);
      }
      get.setConfig(customReqConf.build());

      HttpResponse res = null;

      if (url.startsWith("https")) {
        // 执行 Https 请求.
        client = createSSLInsecureClient();
        res = client.execute(get);
      } else {
        // 执行 Http 请求.
        client = HttpClientUtils.client;
        res = client.execute(get);
      }

      result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
      get.releaseConnection();
      if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
        ((CloseableHttpClient) client).close();
      }
    }
    return result;
  }


  /**
   * 从 response 里获取 charset
   *
   * @param ressponse
   * @return
   */
  @SuppressWarnings("unused")
  private static String getCharsetFromResponse(HttpResponse ressponse) {
    // Content-Type:text/html; charset=GBK
    if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
      String contentType = ressponse.getEntity().getContentType().getValue();
      if (contentType.contains("charset=")) {
        return contentType.substring(contentType.indexOf("charset=") + 8);
      }
    }
    return null;
  }


  /**
   * 发送 HTTP GET请求,不带请求参数和请求头
   * @param url 请求地址
   * @return
   * @throws Exception
   */
  public static String doGet(String url) throws Exception {
    HttpGet httpGet = new HttpGet(url);
    return doHttp(httpGet);
  }

  /**
   * 发送 HTTP GET,请求带参数,不带请求头
   * @param url 请求地址
   * @param params 请求参数
   * @return
   * @throws Exception
   */
  public static String doGet(String url, Map<String, Object> params) throws Exception {
    // 转换请求参数
    List<NameValuePair> pairs = covertParamsToList(params);
    // 装载请求地址和参数
    URIBuilder ub = new URIBuilder();
    ub.setPath(url);
    ub.setParameters(pairs);
    HttpGet httpGet = new HttpGet(ub.build());
    return doHttp(httpGet);
  }

  /**
   * 发送 HTTP GET请求,带请求参数和请求头
   * @param url 请求地址
   * @param headers 请求头
   * @param params 请求参数
   * @return
   * @throws Exception
   */
  public static String doGet(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {
    // 转换请求参数
    List<NameValuePair> pairs = covertParamsToList(params);
    // 装载请求地址和参数
    URIBuilder ub = new URIBuilder();
    ub.setPath(url);
    ub.setParameters(pairs);

    HttpGet httpGet = new HttpGet(ub.build());
    // 设置请求头
    for (Map.Entry<String, Object> param : headers.entrySet()) {
      httpGet.addHeader(param.getKey(), String.valueOf(param.getValue()));
    }
    return doHttp(httpGet);
  }

  /**
   * 发送 HTTP POST请求,不带请求参数和请求头
   *
   * @param url 请求地址
   * @return
   * @throws Exception
   */
  public static String doPost(String url) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    return doHttp(httpPost);
  }

  /**
   * 发送 HTTP POST请求,带请求参数,不带请求头
   *
   * @param url    请求地址
   * @param params 请求参数
   * @return
   * @throws Exception
   */
  public static String doPost(String url, Map<String, Object> params) throws Exception {
    // 转换请求参数
    List<NameValuePair> pairs = covertParamsToList(params);
    HttpPost httpPost = new HttpPost(url);
    // 设置请求参数
    httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));

    return doHttp(httpPost);
  }

  /**
   * 发送 HTTP POST请求,带请求参数和请求头
   *
   * @param url     地址
   * @param headers 请求头
   * @param params  参数
   * @return
   * @throws Exception
   */
  public static String doPost(String url, Map<String, Object> headers, Map<String, Object> params) throws Exception {
    // 转换请求参数
    List<NameValuePair> pairs = covertParamsToList(params);
    HttpPost httpPost = new HttpPost(url);
    // 设置请求参数
    httpPost.setEntity(new UrlEncodedFormEntity(pairs, StandardCharsets.UTF_8.name()));
    // 设置请求头
    for (Map.Entry<String, Object> param : headers.entrySet()) {
      httpPost.addHeader(param.getKey(), String.valueOf(param.getValue()));
    }
    return doHttp(httpPost);
  }

  /**
   * 发送 HTTP POST请求,请求参数是JSON格式,数据编码是UTF-8
   *
   * @param url 请求地址
   * @param param 请求参数
   * @return
   * @throws Exception
   */
  public static String doPostJson(String url, String param) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    // 设置请求头
    httpPost.addHeader("Content-Type", "application/json; charset=UTF-8");
    // 设置请求参数
    httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));
    return doHttp(httpPost);
  }



  /**
   * 发送 HTTP POST请求,请求参数是XML格式,数据编码是UTF-8
   *
   * @param url 请求地址
   * @param param 请求参数
   * @return
   * @throws Exception
   */
  public static String doPostXml(String url, String param) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    // 设置请求头
    httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");
    // 设置请求参数
    httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));

    return doHttp(httpPost);
  }

  /**
   * 发送 HTTPS POST请求,使用指定的证书文件及密码,不带请求头信息<
   *
   * @param url 请求地址
   * @param param 请求参数
   * @param path 证书全路径
   * @param password 证书密码
   * @return
   * @throws Exception
   * @throws Exception
   */
  public static String doHttpsPost(String url, String param, String path, String password) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    // 设置请求参数
    httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));

    return doHttps(httpPost, path, password);
  }

  /**
   * 发送 HTTPS POST请求,使用指定的证书文件及密码,请求头为“application/xml;charset=UTF-8”
   *
   * @param url 请求地址
   * @param param 请求参数
   * @param path 证书全路径
   * @param password 证书密码
   * @return
   * @throws Exception
   * @throws Exception
   */
  public static String doHttpsPostXml(String url, String param, String path, String password) throws Exception {
    HttpPost httpPost = new HttpPost(url);
    // 设置请求头
    httpPost.addHeader("Content-Type", "application/xml; charset=UTF-8");
    // 设置请求参数
    httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8.name()));
    return doHttps(httpPost, path, password);
  }

  /**
   * 发送 HTTPS 请求,使用指定的证书文件及密码
   *
   * @param request
   * @param path 证书全路径
   * @param password 证书密码
   * @return
   * @throws Exception
   * @throws Exception
   */
  private static String doHttps(HttpRequestBase request, String path, String password) throws Exception {
    // 获取HTTPS SSL证书
    SSLConnectionSocketFactory csf = getHttpsFactory(path, password);
    // 通过连接池获取连接对象
    CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
    return doRequest(httpClient, request);
  }

  /**
   * 获取HTTPS SSL连接工厂,使用指定的证书文件及密码
   *
   * @param path     证书全路径
   * @param password 证书密码
   * @return
   * @throws Exception
   * @throws Exception
   */
  private static SSLConnectionSocketFactory getHttpsFactory(String path, String password) throws Exception {

    // 初始化证书,指定证书类型为“PKCS12”
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    // 读取指定路径的证书
    FileInputStream input = new FileInputStream(new File(path));
    try {
      // 装载读取到的证书,并指定证书密码
      keyStore.load(input, password.toCharArray());
    } finally {
      input.close();
    }
    // 获取HTTPS SSL证书连接上下文
    SSLContext sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()).build();
    // 获取HTTPS连接工厂,指定TSL版本
    SSLConnectionSocketFactory sslCsf = new SSLConnectionSocketFactory(sslContext, new String[]{"SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.2"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    return sslCsf;
  }
  /**
   * 发送 HTTP 请求
   *
   * @param request
   * @return
   * @throws Exception
   */
  private static String doHttp(HttpRequestBase request) throws Exception {
    // 通过连接池获取连接对象
    return doRequest(httpClient, request);
  }

  /**
   * 处理Http/Https请求,并返回请求结果,默认请求编码方式:UTF-8
   * @param httpClient
   * @param request
   * @return
   */
  private static String doRequest(CloseableHttpClient httpClient, HttpRequestBase request) throws Exception {
    String result = null;
    try (CloseableHttpResponse response = httpClient.execute(request)) {
      // 获取请求结果
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode != 200) {
        request.abort();
        throw new RuntimeException("HttpClient error status code: " + statusCode);
      }
      // 解析请求结果
      HttpEntity entity = response.getEntity();
      // 转换结果
      result = EntityUtils.toString(entity, StandardCharsets.UTF_8.name());
      // 关闭IO流
      EntityUtils.consume(entity);
    }
    return result;
  }

  /**
   * 转换请求参数,将Map键值对拼接成QueryString字符串
   *
   * @param params
   * @return
   */
  public static String covertMapToQueryStr(Map<String, Object> params) {
    List<NameValuePair> pairs = covertParamsToList(params);
    return URLEncodedUtils.format(pairs, StandardCharsets.UTF_8.name());
  }

  /**
   * 转换请求参数
   *
   * @param params
   * @return
   */
  public static List<NameValuePair> covertParamsToList(Map<String, Object> params) {
    List<NameValuePair> pairs = new ArrayList<>();
    for (Map.Entry<String, Object> param : params.entrySet()) {
      pairs.add(new BasicNameValuePair(param.getKey(), String.valueOf(param.getValue())));
    }
    return pairs;
  }



  /**
   * 创建 SSL连接
   * @return
   * @throws GeneralSecurityException
   */
  private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
    try {
      SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
        public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
          return true;
        }
      }).build();

      SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

        @Override
        public boolean verify(String arg0, SSLSession arg1) {
          return true;
        }

        @Override
        public void verify(String host, SSLSocket ssl)
            throws IOException {
        }

        @Override
        public void verify(String host, X509Certificate cert)
            throws SSLException {
        }

        @Override
        public void verify(String host, String[] cns,
                   String[] subjectAlts) throws SSLException {
        }

      });

      return HttpClients.custom().setSSLSocketFactory(sslsf).build();

    } catch (GeneralSecurityException e) {
      throw e;
    }
  }

  public static void main(String[] args) {
    try {
      String str= post("https://localhost:443/ssl/test.shtml","name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
      System.out.println(str);
    } catch (ConnectTimeoutException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (SocketTimeoutException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}

3、接入微信接口callback:

/**
     *  接入微信接口
     *  @return
     */
    @GetMapping("/wechat/callback")
    @Functional(visible = Visible.FREEDOM)
    @ApiOperation(value = "接入微信接口")
    public String checkSign (HttpServletRequest request){
        // 获取微信请求参数
        String signature = request.getParameter ("signature");
        String timestamp = request.getParameter ("timestamp");
        String nonce = request.getParameter ("nonce");
        String echostr = request.getParameter ("echostr");
        if (CheckWXTokenUtils.checkSignature(signature, timestamp, nonce)) {
            return echostr;
        }
        return " ";
    }

3.2、CheckWXTokenUtils

import lombok.extern.log4j.Log4j2;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
 
/**
 * 和微信建立链接参数校验
 */
@Log4j2
public class CheckWXTokenUtils {
    private static final String TOKEN = "你定义的token"; // 自定义的token
 
    /**
     * 校验微信服务器Token签名
     *
     * @param signature 微信加密签名
     * @param timestamp 时间戳
     * @param nonce     随机数
     * @return boolean
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        //将token、timestamp、nonce三个参数进行字典序排序
        String[] arr = {TOKEN, timestamp, nonce};
        Arrays.sort(arr);
        //将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuilder stringBuilder = new StringBuilder();
        for (String param : arr) {
            stringBuilder.append(param);
        }
        String hexString = SHA1(stringBuilder.toString());
        //获得加密后的字符串可与signature对比,标识该请求来源于微信
        return signature.equals(hexString);
    }
 
    private static String SHA1(String str) {
        MessageDigest md;
        try {
            md = MessageDigest.getInstance("SHA-1");
            byte[] digest = md.digest(str.getBytes());
            return toHexString(digest);
        } catch (NoSuchAlgorithmException e) {
            log.info("校验令牌Token出现错误:{}", e.getMessage());
        }
        return "";
    }
 
    /**
     * 字节数组转化为十六进制
     *
     * @param digest 字节数组
     * @return String
     */
    private static String toHexString(byte[] digest) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : digest) {
            String shaHex = Integer.toHexString(b & 0xff);
            if (shaHex.length() < 2) {
                hexString.append(0);
            }
            hexString.append(shaHex);
        }
        return hexString.toString();
    }
}

4、微信扫码登录

4.1、controller

/**
     * 微信扫码登录
     * @return
     **/
    @GetMapping("/wechat/wechatLogin/{code}")
    @Functional(visible = Visible.FREEDOM)
    @ApiOperation(value = "微信扫码登录")
    public ResultInfo wechatLogin(@PathVariable("code") String code,String state){
        ResultInfo wxlogin = userService.wechatLogin(code,state);
        return new ResultInfo<>(wxlogin);
    }

4.2、service

 @Override
    public ResultInfo wechatLogin(String code, String state) {
        try {
            //获取到code值,拿着code去请求微信固定的地址,得到两个值 access_token和openid
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";
            //拼接好的地址,得到两个值 access_token和openid
            String accessTokenUrl = String.format(baseAccessTokenUrl,APPID,APPSECRET,code);
            //使用httpclient发送请求,得到返回结果
            String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
            //从accessTokenInfo字符串里面获取access_token和openid两个值
            //把accessTokenInfo字符串转换成map集合,根据Map里面的key获取对应值
            //使用json转换工具 Gson
            Gson gson=new Gson();
            HashMap mapAccessToken = gson.fromJson(accessTokenInfo, HashMap.class);
            String access_token = (String) mapAccessToken.get("access_token");
            String openid = (String) mapAccessToken.get("openid");
            //拿着得到的access_token和openid,再去请求微信提供的固定地址,获取扫码人的信息
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                    "?access_token=%s" +
                    "&openid=%s";
            String userInfoUrl= String.format(baseUserInfoUrl,access_token,openid);
            //访问微信的资源服务器,获取用户信息
            String userInfo = HttpClientUtils.get(userInfoUrl);
            //获取返回userInfo中的用户信息(扫码人信息)
            HashMap userInfoMap = gson.fromJson(userInfo, HashMap.class);
            String nickname = (String) userInfoMap.get("nickname"); //昵称
            String headimgurl = (String) userInfoMap.get("headimgurl"); //头像
            //判断数据库里面是否存在相同的微信信息,根据openid判断
            MemberInfo openId = memberInfoMapper.selectByOpenid(openid);
            //openId是null,表示表里面没有相同的微信数据
            //第一种可能 首次扫码
            if(openId == null){
                //把扫码人信息添加到数据库里面
                MemberInfo memberInfo = new MemberInfo();
                memberInfo.setOpenId(openid);
                memberInfo.setMemberName(nickname);
                MemberDetail memberDetail = new MemberDetail();
                memberDetail.setUserName(nickname);
                memberDetail.setPortrait(headimgurl);
                //添加
                memberInfoMapper.insert(memberInfo);
                memberDetailMapper.insert(memberDetail);
                return new ResultInfo<>("500","手机号为空",openid);
            }
            //第二种可能  二次扫码  首次未手机号注册
            if(openId.getMemberId() == null){
                return new ResultInfo<>("500","手机号为空",openId.getOpenId());
            }
            //第三种可能  二次及以上扫码  微信用户绑定了系统用户
            MemberInfo memberInfo = memberInfoMapper.selectById(openId.getMemberId());
            LoginDto loginDto = new LoginDto();
            loginDto.setUserName(memberInfo.getMemberName());

            //根据用户名去查询数据库
            MemberInfo memberInfoName =  memberInfoMapper.selectByName(loginDto.getUserName());
            if(memberInfoName == null){
                // 没查到 抛异常
                throw ErrCodeMsgEnum.ACCOUNT_PWD_NOT_COORECT.err();
            }
            if(enabled){
                //true 开启密码校验   false 关闭密码校验
                String password = loginDto.getPassword();
                String salt = memberInfo.getSalt();
                String saltPassword = SecureUtil.md5(salt + password);
                if(!memberInfo.getMemberPwd().equals(saltPassword)){
                    throw ErrCodeMsgEnum.ACCOUNT_PWD_NOT_COORECT.err();
                }
            }
            // 生成token
            AuthParam authParam = new AuthParam();
            String token = jwtHelper.general(authParam);
            // 保存redis
            redisTemplate.opsForValue().set(token , memberInfo.toString() ,30, TimeUnit.MINUTES);
            // 返回 token 和 用户信息
            Map<String,Object> map = new HashMap<>(2);
            map.put("token",token);
            //清空密码返回 保证安全
            memberInfo.setMemberPwd("");
            memberInfo.setSalt("");
            map.put("wxUser",memberInfo);
            return new ResultInfo<>(map);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResultInfo<>("500","内部错误");
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值