小白也能懂的 linuxdo oauth2 快速上手

是否想试试给自己服务接上始皇论坛的认证,却因为没接触过而无从下手?
轻松动手,不用工具,使用 curl 就能尝尝成功的滋味,赶紧来试试吧!

  1. 取得 code
    这边就拿始皇这篇的测试 client id 来使用。
    直接打开始皇范例的 url,同意授权之后,导向 localhost ,没有架设服务的你,当然是打不开的。但是你可以手动从 url 取得你的 code

http://localhost:8181/oauth2/callback?code=democode&state=ttt1


  1. 有了 code 之后呢,就可以从 https://connect.linux.do/oauth2/token 接口领取 refresh tokenaccess token 。搞过 gpt 的佬友们想必对这两个名词都有 PTSD 或 c号戒断症 了。没错,就是熟悉的老朋友,access 有较短的有效期,而 refresh 则能刷新后者。
    在 token 接口里,我们需要把 client id 跟 secret 中间以冒号间隔,编码成base64,然后附在 header 里。
    始皇文章提供的测试用数值举例:hi3geJYfTotoiR5S62u3rh4W5tSeC5UG:VMPBVoAfOB5ojkGXRDEtzvDhRLENHpaN
    你可以使用 windows 内建的编码工具
certutil -f -encode "C:\输入.txt" "C:\输出.txt"

或者你懒得创 txt 打指令,使用线上工具

我们就能得到结果为:

aGkzZ2VKWWZUb3RvaVI1UzYydTNyaDRXNXRTZUM1VUc6Vk1QQlZvQWZPQjVvamtHWFJERXR6dkRoUkxFTkhwYU4=

带进 header 里,就能向 token 请求:

curl -X POST -H "Authorization: Basic aGkzZ2VKWWZUb3RvaVI1UzYydTNyaDRXNXRTZUM1VUc6Vk1QQlZvQWZPQjVvamtHWFJERXR6dkRoUkxFTkhwYU4=" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&code=你的代码&redirect_uri=http://localhost:8181/oauth2/callback" https://connect.linux.do/oauth2/token

返回例:

{"access_token":"eyJhb_A","expires_in":3600,"refresh_token":"e187zHrR7dPyzJ","token_type":"bearer"}

至此我们就能见到熟悉的老朋友了!


  1. 使用 access token 取得资料
curl -H "Authorization: Bearer eyJhb_A" https://connect.linux.do/api/user

返回例:

{
    "id": 124,
    "username": "Bee",
    "name": "(  ⩌   ˰ ⩌)",
    "active": true,
    "trust_level": 2,
    "silenced": false
}

这样你的程序就能认证当前使用者的个人资料了。
在某些情况下,username 是可以被修改的,所以以 id 来辨别是比较好的方式。


  1. 那如果过期了怎么办?
    我们把刚刚向 token 请求的内容改一下,grant_type=refresh_token
curl -X POST -H "Authorization: Basic aGkzZ2VKWWZUb3RvaVI1UzYydTNyaDRXNXRTZUM1VUc6Vk1QQlZvQWZPQjVvamtHWFJERXR6dkRoUkxFTkhwYU4=" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=refresh_token&refresh_token=e187zHrR7dPyzJ" https://connect.linux.do/oauth2/token

返回例:

{"access_token":"eyJ403ioE","expires_in":3600,"token_type":"bearer"}

更新其他语言参考 ( ⩌ ˰ ⩌)

66 个赞

哦吼

6 个赞

这帖子就写出来了

6 个赞

期待一手各位大佬的全语言sdk :joy:

8 个赞

写得好清晰,点赞

4 个赞

贴一版java的回调后的处理demo

import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.Base64;

@RestController
@RequestMapping("/oauth2")
public class OAuth2Controller {
    private static final String TOKEN_ENDPOINT = "https://connect.linux.do/oauth2/token";
    private static final String CLIENT_ID = "hi3geJYfTotoiR5S62u3rh4W5tSeC5UG";
    private static final String CLIENT_SECRET = "VMPBVoAfOB5ojkGXRDEtzvDhRLENHpaN";
    private static final String REDIRECT_URI = "http://localhost:8181/oauth2/callback";

    @GetMapping("/callback")
    public void callback(String code) throws IOException {
        try (CloseableHttpClient client = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(TOKEN_ENDPOINT);

            String credentials = CLIENT_ID + ":" + CLIENT_SECRET;
            String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());

            httpPost.setHeader("Authorization", "Basic " + encodedCredentials);
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
            httpPost.setHeader("Accept", "application/json");

            StringEntity entity = new StringEntity(
                    "grant_type=authorization_code&" +
                            "code=" + code + "&" +
                            "redirect_uri=" + REDIRECT_URI,
                    "UTF-8");
            httpPost.setEntity(entity);

            String response = EntityUtils.toString(client.execute(httpPost).getEntity());

            System.out.println("Response: " + response);
        }
    }
}
6 个赞

拉起授权(也叫生成登录链接)要生成state,写进session,以便callback的时候跟回来的state做比对,避免攻击。

4 个赞

所以是demo 这部分接口安全的就没去写

4 个赞

生成和发送state参数

