likes
comments
collection
share

OAuth2.0 第三方授权登录(微信和Gitee)

作者站长头像
站长
· 阅读数 13

1. OAuth2.0 简介

全称:Open Authorization

OAuth(Open Authorization)是一个关于授权(authorization)的开放网络标准,允许用户授权第三方 应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方。 OAuth在全世界得到广泛应用,目前的版本是2.0版

特点:

  1. 简单:不管是OAuth服务提供者还是应用开发者,都很易于理解和使用
  2. 安全:没有涉及到用户密钥等信息,更安全灵活
  3. 开放:任何服务提供商都可以实现OAuth,任何软件开发上商都可以使用OAuth

四种模式

  • 授权码模式(Authorization Code):OAuth2 中目前最安全最复杂也是最常用的模式

  • 隐式授权模式(Implicit Grant):

  • 用户命密码模式(Resource Owner Passowrd Credentials grant ):密码凭证授权

  • 客户端模式(Client Credientials Grant):客户端授权

2. OAuth2.0 优点

OAuth登录的优点

  • 第三方登录简单快捷,面对不同平台不同的用户名和密码的问题,第三方登 录正好解决这个问题,几乎可以直接一个账号搞定所有
  • 第三方登录还可以将自己在某个应用的动态信息同步到当前应用下,无需再为每个应用重新写个人资料
  • 第三方登录有很多资料信息可以公用(比如头像和昵称),通常对于敏感资料如手机、邮箱是第三方平台是不会提供的,所以安全信息可以放心

OAuth为企业带来的价值

  • 简化登录过程,降低注册门槛,更能获取海量用户,有效降低了用户的流失。本地注册的稳定+第三方登 录的便捷才是最合适的登录方案

  • 第三方登录接入后,应用可直接获取用户昵称、头像、用户D等信息,方便产品获取用户的基本资料,减少产品设计成本

  • 目前市面上的短信验证码的价钱约在0.05元左右,当用户选择使用第三方登录时,可有效减少产品的登 录成本

OAuth为第三方提供商带来的价值

  • 增加用户对平台(QQ\微信\支付宝\Google)的依赖,用户越多使用本平台的第三方登录,就代表着平台对该用户的粘性越高

  • 获得更广泛的影响力,只要用户使用提供第三方登录的应用,那么这个提供第三方登录的品牌就会被用户浏览,有利于对平台的拉新和促活

3 OAuth2.0 角色

角色作用
客户端本身不存储资源,需要通过资源拥有者去请求资源服务器的资源。APP,游戏,影视网站...
资源拥有者通常为用户,也可以是应用程序,即资源的
授权服务器用于服务提供商对资源拥有者的身份进行认证、对访问资源进行授权发放授权码(code)。认证成功后通过授权码请求发放令牌(token),作为客户端访问资源服务器的凭据。
资源服务器存储资源的服务器,比如微信端存储的用户信息

4 OAuth2.0 授权码模式

OAuth2 中目前最安全最复杂也是最常用的模式,执行流程如下

OAuth2.0 第三方授权登录(微信和Gitee)

5 二维码生成

5.1 依赖要求

<!--Hutool:JaVa工具包类库,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Ui工具类。-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.15</version>
</dependency>
<!--生成二维码-->
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.3.3</version>
</dependency>

5.2 生成二维码示例

  • 普通生成:四个参数:Url,宽,高,输出路径
QrCodeUtil.generate(
    "https://warriorsgo.netlify.app",300,300, FileUtil.file("d:/qrcode.jpg")
);
  • 配置生成:配置信息较多时推荐将配置信息写入配置对象再生成
//配置参数较多时使用配置生成
QrConfig config = new QrConfig(1000, 1000);//新建配置文件并指定宽高
config.setErrorCorrection(ErrorCorrectionLevel.Q);//指定纠错级别
QrCodeUtil.generate(
    "https://warriorsgo.netlify.app",config, FileUtil.file("d:/qrcode.jpg")
);

其中:纠错级别 ErrorCorrectionLevel有四个等级:L,M,Q,H

从左到右:纠错等级提高,即使被遮挡一小部分也能识别成功,单位像素块面积减小,像素块数量增多

  • 其他配置
配置方法作用
setBackColor(Color.XXX)设置背景颜色
setForeColor(Color.XXX)设置像素块颜色
setImg(ImgPath)设置中心logo图标
setMargin(margin)设置边缘宽度
setHeight(h)/setWidth(w)设置宽高

