微信扫描登录详解

本文详细介绍了微信扫描登录的实现步骤,包括配置微信开放平台的appid、appsecret和重定向url,利用Apache HttpClient生成二维码,解析JWT Token显示用户登录信息,并解决了跨域和内网穿透问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

微信扫描登录
第一步:准备工作 在application.properties添加相关配置信息

微信开放平台 appid

wx.open.app_id=你的appid

微信开放平台 appsecret

wx.open.app_secret=你的appsecret

微信开放平台 重定向url

wx.open.redirect_url=http://你的服务器名称/api/ucenter/wx/callback

创建util包,创建ConstantPropertiesUtil.java常量类
package com.guli.ucenter.util;

@Component
//@PropertySource(“classpath:application.properties”)
public class ConstantPropertiesUtil implements InitializingBean {

@Value("${wx.open.app_id}")
private String appId;

@Value("${wx.open.app_secret}")
private String appSecret;

@Value("${wx.open.redirect_url}")
private String redirectUrl;

public static String WX_OPEN_APP_ID;
public static String WX_OPEN_APP_SECRET;
public static String WX_OPEN_REDIRECT_URL;

@Override
public void afterPropertiesSet() throws Exception {
    WX_OPEN_APP_ID = appId;
    WX_OPEN_APP_SECRET = appSecret;
    WX_OPEN_REDIRECT_URL = redirectUrl;
}

}
第二步:先生成微信扫描的二维码
创建一个controller类添加注解要注意添加@Controller不能是RestController
//生成二维码的方法
@GetMapping(“login”)
public String genQrConnect() {
try {
//请求微信提供的固定的地址,向地址里面拼接参数,生成二维码
//微信提供固定的地址
// 1 微信开放平台授权baseUrl
// %s表示?,占位符,传递参数
String baseUrl = “https://open.weixin.qq.com/connect/qrconnect” +
“?appid=%s” +
“&redirect_uri=%s” +
“&response_type=code” +
“&scope=snsapi_login” +
“&state=%s” +//内网穿透前置域名,可以为空
“#wechat_redirect”;
//2 对重定向地址进行urlEncode编码
String redirectUrl = ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL;
redirectUrl = URLEncoder.encode(redirectUrl,“utf-8”);

					//3 把参数设置到路径里面
					baseUrl = String.format(
							baseUrl,
							ConstantPropertiesUtil.WX_OPEN_APP_ID,
							redirectUrl,
							);
					//重定向到地址中
					return "redirect:"+baseUrl;
				}catch(Exception e) {
					return null;
				}
			}

第三步:扫描之后,在首页面显示扫描人昵称,头像等信息
	1)先添加依赖
	<!--httpclient-->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.1</version>
		</dependency>
	<!--commons-io-->
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.6</version>
		</dependency>
	<!--gson-->
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>2.8.2</version>
		</dependency>
		

	2)创建httpclient工具类
		package com.guli.ucenter.utils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.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.HttpGet;
import org.apache.http.client.methods.HttpPost;
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.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
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;

/**

  • 依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
  • @author zhaoyb

*/
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;

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;
}



