第三方登录:QQ登录实现(OAuth2.0)
一、创建应用
1、在 QQ互联 创建应用
地址:https://connect.qq.com/manage.html#/
然后进行实名认证,创建应用,审核通过
然后点击查看,可以获得 APP ID 和 APP Key
回调地址如下
2、授权的基本原理
可以参考官方文档
1)根据QQ登录链接可以回调获得 code
2)根据APP ID 、APP Key 和 code 可获得 token
3)根据 token 获得 OpenId
4) 根据 OpenId 可以获得用户的基本信息
其中 OpenId 是一个唯一的值,比如 8A674574E1B12345D790A1B3EFE81234
3、几个必要的URL
1)登录页面授权 URL:
https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s
2)获得 Token 的 URL:
https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s
3)获得用户OpenId 的 URL:
https://graph.qq.com/oauth2.0/me?access_token=%s
4)获得用户信息的 URL:
https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s
二、基本准备
1、数据库设计
主要看 bind 和 bind_type 表的数据
identifier 表示识别码,保证唯一性
credential 表示凭证,通常用不上
bind_type_id 和 user_id 是外键,很好理解
name 唯一性
style 样式
2、放置 QQ 登录按钮
这里有两种常用的方式
① 弹小窗
<script>
function openWin(url,name,iWidth,iHeight) {
//获得窗口的垂直位置
var iTop = (window.screen.availHeight - 30 - iHeight) / 2;
//获得窗口的水平位置
var iLeft = (window.screen.availWidth - 10 - iWidth) / 2;
window.open(url, name, 'height=' + iHeight + ',innerHeight=' + iHeight + ',width=' + iWidth + ',innerWidth=' + iWidth + ',top=' + iTop + ',left=' + iLeft + ',status=no,toolbar=no,menubar=no,location=no,resizable=no,scrollbars=0,titlebar=no');
}
function qqLogin() {
var url = "https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101472393&redirect_uri=http://codergroup.cn/oauth/qq/callback&scope=get_user_info";
openWin(url,"qqLogin",650,500);
}
</script>
<a href="javascript:void(0);" onclick="qqLogin()"></a>
② 在新窗口打开
<a href="https://graph.qq.com/oauth2.0/authorizeresponse_type=code&client_id=101472393&redirect_uri=http://codergroup.cn/oauth/qq/callback&scope=get_user_info" target="_blank"></a>
三、具体代码
参考这里
1、由于做了多个登录,所以代码做了一定程度的封装,大致如下:
//多个登录差不多都要共用这些方法,所以统一放到这个接口中
public interface AuthService {
public abstract String getAccessToken(String code);
public abstract String getOpenId(String accessToken);
public abstract String refreshToken(String code);
public abstract String getAuthorizationUrl() throws UnsupportedEncodingException;
public abstract JSONObject getUserInfo(String accessToken,String openId);
}
2、由于全部是自己封装的,所以http请求的代码也是所有的登录共用的,这里统一放放到了类DefaultAuthServiceImpl中,代码如下:
public abstract class DefaultAuthServiceImpl implements AuthService{
public static RestTemplate getRestTemplate() {// 手动添加
SimpleClientHttpRequestFactory requestFactory=new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(120000);
List<HttpMessageConverter<?>> messageConverters = new LinkedList<>();
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
RestTemplate restTemplate=new RestTemplate(messageConverters);
restTemplate.setRequestFactory(requestFactory);
return restTemplate;
}
}
由此,所有的登录Service只需要继承AuthService即可。
3、QQ登录 Service 接口
QQAuthService.java
public interface QQAuthService extends AuthService {
}
4、QQ登录 Service 实现
QQAuthServiceImpl.java
@Service
public class QQAuthServiceImpl extends DefaultAuthServiceImpl implements QQAuthService {
private Logger logger = LoggerFactory.getLogger(QQAuthServiceImpl.class);
//QQ 登陆页面的URL
private final static String AUTHORIZATION_URL =
"https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s";
//获取token的URL
private final static String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s";
// 获取用户 openid 的 URL
private static final String OPEN_ID_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
// 获取用户信息的 URL,oauth_consumer_key 为 apiKey
private static final String USER_INFO_URL = "https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s";
// 下面的属性可以通过配置读取
private static final String CALLBACK_URL = "http://XXX/XX/XX"; // QQ 在登陆成功后回调的 URL,这个 URL 必须在 QQ 互联里填写过
private static final String API_KEY = "xxxxxx"; // QQ 互联应用管理中心的 APP ID
private static final String API_SECRET = "xxxxxx"; // QQ 互联应用管理中心的 APP Key
private static final String SCOPE = "get_user_info"; // QQ 互联的 API 接口,访问用户资料
/**
* @return QQ 登陆页面的 URL
*/
@Override
public String getAuthorizationUrl() {
String url = String.format(AUTHORIZATION_URL,API_KEY,CALLBACK_URL,SCOPE);
return url;
}
@Override
public String getAccessToken(String code) {
String url = String.format(ACCESS_TOKEN_URL,API_KEY,API_SECRET,code, CALLBACK_URL);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
URI uri = builder.build().encode().toUri();
String resp = getRestTemplate().getForObject(uri, String.class);
logger.error("getAccessToken resp = "+resp);
if(resp.contains("access_token")){
Map<String,String> map = getParam(resp);
String access_token = map.get("access_token");
return access_token;
}else{
throw new ServiceException(resp);
}
}
//由于QQ的几个接口返回类型不一样,此处是获取key-value类型的参数
private Map<String,String> getParam(String string){
Map<String,String> map = new HashMap();
String[] kvArray = string.split("&");
for(int i = 0;i<kvArray.length;i++){
String[] kv = kvArray[i].split("=");
map.put(kv[0],kv[1]);
}
return map;
}
//QQ接口返回类型是text/plain,此处将其转为json
public JSONObject ConvertToJson(String string){
string = string.substring(string.indexOf("(")+1,string.length());
string = string.substring(0,string.indexOf(")"));
logger.error("ConvertToJson s = "+string);
JSONObject jsonObject = JSONObject.parseObject(string);
return jsonObject;
}
@Override
public String getOpenId(String accessToken) {
String url = String.format(OPEN_ID_URL,accessToken);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
URI uri = builder.build().encode().toUri();
String resp = getRestTemplate().getForObject(uri, String.class);
logger.error("getAccessToken resp = "+resp);
if(resp.contains("openid")){
JSONObject jsonObject = ConvertToJson(resp);
String openid = jsonObject.getString("openid");
return openid;
}else{
throw new ServiceException(resp);
}
}
/**
* https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID
* @param accessToken
* @param openId
* @return
*/
@Override
public JSONObject getUserInfo(String accessToken, String openId){
openId = getOpenId(accessToken);
String url = String.format(USER_INFO_URL,accessToken, API_KEY, openId);
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
URI uri = builder.build().encode().toUri();
String resp = getRestTemplate().getForObject(uri,String.class);
JSONObject data = JSONObject.parseObject(resp);
logger.error("resp = "+data);
JSONObject result = new JSONObject();
result.put("id",openId);
result.put("nickName",data.getString("nickname"));
result.put("avatar",data.getString("figureurl_qq_1"));
return result;
}
@Override
public String refreshToken(String code) {
return null;
}
}
5、在Controller中调用,代码如下:
@RestController
@RequestMapping(value = "/oauth")
public class AuthController {
private Logger logger = LoggerFactory.getLogger(AuthController.class);
@Autowired
private QQAuthService qqAuthService;
@Autowired
private UserService userService;
//访问登陆页面,然后会跳转到 QQ 的登陆页面
@RequestMapping(value = "/qqLoginPage",method = RequestMethod.GET)
public JSONObject qqLogin() throws Exception {
String uri = qqAuthService.getAuthorizationUrl();
return loginPage(uri);
}
}
//qq授权后会回调此方法,并将code传过来
@RequestMapping("/qq/callback")
public void getQQCode(String code, HttpServletRequest request,HttpServletResponse response) throws Exception {
//根据code获取token
String accessToken = qqAuthService.getAccessToken(code);
// 保存 accessToken 到 cookie,过期时间为 30 天,便于以后使用
Cookie cookie = new Cookie("accessToken", accessToken);
cookie.setMaxAge(60 * 24 * 30);
response.addCookie(cookie);
//本网站是将用户的唯一标识存在用户表中,大家也可以加一张表,存储用户和QQ的对应信息。
//根据openId判断用户是否已经绑定过
String openId = qqAuthService.getOpenId(accessToken);
KmsUser user = userService.getUserByCondition(openId);
if (user == null) {
//如果用户不存在,则跳转到绑定页面
response.sendRedirect(request.getContextPath() + "/bind?type="+Constants.LOGIN_TYPE_QQ);
} else {
//如果用户已存在,则直接登录
response.sendRedirect(request.getContextPath());
}
}
查看更多
本文转载自
原文作者:言曌博客