一、創建應用
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());
- }
- }
查看更多
本文轉載自
原文作者:言曌博客