6 Cpolar 内网穿透工具

cpolar极点云: 公开一个本地Web站点至公网

作用:只需一行命令,就可以将内网站点发布至公网,方便给客户演示。高效调试微信公众号、小程序、对接支付宝网关等云端服务,提高您的编程效率。

  • 登录注册下载安装

download下载 - cpolar 极点云www.cpolar.com/download

  • 获取cpolar账号Token

cpolar - secure introspectable tunnels to localhostdashboard.cpolar.com/auth

  • 命令行运行cpolar,验证token
cpolar authtoken MGU1OWY4NWItXXXXXXXXXXXXXXXX
  • 实现简单内网穿透:cpolar 协议名 内网端口
cpolar http 8080

OAuth2.0 第三方授权登录(微信和Gitee)

注意:每次重启后随机域名会改变!

Forwarding里面的域名就是公网域名,可以给其他外网设备访问。

7 OAuth2.0 - 微信登录

7.1 准备工作

扫码登陆微信有两种实现方式

  1. 基于微信公众平台的扫码登录

    让第三方应用投入微信的怀抱而设计的,这第三方应用指的是比如android、ios、网站、系统等;

  2. 基于微信开放平台的扫码登录(For common people) 为了让程序员小伙伴利用微信自家技术(公众号、小程序)开发公众号、小程序而准备的。

区别 微信开放平台需要开企业认证才能注册。 微信公众平台需要认证微信服务号,才能进行扫码登录的开发。只需申请一个公众号。

对于初学者,即没有企业认证,也不一定有自己的公众号,就只能使用测试公众号

测试公众号申请地址微信公众平台 (qq.com)

mp.weixin.qq.com/debug/cgi-b…

1. 接口配置信息

接入概述 | 微信开放文档 (qq.com)

参数说明
URL此处要加协议,以及对应的检验接口
token随意设置自定义token

示例:

URL : 63c27f47.r7.cpolar.top/wechat/chec…

Token :AASAdd

2. 网页授权获取用户基本信息 - 修改回调域名

在本网页里面找到 - 网页服务 - 网页账号 - 网页授权获取用户基本信息 -修改:授权回调页面域名

  • 回调域名作用

    用户在网页授权页同意授权给公众号后,微信会将授权数据传给一个回调页面,回调页面需在此域名下,以确保安全可靠。沙盒号回调地址支持域名和ip,正式公众号回调地址只支持域名

  • 注意:此处填写域名,不是URL!无需https:// 等协议头

示例

回调地址:63c27f47.r7.cpolar.top

7.2 实现细节

网页授权 | 微信开放文档 (qq.com)

基本步骤:

网页授权流程分为四步:

  1. 引导用户进入授权页面同意授权,获取code
    1. 授权页面可以以二维码或者前端请求跳转的形式给用户
    2. 授权链接参数解释:网页授权 | 微信开放文档 (qq.com)
  2. 通过code换取网页授权access token(与基础支持中的access_token不同)
  3. 如果需要,开发者可以刷新网页授权access_token,避免过期
  4. 通过网页授权access token和openid获取用户基本信息(支持UnionID机制)

7.2.0 微信接口校验

编写微信验证的测试接口方法:

@Controller
@RequestMapping("/wechat")
public class WeChatController {


    @GetMapping("/check")
    @ResponseBody
    public String WXCheck(@RequestParam("signature") String signature,
                          @RequestParam("timestamp") String timeStamp,
                          @RequestParam("nonce") String nonce,
                          @RequestParam("echostr") String echoStr){
        System.out.println("hello");
        System.out.println("echostr = " + echoStr);
        return echoStr;
    }
    ...
}

developers.weixin.qq.com/doc/offiacc…

完成 7.1 准备工作中的 URL,TOKEN,回调域名 的数据填写,配置后点击测试,显示 配置成功 则可进入下一步

示例

URL : 63c27f47.r7.cpolar.top/wechat/chec…

Token :AASAdd

回调地址:63c27f47.r7.cpolar.top

7.2.1 进入授权页

用户点击授权之后,服务商服务器发起一个携带授权码code的Get请求,指向设置的回调地址(../wechat/auth)