import javax.servlet.http.HttpSession;
import java.security.SecureRandom;
import java.math.BigInteger;

public class OAuth2InitiateAuthServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 生成随机state值并存储到会话中
        HttpSession session = request.getSession();

        String state = new BigInteger(130, new SecureRandom()).toString(32);
        session.setAttribute("oauthState", state);

        String authorizationRequestUrl = "https://connect.linux.do/oauth2/authorize" +
                                         "?client_id=" + CLIENT_ID +
                                         "&response_type=code" +
                                         "&redirect_uri=" + REDIRECT_URI +
                                         "&state=" + state;

        response.sendRedirect(authorizationRequestUrl);
    }
}

接收授权码和state参数并验证

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class OAuth2CallbackServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 在请求中获取返回的state参数和授权码
        String returnedState = request.getParameter("state");
        String authorizationCode = request.getParameter("code");

        HttpSession session = request.getSession();
        String originalState = (String) session.getAttribute("oauthState");

        // 验证state值
        if (returnedState != null && returnedState.equals(originalState)) {
            // state匹配 则继续处理 将上面的获取rt和at的逻辑放到这
        } else {
            // state不匹配 则拒绝请求
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}
5 个赞

只差一个请求用户信息了。

2 个赞

补一个请求用户信息的
其实大概跟获取token的是一样的
这里简单写一下

import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class OAuth2TokenRequest {

    public static void main(String[] args) {
        // OAuth2 令牌端点
        String tokenEndpoint = "https://connect.linux.do/api/user";
        // 客户端认证信息的Base64编码值 就是回调后的处理demo中的encodedCredentials
        String encodedCredentials = "aGkzZ2VKWWZUb3RvaVI1UzYydTNyaDRXNXRTZUM1VUc6Vk1QQlZvQWZPQjVvamtHWFJERXR6dkRoUkxFTkhwYU4=";
        // 授权码
        String authorizationCode = "接收到的授权码"; // 这里替换为实际的授权码
        // 重定向URI
        String redirectUri = "http://localhost:8181/oauth2/callback";

        try (CloseableHttpClient client = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(tokenEndpoint);

            // 设置HTTP头信息
            httpPost.setHeader("Authorization", "Basic " + encodedCredentials);  

            // 执行请求并获取响应
            String responseString = EntityUtils.toString(client.execute(httpPost).getEntity());

            // 打印响应内容
            System.out.println("Response: " + responseString);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
7 个赞

两位佬都写得好,顶起

3 个赞

以后应该会用上,先感谢

2 个赞

@Hua 6得飞起,帮始皇减轻工作量了,你这属于是

2 个赞

结对编程 :upside_down_face:

4 个赞

哈哈哈哈哈哈

3 个赞

@neo 来来来,教教我,php的写法

1 个赞

不错不错

1 个赞

再贴一个python简单的demo
启动后浏览器访问 http://localhost:8181/oauth2/initiate 即可
详情可根据自己的需求来更改

依赖

需要按照Flask和requests
pip install Flask requests

代码

代码
from flask import Flask, session, redirect, request, jsonify
import os
import requests

app = Flask(__name__)
app.secret_key = os.urandom(24)

# OAuth2 参数
CLIENT_ID = 'hi3geJYfTotoiR5S62u3rh4W5tSeC5UG'
CLIENT_SECRET = 'VMPBVoAfOB5ojkGXRDEtzvDhRLENHpaN'
REDIRECT_URI = 'http://localhost:8181/oauth2/callback'
AUTHORIZATION_ENDPOINT = 'https://connect.linux.do/oauth2/authorize'
TOKEN_ENDPOINT = 'https://connect.linux.do/oauth2/token'
USER_ENDPOINT = 'https://connect.linux.do/api/user'


@app.route('/oauth2/initiate')
def initiate_auth():
    session['oauth_state'] = os.urandom(16).hex()
    authorization_url = f"{AUTHORIZATION_ENDPOINT}?client_id={CLIENT_ID}&response_type=code&redirect_uri={REDIRECT_URI}&state={session['oauth_state']}"
    return redirect(authorization_url)


@app.route('/oauth2/callback')
def callback():
    code = request.args.get('code')
    state = request.args.get('state')

    if state != session.get('oauth_state'):
        return 'State value does not match', 401

    # 请求token
    auth = requests.auth.HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET)
    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': REDIRECT_URI
    }
    headers = {'Accept': 'application/json'}
    response = requests.post(TOKEN_ENDPOINT, auth=auth, data=data, headers=headers)

    if response.status_code == 200:
        # 这里获取的rt根据自己的实际进行处理  比如放入会话或数据库里
        user_response = requests.get(USER_ENDPOINT, headers={'Authorization': 'Bearer ' + response.json()['access_token']})
        if user_response.status_code == 200:
            return jsonify(user_response.json())
        else:
            return 'Failed to fetch user info', user_response.status_code
    else:
        return 'Failed to fetch access token', response.status_code


if __name__ == '__main__':
    app.run(debug=True, port=8181)

注意

代码为demo代码,实际请根据自己所需对token进行处理

9 个赞

你直接把SDK干出来了? :face_with_hand_over_mouth:这不去软区当版主可惜了


image

再接再励!@Hua

2 个赞