我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛
Auth2.0+QQ三方登录
QQ互联申请
申请开发者
这个一般不乱填都会通过的,一般就是四天左右吧。
应用管理—创建应用
这里我申请了很多次才通过,发现了技巧就好了,我们可以不用先管后端怎么写,先把回调申请下来再去写也可以。
-
首先要把QQ登录图片(官方文档里有)放在登录下面
-
将这个图片设置一个超链接graph.qq.com/oauth2.0/sh…
-
将这个前端放在自己的服务器上,最好是80端口
-
下面就是填写申请表了,要想好一个回调地址这个需要和后台对应起来的非常重要,这个申请很快一般就是当天下午或者第二天下午就有结果
这上面基本百度都有教程的,但是下面代码部分我就很奇怪怎么没有人写过码
业务代码
我这是SpringSecurity项目上加的QQ三方登录,其实都一样的。
数据库设计
不需要修改原有的用户表,我们写一个专门存放第三方登录存储表
用户表和三方登录表一对一关系,把外键放哪里表都可以
代码设计 开发文档接口介绍
-
yml配置文件写好自己的应用内容
qq: oauth: appid: ******** appkey: ******* url: ********* #回调地址 -
代码逻辑
生成专属于自己的QQ登录链接防止防止CSRF攻击,用户点击我们生成的链接授权登录后,就直接访问我们的回调地址
/** * QQ互联中提供的 appid 和 appkey 和 回调url */ @Value("${qq.oauth.appid}") public String APPID; @Value("${qq.oauth.appkey}") public String APPKEY; @Value("${qq.oauth.url}") public String URL; /** * 请求授权 */ @PostMapping(value = "/authqq") @ApiOperation("获得自己专属的qq登录路径") public AjaxResult qqAuth(HttpServletRequest request) { // 用于第三方应用防止CSRF攻击 String uuid = UUID.randomUUID().toString().replaceAll("-", ""); request.getServletContext().setAttribute("state",uuid); //获取Authorization Code String url = "https://graph.qq.com/oauth2.0/authorize?response_type=code" + "&client_id=" + APPID + "&redirect_uri=" +URL + "&state=" + uuid; return AjaxResult.success(url); }回调接口,这里逻辑都写在了controller不推荐哟,记得自己改一下,这里逻辑
1.判断状态码state是否是咱们自己的
2.通过Authorization_Code获取Access_Token
3.获取回调后的openID这个id是用户对你系统唯一的id
4.通过Access_Token和openID获取QQ用户信息
逻辑部分
1.从数据库查openId是否存在(QQ用户是否第一次登录),第一次就返回前端让用户绑定当前系统账户,或者申请一个账号(这里有多种实现方式,比如我的是管理员分配账户没有注册接口或者直接用qq登录等操作)
2.不是第一次登录的话我们就直接用security登录方式登录
3.回忆一下security登录userDetailsService.loadUserByUsername()获得UserDetails登录用户
4.更新security登录用户对象
5.生成token返回,将自定义user对象的名字头像换成QQ用户的
/** * 授权回调 */ @GetMapping(value = "/connect") @ApiOperation("回调地址,授权自己专属url后自动跳转") public AjaxResult qqCallback(HttpServletRequest request,String code,String state) throws Exception { // 验证信息 String uuid = (String) request.getServletContext().getAttribute("state"); // 验证信息我们发送的状态码 if (uuid == null || state == null){ return AjaxResult.error("登录出错了"); }else if(null != uuid) { // 状态码不正确,直接返回登录页面 if (!uuid.equals(state)) { return AjaxResult.error("登录出错了"); } } //通过Authorization Code获取Access Token String url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code" + "&client_id=" + APPID + "&client_secret=" + APPKEY + "&code=" + code + "&redirect_uri=" + URL; String access_token = QqHttpClient.getAccessToken(url); //获取回调后的openID url = "https://graph.qq.com/oauth2.0/me?access_token=" + access_token; String openId = QqHttpClient.getOpenID(url); //获取QQ用户信息 url = "https://graph.qq.com/user/get_user_info?access_token=" + access_token + "&oauth_consumer_key=" + APPID + "&openid=" + openId; // 得到用户信息 JSONObject QUser = QqHttpClient.getUserInfo(url); /** * 获取到用户信息之后,业务逻辑 */ //判断数据库是否存在此用户 ThirdParty thirdParty = thirdPartyService.getOne(new QueryWrapper<ThirdParty>().eq("openid", openId)); //不存在 返回请求让前端跳转绑定账号 if (thirdParty == null ){ ThirdParty thirdParty1 = new ThirdParty(); thirdParty1.setLoginType("QQ"); thirdParty1.setOpenid(openId); thirdParty1.setAccessToken(access_token); return AjaxResult.error(505,"第一次登录绑定用户",thirdParty1); } //不是第一次登录 就直接登录 Users users = usersService.getOne(new LambdaQueryWrapper<Users>().eq(Users::getId, thirdParty.getUserId())); if (users == null){ return AjaxResult.error("非法用户,请联系管理员"); } //开始登录 UserDetails userDetails = userDetailsService.loadUserByUsername(users.getUsername()); //更新security登录用户对象 UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null,userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); //生成token String token = jwtTokenUtil.generateToken(users.getUsername()); SuccessUser successUser = new SuccessUser(); HashMap<String,Object> map = new HashMap<>(); Fllow fllow = usersService.queryByRoles(users.getUsername()); Authority authority = new Authority(fllow.getRoles(),"0","","dashboard","",""); successUser.setNickName(QUser.getString("nickname")); successUser.setHeaderImg(QUser.getString("figureurl_2")); successUser.setAuthority(authority); successUser.setSideMode("dark"); successUser.setActiveColor("#1890ff"); successUser.setBaseColor("#fff"); System.out.println(successUser.getAuthority()); map.put("user",successUser); map.put("token", token); return AjaxResult.success("登录成功",map); }绑定当前系统账户
1.像登录接口一样,判断密码
2.判断QQ用户是否一定判定了或者用户已经绑定了QQ
3.绑定,登录
@PostMapping(value = "/bangding") @ApiOperation("QQ第一次登录,绑定存在用户") public AjaxResult bangDing(String username, String password, @RequestBody ThirdParty thirdParty) throws IOException { //登录 //通过username获得userDetails对象 UserDetails userDetails = userDetailsService.loadUserByUsername(username); if(null == userDetails || !passwordEncoder.matches(password,userDetails.getPassword())){ return AjaxResult.error("用户名或密码不正确"); } String encode = passwordEncoder.encode(password); Users users = usersService.getOne(new LambdaQueryWrapper<Users>().eq(Users::getUsername, userDetails.getUsername())); if (users == null){ return AjaxResult.error("登录出错,请联系管理员"); } boolean result = thirdPartyService.checkUserId(users.getId()); if (!result){ return AjaxResult.error("此账号已经绑定了对应的QQ了"); } thirdParty.setUserId(Integer.valueOf(Math.toIntExact(users.getId()))); boolean save = thirdPartyService.save(thirdParty); if (!save){ return AjaxResult.error("绑定出错,请联系管理员"); } // Step4:获取QQ用户信息 String url = "https://graph.qq.com/user/get_user_info?access_token=" + thirdParty.getAccessToken() + "&oauth_consumer_key=" + APPID + "&openid=" + thirdParty.getOpenid(); // 得到用户信息 JSONObject QUser = QqHttpClient.getUserInfo(url); //接下来就是security登录操作,和上面一样 } -
解绑逻辑在于确认用户密码,删除表中对应行就可以了
-
QqHttpClient工具类
jiepublic class QqHttpClient { /** * 获取Access Token */ public static String getAccessToken(String url) throws IOException { CloseableHttpClient client = HttpClients.createDefault(); String token = null; HttpGet httpGet = new HttpGet(url); HttpResponse response = client.execute(httpGet); HttpEntity entity = response.getEntity(); if (entity != null) { String result = EntityUtils.toString(entity, "UTF-8"); if (result.indexOf("access_token") >= 0) { String[] array = result.split("&"); for (String str : array) { if (str.indexOf("access_token") >= 0) { token = str.substring(str.indexOf("=") + 1); break; } } } } httpGet.releaseConnection(); return token; } /** * 获取openID */ public static String getOpenID(String url) throws IOException { JSONObject jsonObject = null; CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse response = client.execute(httpGet); HttpEntity entity = response.getEntity(); if (entity != null) { String result = EntityUtils.toString(entity, "UTF-8"); jsonObject = parseJSONP(result); } httpGet.releaseConnection(); if (jsonObject != null) { return jsonObject.getString("openid"); } else { return null; } } /** * 获取QQ用户信息 */ public static JSONObject getUserInfo(String url) throws IOException { JSONObject jsonObject = null; CloseableHttpClient client = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); HttpResponse response = client.execute(httpGet); HttpEntity entity = response.getEntity(); if (entity != null) { String result = EntityUtils.toString(entity, "UTF-8"); jsonObject = JSONObject.parseObject(result); } httpGet.releaseConnection(); return jsonObject; } /** * 转换json对象 */ private static JSONObject parseJSONP(String jsonp) { int startIndex = jsonp.indexOf("("); int endIndex = jsonp.lastIndexOf(")"); String json = jsonp.substring(startIndex + 1, endIndex); return JSONObject.parseObject(json); } }
总结
之前没接触过就感觉很难,主要是没思路,在网上搜了很多文档几乎都没有找到自己想要的。写完之后才感觉这么简单,说明了什么,人总会对自己的不同的领域有一定的恐惧但是也会有更大的好奇,只要好奇大过恐惧勇敢探索就会变得很简单咯。
希望能帮助到你,希望你也能年薪百万,不脱发,头发茂密。
自我介绍
本人大二,二本院校,热爱学习,编程。有没有哥哥推荐暑假的日常实习,就是算法很垃圾,其他的应该能接受。