@Controller
@RequestMapping("/wechat")
public class WeChatController {
	//登录二维码显示
    @GetMapping("/login")
    public void wxLogin(HttpServletResponse response) throws IOException {
        response.setContentType("image/png");
       QrCodeUtil.generate(WeChatUtil.getUrl(),400,400,"jpg",response.getOutputStream());
    }

	//回调接口
    @GetMapping("/auth")
    public Result callBack(String code, String state, HttpServletRequest request,
                           HttpServletResponse response, HttpSession session) throws IOException {
        WeChatToken weChatToken = WeChatUtil.getToken(code);
        System.out.println("token = " + weChatToken.getAccessToken() +"\n" +"getOpenId = " + weChatToken.getOpenid());

        return weChatToken.getErrCode() == 40029?
                new Result("fail", weChatToken.getErrMsg()):
                new Result("success", weChatToken);
    }
    
}

7.2.3 code换token

通过code换取token

//回调接口
@GetMapping("/auth")
public Result callBack(String code, String state, HttpServletRequest request,
                       HttpServletResponse response, HttpSession session) throws IOException {
    WeChatToken weChatToken = WeChatUtil.getToken(code);
    System.out.println("token = " + weChatToken.getAccessToken() +"\n" +"getOpenId = " + weChatToken.getOpenid());

    return weChatToken.getErrCode() == 40029?
        new Result("fail", weChatToken.getErrMsg()):
    	new Result("success", weChatToken);
}


7.2.4 token换用户数据

@GetMapping("/info")
    public Result getInfo(String token,String openId) throws IOException {
        WeChatUser user = WeChatUtil.getInfo(token, openId);
        System.out.println("user = " + user);

        return user.getErrCode()==40003?
                new Result("fail","获取失败"):
                new Result("success",user);
}

7.2.5 token刷新

@GetMapping("/refresh")
public Result refresh(String refreshToken) throws IOException {
    WeChatToken weChatToken = WeChatUtil.refreshToken(refreshToken);

    return weChatToken.getErrCode()==40029?
            new Result("fail","获取失败"):
            new Result("success", weChatToken);
}

7.2.6 完整代码

Result
package com.example.domain;

import lombok.Data;

@Data
public class Result {
    private String status;//请求状态
    private Object data;//数据
    private String msg;//信息

    public Result() {
    }

    public Result(String status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    public Result(String status, Object data) {
        this.status = status;
        this.data = data;
    }
}
WeChatToken
package com.example.domain;

import lombok.Data;

@Data
public class WeChatToken {
    private String accessToken;//网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
    private String expiresIn;//access_token接口调用凭证超时时间
    private String refreshToken;//用户刷新access_token
    private String openid;//用户唯一标识
    private String scope;//用户授权的作用域
    private String isSnapshotUser;//是否为快照页模式虚拟账号
    private String unionId;//用户统一标识(针对一个微信开放平台帐号下的应用,同一用户的 unionid 是唯一的),只有当scope为"snsapi_userinfo"时返回
    private Integer errCode;//错误码
    private String errMsg;//错误信息
}
WeChatUser
package com.example.domain;

import lombok.Data;

@Data
public class WeChatUser {
    private String openid;//用户唯一id
    private String nickname;//微信昵称
    private Integer sex;//性别
    private String language;//语言
    private String city;//城市
    private String province;//省份
    private String country;//国家
    private String headImgUrl;//头像url
    private String[] privilege;//特权
    private String unionID;//用户综合id
    private Integer errCode;//错误码
    private String errMsg;//错误信息
}
WechatUtil

其中:RedirectUri是使用cpolar内网穿透形成的一个外网地址,微信无法验证localhost地址,请在微信开放平台和此次将域名和URL修改为自己的服务器指定地址或者内网穿透地址

public class WeChatUtil {
    //常量区
    public static final String AppId = "wxxx1234567";
    public static final String AppSecret = "1122344555566678";
    public static final String RedirectUri = "https://http://63c27f47.r7.cpolar.top//wechat/auth";63c27f47.r7.cpolar.top
    //RedirectUri是使用cpolar内网穿透形成的一个外网地址,微信无法验证本地地址,请在微信开放平台和此次将域名和URL修改为自己的服务器指定地址或者内网穿透地址


    //http请求客户端
    static CloseableHttpClient httpClient = HttpClientBuilder.create().build();

