一、微信官方文档
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
二、准备工作
1)一个域名
● 自己备案申请一个域名
● 使用内网穿透工具
2)微信公众平台测试号配置
① 配置JS接口安全域名(不要带http)
② 配置网页授权回调页面域名(不要带http)
③ 扫码关注测试号
3)获取公众平台测试号的 AppID 和 AppSecret
三、授权步骤
1.第三方发起微信授权登录请求,微信用户允许授权第三方应用后,微信会拉起应用或重定向到第三方网站,并且带上授权临时票据 code 参数;
2.通过 code 参数加上 AppID 和 AppSecret 等,通过 API 换取 access_token;
3.通过 access_token 进行接口调用,获取用户基本数据资源或帮助用户实现基本操作。
四、实例
- 项目结构
使用 SpringBoot 构建项目
1)Maven依赖
<!--用于对JSON格式的数据进行解析和打包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<!--支持 HTTP 通讯,实现了所有 HTTP 的方法等-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--Thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2)配置application.yaml文件
# 端口号
server:
port: 8080
# 微信公众号配置
weChat:
appid: xxxxx # 微信测试号ID
appsecret: xxxxxxx # 微信测试号Secret
token: xxxx # Token
backUrl: http://byr1vl4.nat.ipyingshe.com # 你的域名
backApi: /callBack # 登录回调的API
3)配置拦截器解决跨域问题(如跨域没问题,可忽略)
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class CrossInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
return true;
}
}
4)公众平台测试号接口配置(可跳过忽略)
测试号配置
URL:填写 http+前面添加的域名+校验API
Token:任意字符串,只要和后端相对应就行
DecriptUtil工具类:SHA1加密
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class DecriptUtil {
/**
* @param decript
* @return
*/
public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}
接口校验,接入微信公众平台
Controller
@Controller
public class WxAuthController {
@Autowired
private WxAuthService wxAuthService;
/**
* 校验接入微信公众平台开发
*
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return
*/
@GetMapping("/wxSignatureCheck")
@ResponseBody
public String wxSignatureCheck(
@RequestParam(value = "signature") String signature,
@RequestParam(value = "timestamp") String timestamp,
@RequestParam(value = "nonce") String nonce,
@RequestParam(value = "echostr") String echostr) {
return wxAuthService.wxSignatureCheck(signature, timestamp, nonce, echostr);
}
}
Service
import com.cyan.wx_auth_demo.utils.DecriptUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
@Slf4j
@Service
public class WxAuthService {
@Value("${weChat.token}")
private String token;
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @param echostr
* @return 校验成功返回echostr,否则为null
*/
public String wxSignatureCheck(String signature, String timestamp, String nonce, String echostr) {
ArrayList<String> array = new ArrayList<>();
array.add(signature);
array.add(timestamp);
array.add(nonce);
//排序
String sortString = sort(token, timestamp, nonce);
//加密
String mytoken = DecriptUtil.SHA1(sortString);
//校验签名
if (mytoken != null && mytoken != "" && mytoken.equals(signature)) {
log.info("签名校验通过");
//如果检验成功输出 echostr,微信服务器接收到此输出,才会确认检验完成。
return echostr;
} else {
log.info("签名校验失败");
return null;
}
}
/**
* 排序方法
*
* @param token
* @param timestamp
* @param nonce
* @return
*/
public static String sort(String token, String timestamp, String nonce) {
String[] strArray = {token, timestamp, nonce};
Arrays.sort(strArray);
StringBuilder sbuilder = new StringBuilder();
for (String str : strArray) {
sbuilder.append(str);
}
return sbuilder.toString();
}
}
5)实现微信授权登录
AuthUtil工具类
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
@Slf4j
public class AuthUtil {
public static JSONObject doGetJson(String url) throws ClientProtocolException, IOException {
JSONObject jsonObject = null;
//创建HttpClient对象,发送请求,获取数据
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建httpGet方法对象
HttpGet httpGet = new HttpGet(url);
//设置请求头
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36");
//发送请求,获取响应的数据
log.info("请求地址:{}", url);
CloseableHttpResponse response = httpClient.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
if (entity != null) {
// 把返回的结果转换为JSON对象
String result = EntityUtils.toString(entity, "GB2312");
log.info("响应结果:{}", result);
jsonObject = JSONObject.parseObject(result);
}
httpGet.releaseConnection();
return jsonObject;
}
}
网站主页,用户点击登录按钮跳转登录授权页面
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
/**
* 登录按钮
* http://byr1vl4.nat.ipyingshe.com/index
*
* @return
*/
@GetMapping("/index")
public String login() {
return "<a href='http://byr1vl4.nat.ipyingshe.com/authorize'>微信登录</a>";
}
}
网页登录授权控制类
import com.alibaba.fastjson.JSONObject;
import com.cyan.wx_auth_demo.utils.AuthUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.net.URLEncoder;
@Slf4j
@Controller
public class WxAuthController {
@Value("${weChat.appid}")
private String appid;
@Value("${weChat.appsecret}")
private String appsecret;
@Value("${weChat.backUrl}")
private String backUrl;
@Value("${weChat.backApi}")
private String backApi;
/**
* 一、公众号微信登录授权
* <p>
* 通过微信接口获取code
* 1、如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE
* 2、获取到 code 与 state
*
* @return
*/
@GetMapping("/authorize")
public String authorize() throws IOException {
// 第一步:用户同意授权,获取code
String redirectUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + appid
+ "&redirect_uri=" + URLEncoder.encode(backUrl + backApi, "UTF-8")
+ "&response_type=code"
+ "&scope=snsapi_userinfo"
+ "&state=STATE#wechat_redirect";
// 重定向地址
System.out.println("重定向至微信登录授权地址: " + redirectUrl);
return "redirect:" + redirectUrl;
}
/**
* 二、回调获取用户信息
*
* @param code 第一步获取到的code
* @return
* @throws IOException
*/
@GetMapping("/callBack")
@ResponseBody
public String callBack(String code) throws IOException {
// 1、通过code(authorize方法获取)换取网页授权access_token
String accessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?"
+ "appid=" + appid
+ "&secret=" + appsecret
+ "&code=" + code
+ "&grant_type=authorization_code";
JSONObject accessTokenObj = AuthUtil.doGetJson(accessTokenUrl);
// 2、拉取用户信息,传参 access_token 和 openId
String userinfoUrl = "https://api.weixin.qq.com/sns/userinfo?"
+ "access_token=" + accessTokenObj.getString("access_token")
+ "&openid=" + accessTokenObj.getString("openId")
+ "lang=zh_CN";
JSONObject userInfoObj = AuthUtil.doGetJson(userinfoUrl);
// 将用户信息返回到前端
StringBuilder sb = new StringBuilder();
sb.append("<div>");
sb.append("<h3>授权成功</h3>");
sb.append("<img alt='头像' src='" + userInfoObj.getString("headimgurl") + "'>");
sb.append("<p>" + userInfoObj.getString("nickname") + "</p>");
sb.append("<p>" + userInfoObj.getString("province") + "</p>");
sb.append("</div>");
return sb.toString();
}
}
运行结果
① 登录页
② 授权页
② 授权成功页
到此,微信授权登录成功,如果有运行问题请自行调试,我这边能正常运行的。