社交登录(Gitee)

完成这个踩了很多坑,包括第一时间忘了浏览器缓存这回事以及微博一些平台申请社交登录很复杂,所以写这篇博客,主要记录一些前后端代码,方便以后偷个懒,可以直接复制粘贴

OAuth:

  • OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储 在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们 数据的所有内容。
  • OAuth2.0:对于用户相关的 OpenAPI(例如获取用户信息,动态同步,照片,日志,分 享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向 用户征求授权

image-20220801140400876

使用 Gitee 第三方登录

需要注意的是:在测试过程中,由于本地服务器会有缓存,所以建议开启浏览器的 无痕模式 或 手动清理缓存 进行测试

官方文档:Gitee OAuth 文档

1.模拟请求

image-20220801141008554

创建应用-》模拟登录

在这里插入图片描述

获取这个请求url(后面需要放到前端去)(如果直接转到了重定向的网页,清理浏览器缓存)

在这里插入图片描述

同意授权,登录成功后会跳转到

http://gulimall.com/success?code=9c193c73f06f0efda5d88bda7ef5b18cb92f1b7750e941ef444b4b75269e8331

获取到code:

image-20220801145414062

https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}在postman中填入参数 获取token

image-20220801150757997

token 的过期时间是一天,可以通过下面的方式重写获取token

https://gitee.com/oauth/token?grant_type=refresh_token&refresh_token={refresh_token}

在 gitee 的官方 api 中,可以通过 token 获取用户数据

image-20220801151758954

image-20220801151905252

项目整合

1.数据库增加字段

在user 表中添加以下字段

image-20220801152555283

2.前端

将前面复制的 请求uri 放进来,同时放好对应的 logo

修改前端页面:

image-20220801145141008

2.后端

后端代码中使用本地 session 存储,如果是分布式 session,参考文章:分布式session ——Spring Session 实战解决 子域之间的 session 共享问题、不同服务器 session 共享解决方案_HotRabbit.的博客-CSDN博客

对应实体类添加字段

image-20220801153337828

这里用的是两个服务模块间的互相调用,所以新建一个 to 用于在模块间传递数据:

package com.henu.soft.merist.common.to;

import lombok.Data;

@Data
public class SocialUserTo {

    /**
     * 令牌
     */
    private String access_token;


    /**
     * 令牌过期时间
     */
    private long expires_in;

    /**
     * 该社交用户的唯一标识
     */
    private String id;


    /**
     * 第三方用户名称
     */
    private String name;

    /**
     * 头像
     */
    private String  avatar_url;

}

这里是模拟分布式完成了一个远程调用的 OAuth 登录,分为 auth(认证模块)和 member(会员 用户模块)

可以根据需求复制相应代码

Auth 模块功能:

  • 前端页面转到 gitee 的授权页面之后,用户授权,会调用此接口
  • 根据 code 发送 post 请求 获取 token
  • 根据 token 发送 get 请求 获取到 用户信息(TO)
  • 将用户信息(TO)发送给 member 模块进行登录功能
  • 以及根据 返回值 重定向

gitee 官方 api 根据 token 获取到的用户信息(官方还有各种接口如库的查询等,API文档:Gitee API 文档):

{
    "id": XXX,
    "login": "XXX",
    "name": "XXX",
    "avatar_url": "https://gitee.com/assets/no_portrait.png",
    "url": "https://gitee.com/api/v5/users/XXX",
    "html_url": "https://gitee.com/XXX",
    "remark": "",
    "followers_url": "https://gitee.com/api/v5/users/XXX/followers",
    "following_url": "https://gitee.com/api/v5/users/XXX/following_url{/other_user}",
    "gists_url": "https://gitee.com/api/v5/users/XXX/gists{/gist_id}",
    "starred_url": "https://gitee.com/api/v5/users/XXX/starred{/owner}{/repo}",
    "subscriptions_url": "https://gitee.com/api/v5/users/XXX/subscriptions",
    "organizations_url": "https://gitee.com/api/v5/users/XXX/orgs",
    "repos_url": "https://gitee.com/api/v5/users/XXX/repos",
    "events_url": "https://gitee.com/api/v5/users/XXX/events{/privacy}",
    "received_events_url": "https://gitee.com/api/v5/users/XXX/received_events",
    "type": "User",
    "blog": null,
    "weibo": null,
    "bio": null,
    "public_repos": 2,
    "public_gists": 0,
    "followers": 0,
    "following": 0,
    "stared": 5,
    "watched": 5,
    "created_at": "2021-07-15T10:40:04+08:00",
    "updated_at": "2022-08-01T21:04:10+08:00",
    "email": null
}

实现代码:

//用户同意授权 使用 code 获取 token,使用 token 获取 用户数据
// http://auth.gulimall.com/oauth2/gitee/success
@RequestMapping("/oauth2/gitee/success")
public String authorize(@RequestParam("code") String code, HttpSession session) throws Exception {
    //1.使用code换取token,换取成功继续2,否则重定向至登录页
    Map<String,String> query = new HashMap<>();
    query.put("client_id","d2067836210bcb3746b3a31e67a1eb86bad151a788c9cda1885ee64b3e2da233");
    query.put("client_secret","8bcb79c723f68eb4bc0bffd13a12dd5193ce527bb5dd4b62afb8465af0c5e002");
    query.put("grant_type","authorization_code");
    query.put("redirect_uri","http://auth.gulimall.com/oauth2/gitee/success");
    query.put("code",code);

    //2.发送 post 请求获取 token
    //https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
    HttpResponse response = HttpUtils.doPost("https://gitee.com", "/oauth/token", "post", new HashMap<String, String>(), query, new HashMap<String, String>());
    Map<String,String> errors = new HashMap<>();
    if (response.getStatusLine().getStatusCode() == 200){
        //3.调用 member 远程接口进行 oauth 登录,登录成功则转发至首页并携带返回用户信息,否则转发至登录页
        String json = EntityUtils.toString(response.getEntity());
        SocialUserTo socialUserTo = JSON.parseObject(json, new TypeReference<SocialUserTo>() {
        });
        //拿着 accessToken 查询用户信息
        if (socialUserTo != null && (!StringUtils.isEmpty(socialUserTo.getAccess_token()))){

            Map<String,String> queryAccessToken = new HashMap<>();
            queryAccessToken.put("access_token",socialUserTo.getAccess_token());

            Map<String,String> queryHeader = new HashMap<>();
            queryHeader.put("Content-Type","application/json;charset=UTF-8");

            //发送请求
            //https://gitee.com/api/v5/user?access_token={access_token}
            HttpResponse response1 = HttpUtils.doGet("https://gitee.com","/api/v5/user","get",queryHeader,queryAccessToken);;
            if (response1.getStatusLine().getStatusCode() == 200){
                //请求成功
                String json1 = EntityUtils.toString(response1.getEntity());

                //获取 user_info 的 id
                //转成 SocialUserTo 方便调用服务
                SocialUserTo socialUserTo1 = JSON.parseObject(json1, new TypeReference<SocialUserTo>() {
                });
                socialUserTo1.setAccess_token(socialUserTo.getAccess_token());
                socialUserTo1.setExpires_in(socialUserTo.getExpires_in());

                //TODO 社交帐号登录与注册为一体
                //================远程调用登录==================
                R login = memberFeignService.oauthLogin(socialUserTo1);
                //2.1 远程调用成功,返回首页并携带用户信息
                if (login.getCode() == 0){
                    String jsonString = JSON.toJSONString(login.get("memberEntity"));
                    MemberResponseTo memberResponseTo = JSON.parseObject(jsonString, new TypeReference<MemberResponseTo>() {
                    });
                    session.setAttribute(AuthServerConstant.LOGIN_USER,memberResponseTo);
                    return "redirect:http://gulimall.com";
                }else {
                    //2.2 否则返回登录页
                    errors.put("msg","登录失败,请重试");
                    session.setAttribute("errors",errors);
                    return "redirect:http://auth.gulimall.com/login.html";
                }
            }else {
                errors.put("msg","获取第三方授权失败,请重试(获取用户信息失败)");
                session.setAttribute("errors",errors);
                return "redirect:http://auth.gulimall.com/login.html";
            }
        }
    }
    errors.put("msg","获取第三方授权失败,请重试(获取token失败)");
    session.setAttribute("errors",errors);
    return "redirect:http://auth.gulimall.com/login.html";
}

Member 模块功能:

  • 第一次登录:进行注册 根据 TO 中的 token 获取用户数据(名称、头像等)放到 Entity 中,数据库插入数据
  • 重复登录:根据 TO 中的值更新数据库
  • 返回 Entity 供 Auth 模块判断
/**
 * 社交账号登陆 第一次登录(附带注册)
 * auth-server 传递的 SocialUserTo 包含
 * - id:第三方用户唯一标识
 * - token:令牌
 * - expires_in:失效时间
 * - name:用户昵称
 * - avatar_url:用户头像
 * @param socialUserTo
 * @return
 */
@Override
public MemberEntity oauthLogin(SocialUserTo socialUserTo) {
    MemberEntity entityByUid = this.getOne(new QueryWrapper<MemberEntity>().eq("social_uid", socialUserTo.getId()));
    //1.如果之前未登录过,封装社交信息
    if (entityByUid == null){

        String json = null;
        try {

            Map<String,String> queryAccessToken = new HashMap<>();
            queryAccessToken.put("access_token",socialUserTo.getAccess_token());

            Map<String,String> queryHeader = new HashMap<>();
            queryHeader.put("Content-Type","application/json;charset=UTF-8");

            //发送请求
            //https://gitee.com/api/v5/user?access_token={access_token}
            HttpResponse response1 = HttpUtils.doGet("https://gitee.com","/api/v5/user","get",queryHeader,queryAccessToken);


            json = EntityUtils.toString(response1.getEntity());
        } catch (Exception e) {
            e.printStackTrace();
        }

        entityByUid = new MemberEntity();
        entityByUid.setAccessToken(socialUserTo.getAccess_token());
        entityByUid.setSocialUid(socialUserTo.getId());
        entityByUid.setExpiresIn(socialUserTo.getExpires_in());
        MemberLevelEntity defaultLevel = memberLevelService.getOne(new QueryWrapper<MemberLevelEntity>().eq("default_status", 1));
        entityByUid.setLevelId(defaultLevel.getId());

        // 第三方信息
        JSONObject jsonObject = JSON.parseObject(json);
        // 昵称、头像
        String name = jsonObject.getString("name");
        String profile_image_url = jsonObject.getString("avatar_url");
        //这个 service 查询的
        entityByUid.setNickname(name);
        entityByUid.setHeader(profile_image_url);

        this.save(entityByUid);
    }else {
        //2.否则更新令牌等信息并返回
        entityByUid.setAccessToken(socialUserTo.getAccess_token());
        entityByUid.setSocialUid(socialUserTo.getId());
        entityByUid.setExpiresIn(socialUserTo.getExpires_in());
        entityByUid.setHeader(socialUserTo.getAvatar_url());
        entityByUid.setNickname(socialUserTo.getName());
    }
    return entityByUid;
}
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