    //方法区
    /**
     * Get encoded WeChat Authorization Url which embedded AppID and so on
     * @return completed url
     */
    public static String getUrl(){
        //url要转为 UrlEncode编码格式
        String CodedRedirectUri = URLEncoder.encode(RedirectUri, StandardCharsets.UTF_8);
        return "https://open.weixin.qq.com/connect/oauth2/authorize?" +
                "appid="+AppId+
                "&redirect_uri="+CodedRedirectUri+
                "&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect&forcePopup=true";
    }

    /**
     * Get token object by code
     * @param code 授权码
     * @return 返回 WeChatToken 对象
     * @throws IOException IO异常
     */
    public static WeChatToken getToken(String code) throws IOException {
        String tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?"+
                "appid="+AppId+
                "&secret="+AppSecret+
                "&code="+code+
                "&grant_type=authorization_code";
        //get请求对象装入url
        HttpGet httpGet = new HttpGet(tokenUrl);
        String responseResult = "";
        //发起请求 如果请求成功 则接收返回的数据转为UTF-8格式
        HttpResponse response = httpClient.execute(httpGet);
        if (response.getStatusLine().getStatusCode()==200) {
            responseResult = EntityUtils.toString(response.getEntity(),"UTF-8");
        }
        //将结果封装到WechatToken对象 并返回
        return JSON.parseObject(responseResult, WeChatToken.class);
    }

    /**
     * Refresh token
     * @param token:not accessToken,this is refresh token
     * @return new Token Object
     * @throws IOException IO异常
     */
    public static WeChatToken refreshToken(String token) throws IOException {
        String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?" +
                "appid="+AppId+
                "&grant_type=refresh_token" +
                "&refresh_token="+token;
        HttpGet httpGet =new HttpGet(url);
        //执行刷新
        HttpResponse response = httpClient.execute(httpGet);
        String jsonStr = "";
        if (response.getStatusLine().getStatusCode()==200) {
            jsonStr = EntityUtils.toString(response.getEntity());
        }
        return JSON.parseObject(jsonStr, WeChatToken.class);
    }

    /**
     * Get user information by access_token and openID
     * @param accessToken 用户访问token
     * @param openId 用户唯一id
     * @return 微信用户对象
     * @throws IOException IO异常
     */
    public static WeChatUser getInfo(String accessToken,String openId) throws IOException{
        String url = "https://api.weixin.qq.com/sns/userinfo?" +
                "access_token="+accessToken+
                "&openid=" +openId+
                "&lang=zh_CN";
        HttpGet httpGet = new HttpGet(url);
        String jsonStr = "";
        //执行请求
        CloseableHttpResponse response = httpClient.execute(httpGet);
        if (response.getStatusLine().getStatusCode()==200) {
            jsonStr = EntityUtils.toString(response.getEntity());
        }
        System.out.println("jsonStr = " + jsonStr);
        return JSON.parseObject(jsonStr,WeChatUser.class);
    }
}
WeChatController
@RestController
@RequestMapping("/wechat")
public class WeChatController {

    /**
     * check the link status to WeChat Public Platform
     * @param signature signature
     * @param timeStamp timeStamp
     * @param nonce nonce
     * @param echoStr echoStr
     * @return the same echoStr
     */
    @GetMapping("/check")
    public String WXCheck(@RequestParam("signature") String signature,
                          @RequestParam("timestamp") String timeStamp,
                          @RequestParam("nonce") String nonce,
                          @RequestParam("echostr") String echoStr){
        return echoStr;
    }

    /**
     * Generate QRCode jumped to WeChat Authorization website
     * @param response 返回图片流
     * @throws IOException IO异常
     */
    @GetMapping("/login")
    public void wxLogin(HttpServletResponse response) throws IOException {
        response.setContentType("image/png");
        QrCodeUtil.generate(WeChatUtil.getUrl(),400,400,"jpg",response.getOutputStream());
    }

    /**
     * Authorize code to get token
     * @param code code
     * @param state state
     * @param request request
     * @param response response
     * @param session session
     * @return result
     * @throws IOException io
     */
    @GetMapping("/auth")
    public Result callBack(String code, String state, HttpServletRequest request,
                           HttpServletResponse response, HttpSession session) throws IOException {
        WeChatToken weChatToken = WeChatUtil.getToken(code);
        System.out.println("token = " + weChatToken.getAccessToken() +"\n" +"getOpenId = " + weChatToken.getOpenid());

        return weChatToken.getErrCode() == 40029?
                new Result("fail", weChatToken.getErrMsg()):
                new Result("success", weChatToken);
    }