/**
 * 创建 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);
		//String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
        /*Map<String,String> map = new HashMap<String,String>();
        map.put("name", "111");
        map.put("page", "222");
        String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 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)UCenterMember:存成员的一些信息(例如openId,微信用户昵称,微信头像等)的实体类
	@Autowired
	private UcenterMemberService memberService;
	
	//扫描之后,调用这个方法
	@GetMapping("callback")
	public String callback (String code,String state ,HttpSession session){
		//得到授权临时票据code

// System.out.println("code = " + code);
// System.out.println("state = " + state);
//1 获取返回临时票据 code

    //2 拿着code请求微信提供的固定的地址
    String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
            "?appid=%s" +
            "&secret=%s" +
            "&code=%s" +
            "&grant_type=authorization_code";
    //设置参数
    baseAccessTokenUrl = String.format(
            baseAccessTokenUrl,
            ConstantPropertiesUtil.WX_OPEN_APP_ID,
            ConstantPropertiesUtil.WX_OPEN_APP_SECRET,
            code);
    try {
        //使用httpclient请求拼接之后的路径,获取openid和access_token
        String resultAccessToken = HttpClientUtils.get(baseAccessTokenUrl);
        //从返回字符串获取两个值
        Gson gson = new Gson();
        Map<String,Object> map = gson.fromJson(resultAccessToken, HashMap.class);
        String access_token = (String)map.get("access_token");//获取数据凭证
        String openid = (String)map.get("openid");//微信id

       //3 拿着access_token和openid请求固定的地址,取出用户的信息
        String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
                "?access_token=%s" +
                "&openid=%s";
        baseUserInfoUrl = String.format(
                baseUserInfoUrl,
                access_token,
                openid);
        //httpclient请求这个地址
        String resultUserInfo = HttpClientUtils.get(baseUserInfoUrl);
        Map<String,Object> userMap = gson.fromJson(resultUserInfo, HashMap.class);
        String nickname = (String)userMap.get("nickname");//昵称
        String headimgurl = (String)userMap.get("headimgurl");//头像

        //判断用户表是否已经存在添加用户信息,如果不存在,添加
        UcenterMember member = memberService.getOpenUserInfo(openid);
        if(member == null) {//不存在
            //添加
            member = new UcenterMember();
            member.setNickname(nickname);
            member.setAvatar(headimgurl);
            member.setOpenid(openid);

            memberService.save(member);
        }

        //回到首页面
        return "redirect:http://localhost:3000";
    }catch(Exception e) {

    }
    return null;
}



	4)业务接口:MemberService.java

	Member getOpenUserInfo(String openid);


	5)业务实现:MemberServiceImpl.java

	@Override
	public Member getByOpenid(String openid) {
	
	  QueryWrapper<Member> queryWrapper = new QueryWrapper<>();
	  queryWrapper.eq("openid", openid);
	
	  Member member = baseMapper.selectOne(queryWrapper);
	  return member;
	}

第四步整合JWT令牌
	1、加入jwt工具依赖
	在pom中添加
	<!-- JWT -->
	<dependency>
		 <groupId>io.jsonwebtoken</groupId>
		 <artifactId>jjwt</artifactId>
		 <version>0.7.0</version>
	</dependency>

	
	2)创建JWT工具类
	在util包中添加
package com.guli.ucenter.util;

import com.guli.ucenter.entity.Member;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import java.util.Date;

/**

  • jwt工具类
    */
    public class JwtUtils {

    public static final String SUBJECT = “guli”;

    //秘钥
    public static final String APPSECRET = “guli”;

    public static final long EXPIRE = 1000 * 60 * 30; //过期时间,毫秒,30分钟

    /**

    • 生成jwt token

    • @param member

    • @return
      */
      public static String geneJsonWebToken(Member member) {

      if (member == null || StringUtils.isEmpty(member.getId())
      || StringUtils.isEmpty(member.getNickname())
      || StringUtils.isEmpty(member.getAvatar())) {
      return null;
      }
      String token = Jwts.builder().setSubject(SUBJECT)
      .claim(“id”, member.getId())
      .claim(“nickname”, member.getNickname())
      .claim(“avatar”, member.getAvatar())
      .setIssuedAt(new Date())
      .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
      .signWith(SignatureAlgorithm.HS256, APPSECRET).compact();

      return token;
      }

    /**

    • 校验jwt token
    • @param token
    • @return
      */
      public static Claims checkJWT(String token) {
      Claims claims = Jwts.parser().setSigningKey(APPSECRET).parseClaimsJws(token).getBody();
      return claims;
      }

    //测试生成jwt token
    private static String testGeneJwt(){
    Member member = new Member();
    member.setId(“999”);
    member.setAvatar(“www.guli.com”);
    member.setNickname(“Helen”);

     String token = JwtUtils.geneJsonWebToken(member);
     System.out.println(token);
     return token;
    

    }

    //测试校验jwt token
    private static void testCheck(String token){

     Claims claims = JwtUtils.checkJWT(token);
     String nickname = (String)claims.get("nickname");
     String avatar = (String)claims.get("avatar");
     String id = (String)claims.get("id");
     System.out.println(nickname);
     System.out.println(avatar);
     System.out.println(id);
    

    }

    public static void main(String[] args){
    String token = testGeneJwt();
    testCheck(token);
    }
    }

     3、callback中生成jwt
     在callback方法的最后添加如下代码
     // 生成jwt
     String token = JwtUtils.geneJsonWebToken(member);
     
     //存入cookie
     //CookieUtils.setCookie(request, response, "guli_jwt_token", token);
     
     //因为端口号不同存在蛞蝓问题,cookie不能跨域,所以这里使用url重写
     return "redirect:http://localhost:3000?token=" + token;
    

    第五步 显示用户登录信息
    1、解析jwt token
    新建一个Controller

    • 根据对象生成jwt字符串
      public static String geneJsonWebToken(UcenterMember member) {

      if (member == null || StringUtils.isEmpty(member.getId())
      || StringUtils.isEmpty(member.getNickname())
      || StringUtils.isEmpty(member.getAvatar())) {
      return null;
      }

      String token = Jwts.builder().setSubject(SUBJECT)
      .claim(“id”, member.getId()) //向jwt设置主体部分数据
      .claim(“nickname”, member.getNickname())
      .claim(“avatar”, member.getAvatar())
      .setIssuedAt(new Date())//设置jwt字符串过期时间
      .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
      .signWith(SignatureAlgorithm.HS256, APPSECRET).compact();//根据秘钥对字符串进行加密,加密方式使用hs256

      return token;
      }

  • 根据jwt字符串,获取jwt字符串内容
    public static Claims checkJWT(String token) {
    Claims claims = Jwts.parser().setSigningKey(APPSECRET).parseClaimsJws(token).getBody();
    return claims;
    }

     //根据生成jwt的token值获取数据
      Claims claims = checkJWT(token);
      String id = (String)claims.get("id");
      String nickname = (String)claims.get("nickname");
      String avatar = (String)claims.get("avatar");
    
      System.out.println(id);
      System.out.println(nickname);
      System.out.println(avatar);
    

