zoukankan      html  css  js  c++  java
  • QQ第三方授权登录OAuth2.0实现(Java)

    准备材料

    创建应用

    • 1.访问 https://connect.qq.com/manage.html ,登录。
    • 2.创建网站应用,填写网站基本信息以及平台信息,提交审核。注:网站回调域后续会用到,是点击授权登录时回调地址,需要与后续开发一致。

    程序开发

    1. 添加QQ登录按钮,用于点击跳转至QQ授权登录页

    <a href="/account/qqConnect" class="blog-user"> <i
    				class="fa fa-qq"></i>
    			</a>
    

    2. Java后台实现页面跳转

    2.1 编写一个工具类

    QQUtil

    package cn.zwqh.springboot.common.qq;
    import java.io.IOException;
    import java.net.URLEncoder;
    
    import org.apache.http.client.ClientProtocolException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONException;
    import com.alibaba.fastjson.JSONObject;
    
    import cn.zwqh.springboot.common.http.HttpClientUtils;
    import cn.zwqh.springboot.entity.sys.User;
    
    
    public class QQUtil {
    	private static final Logger log = LoggerFactory.getLogger(QQUtil.class);
    
    	private static final String QQ_APP_ID="XXX";//改成自己的
    	private static final String QQ_APP_SECRET="XXX";//改成自己的
    	private static final String LOGIN_REDIRECT_URI="https://www.zwqh.top/account/qqLogin";	//改成自己的
    	private static final String BIND_REDIRECT_URI="https://www.zwqh.top/account/qqBind";									  //改成自己的
    	private static final String AUTH_CODE_URL="https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id="+QQ_APP_ID+"&redirect_uri=REDIRECT_URI&state=STATE";
        private static final String ACCESS_TOKEN_URL="https://graph.qq.com/oauth2.0/token?client_id="+QQ_APP_ID+"&client_secret="+QQ_APP_SECRET+"&code=CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI";
        private static final String REFRESH_TOKEN_URL="https://graph.qq.com/oauth2.0/token?client_id="+QQ_APP_ID+"&client_secret="+QQ_APP_SECRET+"&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
        private static final String OPEN_ID_URL="https://graph.qq.com/oauth2.0/me?access_token=ACCESS_TOKEN";
        private static final String USER_INFO_URL="https://graph.qq.com/user/get_user_info?access_token=ACCESS_TOKEN&oauth_consumer_key="+QQ_APP_ID+"&openid=OPENID";
        
        public static JSONObject getJsonStrByQueryUrl(String paramStr){
            String[] params = paramStr.split("&");
            JSONObject obj = new JSONObject();
            for (int i = 0; i < params.length; i++) {
                String[] param = params[i].split("=");
                if (param.length >= 2) {
                    String key = param[0];
                    String value = param[1];
                    for (int j = 2; j < param.length; j++) {
                        value += "=" + param[j];
                    }
                    try {
                        obj.put(key,value);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
            return obj;
        }
        /**
         * 获取授权登录页码url
         * @return
         */
        public static String getLoginConnectUrl(String state) {
        	String url=null;
        	try{
        		url=AUTH_CODE_URL.replace("REDIRECT_URI", URLEncoder.encode(LOGIN_REDIRECT_URI, "utf-8")).replace("STATE", state);
        	}catch (Exception e) {
    			log.error(e.toString());
    		}
    		return url;
    	}
        
        /**
         * 获取授权绑定页码url
         * @return
         */
        public static String getBindConnectUrl() {
        	String url=null;
        	try{
        		url=AUTH_CODE_URL.replace("REDIRECT_URI", URLEncoder.encode(BIND_REDIRECT_URI, "utf-8"));
        	}catch (Exception e) {
    			log.error(e.toString());
    		}
    		return url;
    	}
          
        /**
         * 获取AccessToken
         * @return 返回拿到的access_token及有效期
         */
        public static QQAccessToken getQQLoginAccessToken(String code) throws ClientProtocolException, IOException{
        	QQAccessToken token = new QQAccessToken();
            String url = ACCESS_TOKEN_URL.replace("CODE", code).replace("REDIRECT_URI", URLEncoder.encode(LOGIN_REDIRECT_URI, "utf-8"));
            log.info("这是请求路径:"+url);
            String result = HttpClientUtils.doGet(url);
            JSONObject jsonObject=getJsonStrByQueryUrl(result);
            log.info("这是返回结果:"+jsonObject);
            if(jsonObject!=null){ //如果返回不为空,将返回结果封装进AccessToken实体类
                token.setAccessToken(jsonObject.getString("access_token"));//接口调用凭证
                token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口调用凭证超时时间,单位(秒)
                token.setRefreshToken(jsonObject.getString("refresh_token"));
            }
            return token;
        }
        
        /**
         * 获取AccessToken
         * @return 返回拿到的access_token及有效期
         */
        public static QQAccessToken getQQBindAccessToken(String code) throws ClientProtocolException, IOException{
        	QQAccessToken token = new QQAccessToken();
            String url = ACCESS_TOKEN_URL.replace("CODE", code).replace("REDIRECT_URI", URLEncoder.encode(BIND_REDIRECT_URI, "utf-8"));
            log.info("这是请求路径:"+url);
            String result = HttpClientUtils.doGet(url);
            JSONObject jsonObject=getJsonStrByQueryUrl(result);
            log.info("这是返回结果:"+jsonObject);
            if(jsonObject!=null){ //如果返回不为空,将返回结果封装进AccessToken实体类
                token.setAccessToken(jsonObject.getString("access_token"));//接口调用凭证
                token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口调用凭证超时时间,单位(秒)
                token.setRefreshToken(jsonObject.getString("refresh_token"));
            }
            return token;
        }
        
        /**
         * 刷新或续期access_token使用
         * @return 返回拿到的access_token及有效期
         */
        public static QQAccessToken refreshQQAccessToken(String refreshToken) throws ClientProtocolException, IOException{
        	QQAccessToken token = new QQAccessToken();
            String url = REFRESH_TOKEN_URL.replace("REFRESH_TOKEN",refreshToken);
            log.info("这是请求路径:"+url);
            String result = HttpClientUtils.doGet(url);
            log.info("这是返回结果:"+result);
            JSONObject jsonObject=getJsonStrByQueryUrl(result);
            log.info("这是转为json的结果:"+jsonObject);
            if(jsonObject!=null){ //如果返回不为空,将返回结果封装进AccessToken实体类
                token.setAccessToken(jsonObject.getString("access_token"));//接口调用凭证
                token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口调用凭证超时时间,单位(秒)
                token.setRefreshToken(jsonObject.getString("refresh_token"));
            }
            return token;
        }
        /**
         * 获取QQopenId
         * @return QQopenId
         */
        public static String getQQOpenId(String accessToken) throws ClientProtocolException, IOException{
            String url = OPEN_ID_URL.replace("ACCESS_TOKEN",accessToken);
            log.info("这是请求路径:"+url);
            String result = HttpClientUtils.doGet(url).replace("callback(", "").replace(");", "");
            log.info("这是返回结果:"+result);
            JSONObject jsonObject=JSON.parseObject(result);
            log.info("这是转为json的结果:"+jsonObject);
            if(jsonObject!=null&&jsonObject.getString("openid")!=null){ //如果返回不为空
                return jsonObject.getString("openid");
            }
            return null;
        }  
        /**
         * 获取QQ用户信息
         * @param accessToken
         * @param openId
         * @return
         * @throws IOException 
         * @throws ClientProtocolException 
         */
        public static JSONObject getUserInfo(String accessToken, String openId) throws ClientProtocolException, IOException {
            // 拼接请求地址
            String url = USER_INFO_URL.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
            log.info("这是请求路径:"+url);
            String result = HttpClientUtils.doGet(url);
            log.info("这是返回结果:"+result);
            JSONObject jsonObject=JSONObject.parseObject(result);
            log.info("这是转为json的结果:"+jsonObject);
            JSONObject json=new JSONObject();
            if (jsonObject!=null&&jsonObject.getInteger("ret").equals(0)) {
                try {
                	User user= new User();
                    // 用户的标识
                    user.setQqId(openId);
                    // 昵称
                    user.setNickname(jsonObject.getString("nickname"));
                    if(jsonObject.getString("figureurl_2")!=null&&!jsonObject.getString("figureurl_2").isEmpty()) {
                    	  // 用户头像
                        user.setAvatar(jsonObject.getString("figureurl_qq_2"));
                    }else {
                    	  // 用户头像
                        user.setAvatar(jsonObject.getString("figureurl_qq_1"));
                    }
                    json.put("success", true);
                    json.put("msg", "success");
                    json.put("user", user);
                } catch (Exception e) {
                    int errorCode = jsonObject.getInteger("ret");
                    String errorMsg = jsonObject.getString("msg");
                    log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, e.toString());
                    json.put("success", false);
                    json.put("msg", errorMsg);
                    json.put("user", null);
                }
            }else {
            	  json.put("success", false);
                  json.put("msg", "请先登录");
                  json.put("user", null);
            }
            return json;
        }
    }
    

    QQAccessToken

    package cn.zwqh.springboot.common.qq;
    
    import java.io.Serializable;
    
    public class QQAccessToken implements Serializable {
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 5258435811207021018L;
    	private String accessToken;//接口调用凭证
    	private int expiresIn;//access_token接口调用凭证超时时间,单位(秒)
    	private String openid;//授权用户唯一标识
    	private String refreshToken;//用户刷新access_token
    	private String scope;//用户授权的作用域,使用逗号(,)分隔
    
    	public String getOpenid() {
    		return openid;
    	}
    
    	public void setOpenid(String openid) {
    		this.openid = openid;
    	}
    
    	public String getAccessToken() {
    		return accessToken;
    	}
    
    	public void setAccessToken(String accessToken) {
    		this.accessToken = accessToken;
    	}
    
    	public int getExpiresIn() {
    		return expiresIn;
    	}
    
    	public void setExpiresIn(int expiresIn) {
    		this.expiresIn = expiresIn;
    	}
    
    	public String getRefreshToken() {
    		return refreshToken;
    	}
    
    	public void setRefreshToken(String refreshToken) {
    		this.refreshToken = refreshToken;
    	}
    
    	public String getScope() {
    		return scope;
    	}
    
    	public void setScope(String scope) {
    		this.scope = scope;
    	}
    
    }
    
    
    2.2 Controller层实现
    package cn.zwqh.springboot.action.web;
    
    import java.io.IOException;
    import java.util.Date;
    import java.util.UUID;
    
    import javax.annotation.Resource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.alibaba.fastjson.JSONObject;
    
    import cn.zwqh.springboot.action.BaseAction;
    import cn.zwqh.springboot.common.CookieUtil;
    import cn.zwqh.springboot.common.DateUtil;
    import cn.zwqh.springboot.common.EscapeUnescape;
    import cn.zwqh.springboot.common.qq.QQAccessToken;
    import cn.zwqh.springboot.common.qq.QQUtil;
    import cn.zwqh.springboot.common.redis.RedisHandle;
    import cn.zwqh.springboot.entity.SessionUser;
    import cn.zwqh.springboot.entity.sys.User;
    import cn.zwqh.springboot.service.UserService;
    
    @Controller
    @RequestMapping("/account")
    public class AccountAction extends BaseAction {
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 1729415442021645693L;
    
    	@Resource
    	private RedisHandle redisHandle;
    
    	@Autowired
    	private UserService userService;
    
    	/**
    	 * 跳转至QQ登录界面
    	 */
    	@RequestMapping("/qqConnect")
    	@ResponseBody
    	public void qqConnect() {
    		try {
    			String referer = getRequest().getHeader("REFERER");
    			String state = DateUtil.formatUserDefineDate(new Date(), "yyyyMMddHHmmssSSS");
    			redisHandle.set(state, referer, 60 * 30L);
    			getResponse().sendRedirect(QQUtil.getLoginConnectUrl(state));
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    
    	/**
    	 * QQ第三方登录
    	 * 
    	 * @throws Exception
    	 */
    	@RequestMapping("/qqLogin")
    	@ResponseBody
    	public void qqLogin() throws Exception {
    		String code = getRequest().getParameter("code");
    		String state = getRequest().getParameter("state");
    		System.out.println("code = " + code + ", state = " + state);
    		if (code != null && !"".equals(code)) {
    			QQAccessToken qqAccessToken = QQUtil.getQQLoginAccessToken(code);
    			if (qqAccessToken.getAccessToken().equals("")) {
    				// 我们的网站被CSRF攻击了或者用户取消了授权
    				// 做一些数据统计工作
    				System.out.print("没有获取到响应参数");
    				// 跳转返回地址
    				outJsonFailure("未获取到AccessToken,请重新进行QQ授权登录");
    			} else {
    				QQAccessToken qqAccessToken2 = QQUtil.refreshQQAccessToken(qqAccessToken.getRefreshToken());
    				String accessToken = qqAccessToken2.getAccessToken();
    				String referer = redisHandle.get(state).toString();
    				redisHandle.set(accessToken, referer, 60 * 30L);
    				redisHandle.remove(state);
    				getResponse().sendRedirect("https://www.zwqh.top/account/getQQUserInfo?qqAccessToken=" + accessToken);
    
    			}
    		} else {
    			outJsonFailure("缺少code参数");
    		}
    	}
    
    	/**
    	 * 获取QQ用户信息
    	 * 
    	 * @param qqAccessToken
    	 * @throws Exception
    	 */
    	@GetMapping("/getQQUserInfo")
    	public String getQQUserInfo(String qqAccessToken) throws Exception {
    		System.out.println("accessToken = " + qqAccessToken);
    		String referer = redisHandle.get(qqAccessToken).toString();
    		if (qqAccessToken != null && !"".equals(qqAccessToken)) {
    			try {
    				String qqOpenId = QQUtil.getQQOpenId(qqAccessToken);
    				if (qqOpenId != null) {
    					System.out.println("**************qq登录成功 qqOpenId = " + qqOpenId);
    					// 获取QQ用户信息
    					JSONObject object = QQUtil.getUserInfo(qqAccessToken, qqOpenId);
    					// 数据库中判断qqOpenId是否存在,存在则登录,不存在则注册
    					User user = userService.getUserByQQOpenId(qqOpenId);
    					if (user != null) {
    						user.setAvatar(object.getJSONObject("user").getString("avatar"));
    						user.setNickname(object.getJSONObject("user").getString("nickname"));
    						user.setLastLoginTime(DateUtil.formatDateTime(new Date()));
    						userService.updateUser(user);
    						SessionUser suser = SessionUser.getInstance(user);
    						String token = UUID.randomUUID().toString();
    						redisHandle.set(token, suser, 60 * 60L * 24 * 7);// 设置用户缓存及过期时间(一星期)
    						JSONObject data = new JSONObject();
    						data.put("userId", user.getId());
    						data.put("nickname", user.getNickname());
    						data.put("avatar", user.getAvatar());
    						data.put("token", token);
    						CookieUtil.setValue(getResponse(), "loginUser", data.toString());
    					} else {
    						user = new User();
    						user.setAvatar(object.getJSONObject("user").getString("avatar"));
    						user.setNickname(object.getJSONObject("user").getString("nickname"));
    						user.setLastLoginTime(DateUtil.formatDateTime(new Date()));
    						user.setRegisterTime(DateUtil.formatDateTime(new Date()));
    						user.setQqId(qqOpenId);
    						userService.insertUser(user);
    						SessionUser suser = SessionUser.getInstance(user);
    						String token = UUID.randomUUID().toString();
    						redisHandle.set(token, suser, 60 * 60L * 24 * 7);// 设置用户缓存及过期时间(一星期)
    						JSONObject data = new JSONObject();
    						data.put("userId", user.getId());
    						data.put("nickname", user.getNickname());
    						data.put("avatar", user.getAvatar());
    						data.put("token", token);
    						CookieUtil.setValue(getResponse(), "loginUser", data.toString());
    					}
    
    				} else {
    					putInRequest("error", "未获取到用户openid,请重新QQ授权登录");
    				}
    			} catch (Exception e) {
    				e.printStackTrace();
    				putInRequest("error", "登录异常");
    			}
    		} else {
    			putInRequest("error", "缺少code参数");
    		}
    		return "redirect:" + referer;
    	}
    	/**
    	 * 退出登录
    	 * @return
    	 */
    	@RequestMapping("/logout")
    	public String logout() {
    		String referer = getRequest().getHeader("REFERER");
    		String data= CookieUtil.getCookieValue(getRequest(), "loginUser");
    		if(data!=null&&data!="") {
    			JSONObject user=JSONObject.parseObject(EscapeUnescape.unescape(data));
    			String token=user.getString("token");
    			redisHandle.remove(token);
    			CookieUtil.deleteValue("loginUser",getResponse());
    		}
    		return "redirect:" + referer;
    	}
    }
    
    
    2.3 JavaScript 处理页面
    var data=eval('('+unescape(getCookie("loginUser"))+')');
    	var a = document.getElementsByClassName("blog-user")[0];
    	if(data!=null){		
    		a.setAttribute("href","/account/logout");
    		a.innerHTML='<img alt="'+data.nickname+'" title="'+data.nickname+'" src="'+data.avatar+'" class="layui-circle" width="40px" height="40px">';
    	}else{
    		a.setAttribute("href","/account/qqConnect");
    		a.innerHTML='<i class="fa fa-qq"></i>';
    	}
    

    总结

    总的来说QQ授权登录还是很简单的,该方法使用web端以及wap端。

    非特殊说明,本文版权归 朝雾轻寒 所有,转载请注明出处.

    本文标题: QQ第三方授权登录OAuth2.0实现(Java)

    本文网址: https://www.zwqh.top/article/info/7

    如果文章对您有帮助,请扫码关注下我的公众号,文章持续更新中...

  • 相关阅读:
    synchronized一个(二)
    org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)
    java.lang.AbstractMethodError: org.mybatis.spring.transaction.SpringManagedTransaction.getTimeout
    NTFS簇大小对硬盘性能的影响测试
    Win10 开机自动打开上次未关闭程序怎么办
    Win10快速访问怎么关闭?
    激活windows 10 LTSC 2019(无需工具)
    WPS文字双行合一
    插入百度地图代码后,页面文字不能被选中的解决方案
    WPS文字批量选中整行(以第?章为例)
  • 原文地址:https://www.cnblogs.com/zwqh/p/11579275.html
Copyright © 2011-2022 走看看