    /**
     * Refresh token
     * @param refreshToken refreshToken
     * @return new token
     * @throws IOException io
     */
    @GetMapping("/refresh")
    public Result refresh(String refreshToken) throws IOException {
        WeChatToken weChatToken = WeChatUtil.refreshToken(refreshToken);

        return weChatToken.getErrCode()==40029?
                new Result("fail","获取失败"):
                new Result("success", weChatToken);
    }

    /**
     * Get user information
     * @param token token
     * @param openId openID
     * @return user info
     * @throws IOException io
     */
    @GetMapping("/info")
    public Result getInfo(String token,String openId) throws IOException {
        WeChatUser user = WeChatUtil.getInfo(token, openId);
        System.out.println("user = " + user);

        return user.getErrCode()==40003?
                new Result("fail","获取失败"):
                new Result("success",user);

    }
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>HELLO</title>
</head>
<body>
  <h1>LOGIN</h1>
  <a href="/wechat/login">微信登录</a>
</body>
</html>

8 OAuth2.0 - Gitee

8.1 准备工作

Gitee OAuth 文档

在Gitee官网登录后找到设置里面的第三方应用 创建第三方应用 - Gitee.com

gitee.com/oauth/appli…

  • 保存 ClientIDClient Secret

  • 设置回调地址:Gitee和微信不一样,无需内网穿透,可以设置本地地址

    http://127.0.0.1:8080/gitee/auth
    
  • 主页地址作用不大,可随意填写

    http://127.0.0.1:8080/page/main.html
    
  • 权限:设置你的应用将要向用户索取的权限

  • 其余参数请自定义

8.2 实现细节

和 微信的OAuth2.0授权大致相同

Result

@Data
public class Result {
    private String status;//请求状态
    private Object data;//数据
    private String msg;//信息

    public Result() {
    }

    public Result(String status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    public Result(String status, Object data) {
        this.status = status;
        this.data = data;
    }
}

GiteeUser

@Data
public class GiteeUser {
    private String id;//id
    private String name;//用户名
    private String email;//邮箱
    private String avatarUrl;//用户头像
    private String url;//json-数据
    private String htmlUrl;//json-用户主页
    private String starredUrl;//json-用户收藏
    private String blog;//用户博客连接
    private String weibo;//绑定微博
    private String createdAt;//账号创建日期
    private Date updatedAt;//最近项目活跃时间
}

GiteeToken

@Data
public class GiteeToken {
    private String accessToken;//token
    private String tokenType;//token类型
    private String expiresIn;//token过期时长 86400s = 1day
    private String refreshToken;//刷新token
    private String scope;//权限范围
    private String createdAt;//token创建时间戳
    private String error; //错误
    private String errorDescription;//错误信息
}

GiteeUtil

public class GiteeUtil {

    public static final String ClientID = "8e3exxxxxxxxxxxxxxxxxx";
    public static final String ClientSecret = "88efc2c187exxxxxxxx";
    public static final String RedirectUri = "http://127.0.0.1:8080/gitee/auth"; //回调地址
//    public static final String RedirectUri = "https://2dbfb5d2.r7.cpolar.top/gitee/auth"; //回调地址
    public static CloseableHttpClient httpClient = HttpClientBuilder.create().build();

    /**
     * 拼接 URL
     * @return url
     * @throws UnsupportedEncodingException Exception
     */
    public static String getUrl() throws UnsupportedEncodingException {
        String url = URLEncoder.encode(RedirectUri, StandardCharsets.UTF_8);
        return "https://gitee.com/oauth/authorize?client_id=" + ClientID + "&redirect_uri=" + url + "&response_type=code";
    }

    /**
     * 发起请求获取Token
     * @param code 校验码
     * @return 返回数据
     * @throws Exception exception
     */
    public static GiteeToken getToken(String code) throws Exception {
        //新建httpClient对象 新建post请求对象
        HttpPost postRequest = new HttpPost("https://gitee.com/oauth/token");

        //post请求对象传入值
        StringEntity input = new StringEntity(
                        "grant_type=authorization_code&" +
                        "code=" + code +
                        "&client_id=" + ClientID +
                        "&redirect_uri=" + RedirectUri +
                        "&client_secret=" + ClientSecret);

        input.setContentType("application/x-www-form-urlencoded");
        postRequest.setEntity(input);

        //httpClient执行 post请求 并获取返回内容·
        HttpResponse response = httpClient.execute(postRequest);
        HttpEntity entity = response.getEntity();
        String jsonStr = EntityUtils.toString(entity);
        System.out.println("JSONSTR: "+jsonStr);
        //控制台
        GiteeToken gt = JSON.parseObject(jsonStr,GiteeToken.class);

        return gt;
    }

