集成微信扫描登录,实现第三方的登录功能

现今使用微信登录第三方网站已成为潮流,这样免去了注册的繁琐步骤,因此,本篇博客就简单记录下springboot集成微信扫描登录的步骤。

目  录

1、前言

2、编写配置类将appid等信息注入

3、获取扫描二维码的方法

4、获取微信个人信息

5、总结


1、前言

        使用微信扫描登录功能,要去官网申请开发者资质,要通过申请才能获取到appid、appsecret,申请完后要设置回调链接(用于设置扫码登录确认后)。

        具体开发文档可参考官方开发文档网址

集成微信扫描登录,实现第三方的登录功能_第1张图片

        整个调用过程可以通过图示描述:

  1.  第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据code参数;
  2. 通过code参数加上AppID和AppSecret等,通过API换取access_token;
  3. 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。

    集成微信扫描登录,实现第三方的登录功能_第2张图片

2、编写配置类将appid等信息注入

@Component
//@PropertySource("classpath:application.properties")
public class ConstantProperties 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_APP_ID;
    public static String WX_APP_SECRET;
    public static String WX_REDIRECT_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
        WX_APP_ID=this.appID;
        WX_APP_SECRET=this.appSecret;
        WX_REDIRECT_URL=this.redirectUrl;
    }

}

application.properties文件

# 微信开放平台 appid
wx.open.app_id=wxed9954c01b写你自己的
# 微信开放平台 appsecret
wx.open.app_secret=a7482517235173ddb4083788de写你自己的
# 微信开放平台 重定向url
wx.open.redirect_url=http://写你自己的/ucenter/wx/callback

3、获取扫描二维码的方法

        可参考上面给出的官方文档

@Controller //注意这里没有配置 @RestController
@CrossOrigin
@RequestMapping("/api/ucenter/wx")
public class WxApiController {

    //生成微信扫描二维码,   %s 相当于?占位符
    @GetMapping("/login")
    public String getWxCode(){
        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_uri进行URLEncoder编码
        String redirect_uri = ConstantProperties.WX_REDIRECT_URL;
        try {
            redirect_uri = URLEncoder.encode(redirect_uri, "utf-8");//参数1:待编码字符串 参数2:编码方式
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        //设置 %s 占位符的参数,上面有3处
        String url = String.format(baseUrl,
                ConstantProperties.WX_APP_ID,
                redirect_uri,
                "guli");


        //请求微信地址
        return "redirect:" + url;
    }
}

4、获取微信个人信息

  1. 通过扫描二维码并确认登录后,会进行回调填写的回调链接,为进行顺利进行,现在后端编写一个回调方法用于获取code凭证。
  2. 拿着code凭证+appid、secret通过HttpClient技术访问固定网址,得到返回值,通过gson转换工具,将json转换为map集合获取到access_token。
  3. 拿着access_token和appid使用HttpClient访问固定网址得到微信的个人信息(昵称头像等)。

        现给出我的逻辑代码,可以作为初步参考:

 @Autowired
    private UcenterMemberService ucenterMemberService;

