最早在2007年时Open Authorization提出了OAuth1.0版,在推出五年后,很多开发者们都觉得OAuth1.0开发过程有点复杂,于是就在OAuth1.0基础之上进行改造就有了后来的OAuth2.0。两者的区别在于,OAuth2.0变得更加简单易用,第二个取消了中间的加密过程同时也保留了OAuth1.0中的安全特性。这就是OAuth2.0的基本概念。(相比于OAuth1.0更加简单易用,取消的中间的加密过程且保留了原1.0的安全特性)
时下比较流行的第三方登录,不管是微信登录还是QQ登录,它们采用的标准都是采用OAuth2.0的协议标准。
接下来看一下,OAuth2.0在微信登录过程当中,它是怎么来进行作用的
如果在网页中,用户并不想使用普通的流程去注册登陆,而是用微信登陆的话,那么在登陆页面就会提供一个用于微信登陆的链接,当用户点击微信登陆链接的时候,网页会向微信开发平台发送请求,微信开发平台收到请求之后,会生成一个二维码供用户查看,用户进行微信扫码之后,也就是扫码登陆,会拿到被授权的临时票据(Code),这时拿着票据Code以及在单击微信注册登陆的shi’h获取到的appid及appsecret一共三个参数(Code,appid,appsecret).拿着这三个参数去换取一个时间较长的票据,这个票据被称为access_token,微信中access_token被设置为2个小时的有效期,这个时候我们就可以通过access_token获取用户相应微信用户信息.
步骤如下:
1.用户访问项目(网页登陆页面的微信登陆图标),点击微信登录链接。(调用微信登陆的控制层)
2.在从后台跳转到微信登录页时(携带appid,回调地址),展示给用户登录二维码
3.微信用户扫码进行确认授权,微信平台跳转至回调地址(携带临时票据Code)
4.后台接收到请求,根据临时票据appid和appsecret获取access_token。
5. 项目再根据access_token访问微信登录平台获取用户信息,进行登录
问题:之所以要通过临时票据来得到access_token而不是直接获取access_token,是为了保证数据的更加安全,也为了保证access_token不被泄露。
1.接入条件
1.注册微信开发者平台账号
2.拥有一个已过审核的网站应用
实现方式
- 浏览器访问 https://open.weixin.qq.com/
- 找到网站应用开发点击下方的蓝色链接进入
- 点击创建应用
- 没有账号就进行注册(审核过后就会拥有(appid和AppSecret))
- 邮箱进行激活注册,然后填写相关企业信息
- 通过注册的邮箱账号进行登陆,然后按照第三步骤点击创建应用
填写相关的信息,提交审核,审核在7个工作日内完成。当完成之后我们即可在项目中编写相关的API接口代码:
应用官网其实就是你的项目地址,授权回调域也是你的外网访问地址.
注意:授权回调域前缀不能有http,后缀不能带"/"。
参数 | 是否必须 | 说明 |
---|---|---|
appid | 是 | 应用唯一标识(网站应用审核通过后,会获取到appid和appsecret) |
redirect_uri | 是 | 回调地址,即当用户扫描二维码授权通过后,微信平台要请求网站的URL由它指定。注:需要urlEncode对链接进行处理 |
response_type | 是 | 填写code (值固定是code) |
scope | 是 | 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login。 |
state | 否 | 用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验。 |
**
2. 通过code获取access_token
- 获取access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code - 参数说明:
**
如果获取正确那么返回的JSON字符串如下:
{
“access_token”:“ACCESS_TOKEN”,
“expires_in”:7200,
“refresh_token”:“REFRESH_TOKEN”,
“openid”:“OPENID”,
“scope”:“SCOPE”,
“unionid”: “o6_bmasdasdsad6_2sgVt7hMZOPfL”
}
参数说明:
错误返回案例:
{“errcode”:40030,“errmsg”:“invalid refresh_token”}
本次代码演示在多模块当中进行布局:
因为是与登陆相关的,所以我将其写在了我的认证模块当中,认证模块就是对登陆用户的认证及Token的处理。如下:
首先在工具类模块中的common目录中导入UrlUtils这个类,然后在认证模块下的controller目录下创建WxLoginController类,这个类就是处理微信登陆的控制层:
1.
1. UrlUtils类中的内容:
package cn.itrip.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 本类提供了对URL所指向的内容的加载操作
* @author hduser
*
*/
public class UrlUtils {
/**
* 获取url网址返回的数据内容
* @param urlStr
* @return
*/
public static String loadURL(String urlStr){
try{
URL url = new URL(urlStr);
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.connect();
InputStream inputStream = urlConnection.getInputStream();
String responseStr = ConvertToString(inputStream);
return responseStr;
}catch(IOException e){
e.printStackTrace();
return null;
}
}
private static String ConvertToString(InputStream inputStream){
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder result = new StringBuilder();
String line = null;
try {
while((line = bufferedReader.readLine()) != null){
result.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
inputStreamReader.close();
inputStream.close();
bufferedReader.close();
}catch(IOException e){
e.printStackTrace();
}
}
return result.toString();
}
}
2.认证模块中的代码:
package cn.itrip.auth.controller.Login;
import cn.itrip.common.UrlUtils;
import com.alibaba.fastjson.JSON;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@Controller
@RequestMapping("/wx/login")
public class WxLoginController {
//日志对象
private Logger logger = Logger.getLogger(WxLoginController.class);
/**
*
* @param code 授权的临时票据
* @param state 用于保持请求和回调的状态,授权请求后原样带回给第三方。
* 该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,
* 可设置为简单的随机数加session进行校验。
* @param response
* @throws Exception
*/
@RequestMapping(value = "/callBackWeChat",method= RequestMethod.POST)
public void callBackWeChat(@RequestParam String code,
@RequestParam String state,
HttpServletResponse response) throws Exception {
/**
* 1.编写请求code
* 1.1.请求地址
* appid=应用唯一标识(网站应用审核通过后,会获取到appid和appsecret)
* secret=应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
* code=code参数
* grant_type=填authorization_code
*/
String accessUrl="https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=wx860bf23c66d93e33" +
"&secret=9c92026ab4faa4a4f7ac4cf10b2a8a3c" +
"&code=" + code +
"&grant_type=authorization_code";
//1.2通过发送accessUrl请求地址,并得到返回参数,返回的参数是一个json字符串
String jsonStr = UrlUtils.loadURL(accessUrl);
//将json字符串转换成Map集合
Map<String,String> accessMap= JSON.parseObject(jsonStr, Map.class);
/**
* 2.通过code——access_token获取json字符串/accessMap集合中的accessToken,
* 也就是获取集合中的“接口调用凭证”
*/
String accessToken = accessMap.get("access_token");
//3.获取授权用户唯一标识
String openId=accessMap.get("openid");
logger.info("accessToken的值为:" + accessToken + ",openId的值为:" + openId);
//重定向网址
response.sendRedirect("http://www.baidu.com");
}
}
3. 通过access_token和openid获取当前微信的用户信息
1. 请求地址
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
2. 参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 接口调用凭证 |
openid | 是 | 普通用户的标识,对当前开发者帐号唯一(授权用户唯一标识) |
lang | 否 | 国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语,默认为zh-CN |
3.获取后返回的正确的JSON字符串:
{
"openid":"OPENID",
"nickname":"NICKNAME", #昵称
"sex":1, #性别
"province":"PROVINCE", #省
"city":"CITY", #市
"country":"COUNTRY", #国家
"headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
上述返回的JSON字符串的参数说明:
参数 | 说明 |
---|---|
openid | 普通用户的标识,对当前开发者帐号唯一 |
nickname | 普通用户昵称 |
sex | 普通用户性别,1为男性,2为女性 |
province | 普通用户个人资料填写的省份 |
city | 普通用户个人资料填写的城市 |
country | 国家,如中国为CN |
headimgurl | 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 |
privilege | 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) |
unionid | 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的 |
4. 返回错误的JSON字符串
{ “errcode”:40003,“errmsg”:“invalid openid” }
3. 代码编写
对认证模块itripauth中之前编写的WxLoginController类的callBackWeChat方法进行修改,如下:
找到log4j日志输出语句,也就是:
logger.info(“accessToken的值为:” + accessToken + “,openId的值为:” + openId);
1.在这后面通过请求地址获取登陆的微信用户信息也就是 通过access_token和openid获取当前微信的用户信息如下:
/**
4.通过access_token获取用户信息,
参考:资源中心 网站应用微信登录功能授权后接口调用获取用户个人信息(UnionID机制)
4.1 请求URL地址
*/
String userInfoUrl="https://api.weixin.qq.com/sns/userinfo?access_token="
+ accessToken +
"&openid=" + openId;
- 发送请求,返回JSON字符串:
- 获取用户个人信息
String userInfoStr = UrlUtils.loadURL(userInfoUrl);
- 获取用户信息中的城市
Map<String,String> userInfoMap = JSON.parseObject(userInfoStr, Map.class);
//3.3 获取用户个人信息
String city = userInfoMap.get("city");//获取用户信息中的城市
- 获取用户昵称
String nickname = userInfoMap.get("nickname");
- 日志输出这些用户信息
logger.info("city的值为:" + city + ",nickname的值为:" + nickname);
整块代码如下:
logger.info("accessToken的值为:" + accessToken + ",openId的值为:" + openId);
//3、通过access_token获取用户信息,参考:资源中心 网站应用微信登录功能授权后接口调用获取用户个人信息(UnionID机制)
//3.1 请求URL地址
String userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId;
//3.2 发送请求,返回JSON字符串
String userInfoStr = UrlUtils.loadURL(userInfoUrl);
Map<String,String> userInfoMap = JSON.parseObject(userInfoStr, Map.class);
//3.3 获取用户个人信息
String city = userInfoMap.get("city"); //获取用户信息中的城市
String nickname = userInfoMap.get("nickname");
logger.info("city的值为:" + city + ",nickname的值为:" + nickname);
response.sendRedirect("http://www.baidu.com");
这个时候我就在linux服务器上启动sunny-ngrok,通过外网域名访问linux服务器,然后将项目打包放在linux服务器上,之打包当前模块测试运行,如下:
注:要放在对应的tomcat容器上,端口要一致:
启动tomcat之后通过外网域名+项目名访问项目:
这个时候访问微信登陆页面:
https://open.weixin.qq.com/connect/qrconnect?appid=wx860bf23c66d93e33&redirect_uri=http%3a%2f%2fitripDebug.vipgz1.idcfengye.com%2fitripauth%2fwx%2flogin%2fcallBackWeChat&response_type=code&scope=snsapi_login&state=12512#wechat_redirect
参数 | 说明 |
---|---|
appid | 应用唯一标识(网站应用审核通过后,会获取到appid和appsecret) |
参数 | 说明 |
---|---|
redirect_uri | 回调地址,即当用户扫描二维码授权通过后,微信平台要请求网站的URL由它指定。注:需要urlEncode对链接进行处理。对应项目的Controller处理方法地址,上述的地址进行拆分为: redirect_uri**=http%3a%2f%2fitripDebug.vipgz1.idcfengye.com%2fitripauth%2fwx%2flogin%2fcallBackWeChat |
第一个加粗的蓝色字体对应我们上述说的启动了sunny-ngrok后的外网域名,第二个加粗的字体也就是itripauth对应了项目名,第三个加粗的字体wx对应了控制层地址,第四个加粗的地址login也对赢了controller地址,第五个callBackWeChat对应了方法名,也就是对应http://itripdebug.vipgz1.idcfengye.com/itripauth/wx/login/callBackWeChat |
如下:
可以清楚得看到层次分名的方法调用,根据自己的方法地址更改值即可,
这个时候调用微信登陆扫码页面即可看到成功调用方法,项目配了log4j地址就可在配置地址的log4j日志文件中查看输出的日志信息,如下:
这个时候就说明自己的微信登陆扫码是成功完成了,后续根据项目需要进行数据的存储,如Token,redis这些!!!