    /**
     * 刷新 Gitee 的 token
     * @param refreshToken 先前获取到的refreshToken
     * @return 返回新的 GiteeToken 对象
     * @throws IOException exception
     */
    public static GiteeToken RefreshToken(String refreshToken) throws IOException {
        HttpPost postRequest = new HttpPost("https://gitee.com/oauth/token");

        StringEntity input = new StringEntity("grant_type=refresh_token&refresh_token=" + refreshToken);
        input.setContentType("application/x-www-form-urlencoded");

        //数据传入方法体
        postRequest.setEntity(input);

        //执行请求
        HttpResponse response = httpClient.execute(postRequest);
        HttpEntity entity = response.getEntity();
        String jsonStr = EntityUtils.toString(entity);

        System.out.println(jsonStr);

        //将JSON数据实例化为GiteeToken对象
        return JSON.parseObject(jsonStr, GiteeToken.class);
    }

    /**
     * 根据用户的token获取用户的信息
     * @param token access_token
     * @return W
     * @throws IOException exception
     */
    public static GiteeUser getInfo(String token) throws IOException {

        //配置
        HttpGet emailGet = new HttpGet("https://gitee.com/api/v5/emails?access_token="+token);
        HttpGet userGet = new HttpGet("https://gitee.com/api/v5/user?access_token="+token);

        //执行请求获取内容
        HttpEntity entity1 = httpClient.execute(emailGet).getEntity();
        HttpEntity entity2 = httpClient.execute(userGet).getEntity();

        //获取邮箱
        String jsonStr4email = EntityUtils.toString(entity1);
        String substring = jsonStr4email.substring(1, jsonStr4email.length()-1);
        String email = JSON.parseObject(substring).getString("email");

        //获取用户
        String jsonStr4User = EntityUtils.toString(entity2);
        GiteeUser giteeUser = JSON.parseObject(jsonStr4User, GiteeUser.class);
        giteeUser.setEmail(email);//将邮箱信息添加至用户

        return giteeUser;
    }
}

GiteeController

@Controller
@RequestMapping("/gitee")
public class GiteeController {

    /**
     * 拼接访问地址
     * @return 跳转到拼接了clientID的url
     */
    @GetMapping("/login")
    public String giteeLogin() throws UnsupportedEncodingException {
        return "redirect:"+GiteeUtil.getUrl();
    }

    /**
     * Gitee 登录校验
     * @param code 授权校验码
     * @param session session
     * @return res
     * @throws Exception io
     */
    @GetMapping("/auth")
    @ResponseBody
    public Result giteeAuth(@RequestParam("code") String code, HttpSession session) throws Exception {

        GiteeToken giteeToken = GiteeUtil.getToken(code);
        String token = giteeToken.getAccessToken();
        System.out.println("giteeToken.toString() = " + giteeToken.toString());
        return giteeToken.getError() ==null ?
                new Result("success",giteeToken):
                new Result("fail",giteeToken.getErrorDescription());
    }

    /**
     * Refresh token
     * @param refreshToken 先前 GiteeToken的refreshToken
     * @return 新 GiteeToken对象
     * @throws IOException io
     */
    @GetMapping("refresh")
    @ResponseBody
    public Result refreshToken(String refreshToken) throws IOException {
        GiteeToken newGiteeToken = GiteeUtil.RefreshToken(refreshToken);

        //返回刷新情况
        return newGiteeToken.getError()!=null?
                new Result("fail",newGiteeToken.getErrorDescription()):
                new Result("success",newGiteeToken);
    }

    /**
     * Get gitee user information
     * @param token token
     * @return res
     * @throws IOException io
     */
    @GetMapping("/info")
    @ResponseBody
    public Result getInfo(String token) throws IOException {
        return new Result("success",GiteeUtil.getInfo(token));
    }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>HELLO</title>
</head>
<body>
  <h1>LOGIN</h1>
  <a href="/gitee/login">Gitee登录</a>
</body>
</html>