    @GetMapping("/callback")
    public String callback(String code, String state, HttpSession session){
        //获取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";

        //拼接三个参数:id 秘钥 和 code值
        String accessTokenUrl = String.format(baseAccessTokenUrl,
                ConstantProperties.WX_APP_ID
                , ConstantProperties.WX_APP_SECRET
                , code);


        //请求上面拼接好的地址,得到两个值 access_token 和 openid
        //使用httpclient【不用浏览器,也能模拟器出浏览器的请求和响应过程】发送请求,得到返回的结果
        String accessTokenInfo = null;
        try {
            accessTokenInfo = HttpClientUtils.get(accessTokenUrl);
            System.out.println("accessTokenInfo:。。。" +accessTokenInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //从accessTokenInfo中获取出  access_token 和 openid 的值
        //将 accessTokenInfo 转换成 map集合,根据map的key 就可以获取对应的value
        //使用json转换工具
        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");

        //判断数据库是否存在相同的微信内容
        UcenterMember member = ucenterMemberService.getMemberByOpenId(openid);

        //如果没有,则为新用户,添加数据库
        if (member == null){

            //拿着 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 resultUserInfo = null;
            try {
                resultUserInfo = HttpClientUtils.get(userInfoUrl);
                System.out.println(resultUserInfo);
            } catch (Exception e) {
                e.printStackTrace();
            }
            HashMap resultMap = gson.fromJson(resultUserInfo, HashMap.class);

            String nickname = (String) resultMap.get("nickname");
            String headimgurl = (String) resultMap.get("headimgurl");

            //向数据库插入一条记录
            member = new UcenterMember();
            member.setNickname(nickname);
            member.setAvatar(headimgurl);
            member.setOpenid(openid);
            ucenterMemberService.save(member);
        }

        //使用jwt根据member对象生成token字符串
        String jwtToken = JwtUtils.getJwtToken(member.getId(), member.getNickname());

        //最后,返回首页,通过路径传递token字符串
        return "redirect:http://localhost:3000?token="+jwtToken;
    }

        注意:以上操作前,要引入Gson依赖,以及编写HttpClient依赖,上面代码中的ucenterMemberService操作为将个人信息添加到的数据库中。

        下面给出两个工具类HttpClientUtils和JwtUtils的逻辑代码:

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
 * HttpClient 可以模拟浏览器的请求响应
 *
 */
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 params) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
		return postForm(url, params, null, connTimeout, readTimeout);
	}

	public static String postParameters(String url, Map 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 params, Map headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {

		HttpClient client = null;
		HttpPost post = new HttpPost(url);
		try {
			if (params != null && !params.isEmpty()) {
				List formParams = new ArrayList();
				Set> entrySet = params.entrySet();
				for (Entry 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 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 map = new HashMap();
            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();
		}
	}

}
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

public class JwtUtils {
    //使用前提请先导包jjtw包

    //token过期时间
    public static final long EXPIRE = 1000 * 60 * 60 * 24;

    //秘钥,每个公司生成规则不一样
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    //生成token字符串方法
    public static String getJwtToken(String id, String nickname) {
        String JwtToken = Jwts.builder()
                //设置jwt头信息,红色部分,内容固定,不需要改
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                .setSubject("guli-user")
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))//设置过期时间

                //设置token主体部分,存储用户信息,可设置多个值
                .claim("id", id)
                .claim("nickname", nickname)

                //设置签名哈希(防伪标志)
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();

        return JwtToken;
    }

    /**
     * 判断token是否存在与有效
     *
     * @param jwtToken
     * @return
     */
    public static boolean checkToken(String jwtToken) {
        if (StringUtils.isEmpty(jwtToken)) return false;
        try {
            //根据设置的防伪码解析token
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     *
     * @param request
     * @return
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if (StringUtils.isEmpty(jwtToken)) return false;
            //根据设置的防伪码解析token
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     *
     * @param request
     * @return
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if (StringUtils.isEmpty(jwtToken)) return "";
        //根据设置的防伪码解析token,获取对象
        Jws claimsJws =
                Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        //获取token有效载荷【用户信息】
        Claims claims = claimsJws.getBody();
        return (String) claims.get("id");
    }


}

5、总结

        以上就是整个集成过程,其中可能复杂点在回调网址那部分,仔细阅读官网文档和代码,理解其中逻辑,你肯定没有问题。

学习之所以会想睡觉,是因为那是梦开始的地方。
ଘ(੭ˊᵕˋ)੭ (开心) ଘ(੭ˊᵕˋ)੭ (开心)ଘ(੭ˊᵕˋ)੭ (开心)ଘ(੭ˊᵕˋ)੭ (开心)ଘ(੭ˊᵕˋ)੭ (开心)
                                                                                                        ------不写代码不会凸的小刘
 

你可能感兴趣的:(谷粒学院在线教育项目,Springboot,Java,微信,java,后端,spring,boot)