基于OAuth2.0协议方式
什么是OAuth
OAuth: OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
OAuth2.0
对于用户相关的OpenAPI(例如获取用户信息,动态同步,照片,日志,分享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。
QQ登录OAuth2.0采用OAuth2.0标准协议来进行用户身份验证和获取用户授权,相对于之前的OAuth1.0协议,其认证流程更简单和安全。
OAuth2.0总体处理流程
1 第一步:用户同意授权,获取code
2 第二步:通过code换取网页授权access_token
3 第三步:刷新access_token(如果需要)
4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
实现微信授权获取信息
微信网页授权地址
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
1.填写网页授权回调地址权限
2.生成网页授权地址
3.跳转到回调地址获取授权码
http://meitedu.s1.natapp.cc/?code=061yIRgM13IOc41ZQveM1tODgM1yIRge&state=STATE
4. 通过code换取网页授权access_token
https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx5c43fde3c9733d9e&secret=b8b217126c33a5fb7074927d5e72a81a&code=061WfM4E0TABnc2Cv04E02Lb5E0WfM4b&grant_type=authorization_code
4. 拉取用户信息(需scope为 snsapi_userinfo)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.learn</groupId>
<artifactId>oauth_web</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<!-- SpringBoot 对lombok 支持 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- SpringBoot web 核心组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!-- SpringBoot 外部tomcat支持 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- springboot-log4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<!-- springboot-aop 技术 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
</dependencies>
</project>
appid=wx5c43fde3c9733d9e
secret=b8b217126c33a5fb7074927d5e72a81a
redirectUri=http://127.0.0.1:8080/callback
authorizedUrl=https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
access_token=https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
userinfo=https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
package com.learn.base;
import org.springframework.stereotype.Component;
import com.learn.utils.Constants;
@Component
public class BaseApiService {
public ResponseBase setResultError(Integer code, String msg) {
return setResult(code, msg, null);
}
// 返回错误,可以传msg
public ResponseBase setResultError(String msg) {
return setResult(Constants.HTTP_RES_CODE_500, msg, null);
}
// 返回成功,可以传data值
public ResponseBase setResultSuccessData(Object data) {
return setResult(Constants.HTTP_RES_CODE_200, Constants.HTTP_RES_CODE_200_VALUE, data);
}
public ResponseBase setResultSuccessData(Integer code, Object data) {
return setResult(code, Constants.HTTP_RES_CODE_200_VALUE, data);
}
// 返回成功,沒有data值
public ResponseBase setResultSuccess() {
return setResult(Constants.HTTP_RES_CODE_200, Constants.HTTP_RES_CODE_200_VALUE, null);
}
// 返回成功,沒有data值
public ResponseBase setResultSuccess(String msg) {
return setResult(Constants.HTTP_RES_CODE_200, msg, null);
}
// 通用封装
public ResponseBase setResult(Integer code, String msg, Object data) {
return new ResponseBase(code, msg, data);
}
}
package com.learn.base;
public class ResponseBase {
private Integer rtnCode;
private String msg;
private Object data;
public ResponseBase() {
}
public ResponseBase(Integer rtnCode, String msg, Object data) {
super();
this.rtnCode = rtnCode;
this.msg = msg;
this.data = data;
}
public Integer getRtnCode() {
return rtnCode;
}
public void setRtnCode(Integer rtnCode) {
this.rtnCode = rtnCode;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static void main(String[] args) {
ResponseBase responseBase = new ResponseBase();
responseBase.setData("123456");
responseBase.setMsg("success");
responseBase.setRtnCode(200);
System.out.println(responseBase.toString());
}
@Override
public String toString() {
return "ResponseBase [rtnCode=" + rtnCode + ", msg=" + msg + ", data=" + data + "]";
}
}
package com.learn.entity;
public class AppEntity {
private long id;
private String appId;
private String appName;
private String appSecret;
private String accessToken;
private String redirectUri;
private int isFlag;
/**
* @return the id
*/
public long getId() {
return id;
}
/**
* @param id
* the id to set
*/
public void setId(long id) {
this.id = id;
}
/**
* @return the appId
*/
public String getAppId() {
return appId;
}
/**
* @param appId
* the appId to set
*/
public void setAppId(String appId) {
this.appId = appId;
}
/**
* @return the appName
*/
public String getAppName() {
return appName;
}
/**
* @param appName
* the appName to set
*/
public void setAppName(String appName) {
this.appName = appName;
}
/**
* @return the appSecret
*/
public String getAppSecret() {
return appSecret;
}
/**
* @param appSecret
* the appSecret to set
*/
public void setAppSecret(String appSecret) {
this.appSecret = appSecret;
}
/**
* @return the isFlag
*/
public int getIsFlag() {
return isFlag;
}
/**
* @param isFlag
* the isFlag to set
*/
public void setIsFlag(int isFlag) {
this.isFlag = isFlag;
}
/**
* @return the accessToken
*/
public String getAccessToken() {
return accessToken;
}
/**
* @param accessToken
* the accessToken to set
*/
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
/**
* @return the redirectUri
*/
public String getRedirectUri() {
return redirectUri;
}
/**
* @param redirectUri
* the redirectUri to set
*/
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
}
package com.learn.entity;
public class ResponseBase {
private Integer rtnCode;
private String msg;
private Object data;
public ResponseBase() {
}
public ResponseBase(Integer rtnCode, String msg, Object data) {
super();
this.rtnCode = rtnCode;
this.msg = msg;
this.data = data;
}
public Integer getRtnCode() {
return rtnCode;
}
public void setRtnCode(Integer rtnCode) {
this.rtnCode = rtnCode;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static void main(String[] args) {
ResponseBase responseBase = new ResponseBase();
responseBase.setData("123456");
responseBase.setMsg("success");
responseBase.setRtnCode(200);
System.out.println(responseBase.toString());
}
@Override
public String toString() {
return "ResponseBase [rtnCode=" + rtnCode + ", msg=" + msg + ", data=" + data + "]";
}
}
package com.learn.utils;
public interface Constants {
// 响应请求成功
String HTTP_RES_CODE_200_VALUE = "success";
// 系统错误
String HTTP_RES_CODE_500_VALUE = "fial";
// 响应请求成功code
Integer HTTP_RES_CODE_200 = 200;
// 系统错误
Integer HTTP_RES_CODE_500 = 500;
// 未关联QQ账号
Integer HTTP_RES_CODE_201 = 201;
// 发送邮件
String MSG_EMAIL = "email";
// 会员token
String TOKEN_MEMBER = "TOKEN_MEMBER";
// 支付token
String TOKEN_PAY = "TOKEN_pay";
// 支付成功
String PAY_SUCCESS = "success";
// 支付白
String PAY_FAIL = "fail";
// 用户有效期 90天
Long TOKEN_MEMBER_TIME = (long) (60 * 60 * 24 * 90);
int COOKIE_TOKEN_MEMBER_TIME = (60 * 60 * 24 * 90);
Long PAY_TOKEN_MEMBER_TIME = (long) (60 * 15);
// cookie 会员 totoken 名称
String COOKIE_MEMBER_TOKEN = "cookie_member_token";
}
package com.learn.utils;
import java.io.IOException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
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.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
/**
* HttpClient4.3工具类
*
* @author hang.luo
*/
public class HttpClientUtils {
private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class); // 日志记录
private static RequestConfig requestConfig = null;
static {
// 设置请求和传输超时时间
requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();
}
/**
* post请求传输json参数
*
* @param url
* url地址
* @param json
* 参数
* @return
*/
public static JSONObject httpPost(String url, JSONObject jsonParam) {
// post请求返回结果
CloseableHttpClient httpClient = HttpClients.createDefault();
JSONObject jsonResult = null;
HttpPost httpPost = new HttpPost(url);
// 设置请求和传输超时时间
httpPost.setConfig(requestConfig);
try {
if (null != jsonParam) {
// 解决中文乱码问题
StringEntity entity = new StringEntity(jsonParam.toString(), "utf-8");
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
}
CloseableHttpResponse result = httpClient.execute(httpPost);
// 请求发送成功,并得到响应
if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String str = "";
try {
// 读取服务器返回过来的json字符串数据
str = EntityUtils.toString(result.getEntity(), "utf-8");
// 把json字符串转换成json对象
jsonResult = JSONObject.parseObject(str);
} catch (Exception e) {
logger.error("post请求提交失败:" + url, e);
}
}
} catch (IOException e) {
logger.error("post请求提交失败:" + url, e);
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* post请求传输String参数 例如:name=Jack&sex=1&type=2
* Content-type:application/x-www-form-urlencoded
*
* @param url
* url地址
* @param strParam
* 参数
* @return
*/
public static JSONObject httpPost(String url, String strParam) {
// post请求返回结果
CloseableHttpClient httpClient = HttpClients.createDefault();
JSONObject jsonResult = null;
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
try {
if (null != strParam) {
// 解决中文乱码问题
StringEntity entity = new StringEntity(strParam, "utf-8");
entity.setContentEncoding("UTF-8");
entity.setContentType("application/x-www-form-urlencoded");
httpPost.setEntity(entity);
}
CloseableHttpResponse result = httpClient.execute(httpPost);
// 请求发送成功,并得到响应
if (result.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String str = "";
try {
// 读取服务器返回过来的json字符串数据
str = EntityUtils.toString(result.getEntity(), "utf-8");
// 把json字符串转换成json对象
jsonResult = JSONObject.parseObject(str);
} catch (Exception e) {
logger.error("post请求提交失败:" + url, e);
}
}
} catch (IOException e) {
logger.error("post请求提交失败:" + url, e);
} finally {
httpPost.releaseConnection();
}
return jsonResult;
}
/**
* 发送get请求
*
* @param url
* 路径
* @return
*/
public static JSONObject httpGet(String url) {
// get请求返回结果
JSONObject jsonResult = null;
CloseableHttpClient client = HttpClients.createDefault();
// 发送get请求
HttpGet request = new HttpGet(url);
request.setConfig(requestConfig);
try {
CloseableHttpResponse response = client.execute(request);
// 请求发送成功,并得到响应
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// 读取服务器返回过来的json字符串数据
HttpEntity entity = response.getEntity();
String strResult = EntityUtils.toString(entity, "utf-8");
// 把json字符串转换成json对象
jsonResult = JSONObject.parseObject(strResult);
} else {
logger.error("get请求提交失败:" + url);
}
} catch (IOException e) {
logger.error("get请求提交失败:" + url, e);
} finally {
request.releaseConnection();
}
return jsonResult;
}
}
package com.learn.utils;
import java.net.URLEncoder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class WeiXinUtils {
@Value("${appid}")
private String appId;
@Value("${secret}")
private String secret;
@Value("${redirecturi}")
private String redirectUri;
@Value("${authorizedUrl}")
private String authorizedUrl;
@Value("${access_token}")
private String accessToken;
@Value("${userinfo}")
private String userinfo;
public String getAuthorizedUrl() {
return authorizedUrl.replace("APPID", appId).replace("REDIRECT_URI", URLEncoder.encode(redirectUri));
}
public String getAccessTokenUrl(String code) {
return accessToken.replace("APPID", appId).replace("SECRET", secret).replace("CODE", code);
}
public String getUserInfo(String accessToken, String openId) {
return userinfo.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
}
}
package com.learn.oauth;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.alibaba.fastjson.JSONObject;
import com.learn.base.BaseApiService;
import com.learn.utils.HttpClientUtils;
import com.learn.utils.WeiXinUtils;
@Controller
public class OauthController extends BaseApiService {
@Autowired
private WeiXinUtils weiXinUtils;
private String errorPage = "errorPage";
// 生成授权链接
@RequestMapping("/authorizedUrl")
public String authorizedUrl() {
String authorizedUrl = weiXinUtils.getAuthorizedUrl();
System.out.println("authorizedUrl:" + authorizedUrl);
return "redirect:" + weiXinUtils.getAuthorizedUrl();
}
// 微信授权回调地址
@RequestMapping("/callback")
public String callback(String code, HttpServletRequest request) {
// 1.使用Code 获取 access_token
String accessTokenUrl = weiXinUtils.getAccessTokenUrl(code);
JSONObject resultAccessToken = HttpClientUtils.httpGet(accessTokenUrl);
boolean containsKey = resultAccessToken.containsKey("errcode");
if (containsKey) {
request.setAttribute("errorMsg", "系统错误!");
return errorPage;
}
// 2.使用access_token获取用户信息
String accessToken = resultAccessToken.getString("access_token");
String openid = resultAccessToken.getString("openid");
// 3.拉取用户信息(需scope为 snsapi_userinfo)
String userInfoUrl = weiXinUtils.getUserInfo(accessToken, openid);
JSONObject userInfoResult = HttpClientUtils.httpGet(userInfoUrl);
System.out.println("userInfoResult:" + userInfoResult);
request.setAttribute("nickname", userInfoResult.getString("nickname"));
request.setAttribute("city", userInfoResult.getString("city"));
request.setAttribute("headimgurl", userInfoResult.getString("headimgurl"));
return "info";
}
}
package com.learn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class AppOauth {
public static void main(String[] args) {
SpringApplication.run(AppOauth.class, args);
}
}
准备工作:生成授权链接
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect 若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。
https://open.weixin.qq.com/connect/oauth2/authorize?appid= wx5c43fde3c9733d9e &redirect_uri=http://127.0.0.1:8080/callback&response_type=code&scope= snsapi_userinfo &state=STATE#wechat_redirect
1 第一步:用户同意授权,获取code
2 第二步:通过code换取网页授权access_token
3 第三步:刷新access_token(如果需要)
4 第四步:拉取用户信息(需scope为 snsapi_userinfo)
5 附:检验授权凭证(access_token)是否有效