(3)整合jwt到项目具体功能里面

  • 找到扫描之后调用的方法,callback方法

  • 在callback方法根据member对象生成jwt的字符串,传递到首页面路径中
    //根据member对象生成jwt字符串
    String token = JwtUtils.geneJsonWebToken(member);

    //回到首页面
    return “redirect:http://localhost:3000?token=”+token;

(4)在页面具体操作

  • 第一获取路径中token值
    ** 在页面中使用代码 this.$route.query.token 获取路径中参数值

  • 第二调用后端接口,根据token值获取token值用户信息,把信息返回

  • 第三得到接口返回用户信息,把信息显示到页面中

☆☆☆ 总结
一、微信扫描登录
1、编写代码生成微信扫描二维码
(1)String.format()方法
(2) @Controller 和 @RestController 注解

2、编写代码实现扫描之后获取扫描人信息
(1)内网穿透
(2)httpclient
(3)跨域问题:三个地方访问协议、ip地址、端口号,有任何一个不一样就是跨域
跨域解决:
第一种,在controller添加注解 @CrossOrigin
第二种:使用httpclient
(4)Gson是json转换工具
json其他转换工具:jackson、fastjson、jsonlib

(5)JWT
* 根据对象生成jwt字符串
* 根据jwt字符串获取数据

3、扫描之后,首页面显示用户信息
this.$route.query.token :获取路由里面参数值 ?token=uyytyew

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值