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

你好棒

1 个赞

官方文档
https://docs.discourse.org/#tag/Users/operation/getUser

没开认证应该是方便查询,毕竟授权的意义是在于使用者向伺第三方证明他在这里的身份,所以这个接口是无法拿来认证的,因为伺服器不可能问使用者你是谁,而是要向始皇伺服器问,不然使用者F12改一下发送的内容就能伪造了。

仔细回想流程,伺服器先发给使用者随机code,使用者再拿code给第三方(经由callback uri),第三方再拿code找始皇伺服器问他是谁,如此才能进行认证

2 个赞

学习了

1 个赞

前言

将上面的java重新整理一下

依赖

所需依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>demo-OAuth2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

</project>

配置

application.yml
server:
  port: 8181

linux-do:
  oauth2:
    client:
      registration:
        client-id: hi3geJYfTotoiR5S62u3rh4W5tSeC5UG
        client-secret: VMPBVoAfOB5ojkGXRDEtzvDhRLENHpaN
        redirect-uri: http://localhost:8181/oauth2/callback
        authorization-grant-type: authorization_code
        scope: read,write
      provider:
        authorization-uri: https://connect.linux.do/oauth2/authorize
        token-uri: https://connect.linux.do/oauth2/token
        user-info-uri: https://connect.linux.do/api/user
        user-name-attribute: id

主要代码

代码
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Map;

@RestController
@RequestMapping("/oauth2")
public class OAuth2Controller {

    @Value("${linux-do.oauth2.client.registration.client-id}")
    private String clientId;

    @Value("${linux-do.oauth2.client.registration.client-secret}")
    private String clientSecret;

    @Value("${linux-do.oauth2.client.registration.redirect-uri}")
    private String redirectUri;

    @Value("${linux-do.oauth2.client.provider.authorization-uri}")
    private String authorizationEndpoint;

    @Value("${linux-do.oauth2.client.provider.token-uri}")
    private String tokenEndpoint;

    @Value("${linux-do.oauth2.client.provider.user-info-uri}")
    private String userEndpoint;

    @GetMapping("/initiate")
    public void initiateAuth(HttpServletRequest request, HttpServletResponse response) throws IOException {
        HttpSession session = request.getSession();
        String state = new BigInteger(130, new SecureRandom()).toString(32);
        session.setAttribute("oauth2State", state);
        response.sendRedirect(String.format("%s?client_id=%s&response_type=code&redirect_uri=%s&scope=%s&state=%s",
                authorizationEndpoint, clientId, redirectUri, "read,write", state));
    }

    @GetMapping("/callback")
    public String handleAuthorizationCode(@RequestParam("code") String code, @RequestParam("state") String state, HttpServletRequest request) {
        String sessionState = (String) request.getSession().getAttribute("oauth2State");
        if (sessionState == null || !sessionState.equals(state)) {
            return "State mismatch error";
        }
        // 创建请求头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        String auth = clientId + ":" + clientSecret;
        String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
        headers.add("Authorization", "Basic " + encodedAuth);

        // 使用授权码请求访问令牌
        MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add("grant_type", "authorization_code");
        requestBody.add("code", code);
        requestBody.add("redirect_uri", redirectUri);

        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(requestBody, headers);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<Map> response = restTemplate.postForEntity(tokenEndpoint, requestEntity, Map.class);

        Map<String, Object> responseBody = response.getBody();

        // 处理响应,例如提取和返回访问令牌
        if (responseBody != null && responseBody.containsKey("access_token")) {
            HttpHeaders userHeaders = new HttpHeaders();
            userHeaders.setBearerAuth(responseBody.get("access_token").toString());
            HttpEntity<String> entity = new HttpEntity<>(userHeaders);
            ResponseEntity<Map> userResponse = restTemplate.exchange(userEndpoint, HttpMethod.GET, entity, Map.class);

            Map<String, Object> userResBody = userResponse.getBody();
            if (userResBody != null) {
                return userResBody.toString();
            } else {
                return "Failed to obtain user details";
            }
        } else {
            return "Failed to obtain access token";
        }
    }
}

注意

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

6 个赞

这种鉴权的 其实并不推荐在前端进行处理 如果你是说nodejs当后端的话那可以

2 个赞

即pyhton和java之后再来个nodejs的

依赖

npm install express axios express-session qs

代码

代码
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const qs = require('qs');


const app = express();
const port = 8181;

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

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 使用 session 来保存 state 和其他 OAuth2 相关信息
const session = require('express-session');
app.use(session({
    secret: crypto.randomBytes(24).toString('hex'),
    resave: false,
    saveUninitialized: true
}));

app.get('/oauth2/initiate', (req, res) => {
    req.session.oauthState = crypto.randomBytes(16).toString('hex');
    const authorizationUrl = `${AUTHORIZATION_ENDPOINT}?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${REDIRECT_URI}&state=${req.session.oauthState}`;
    res.redirect(authorizationUrl);
});

app.get('/oauth2/callback', async (req, res) => {
    const { code, state } = req.query;

    if (state !== req.session.oauthState) {
        console.error('State验证失败');
        return res.status(401).send('State value does not match');
    }

    try {
        const data = qs.stringify({
            grant_type: 'authorization_code',
            code: code,
            redirect_uri: REDIRECT_URI
        });

        const tokenResponse = await axios.post(TOKEN_ENDPOINT, data, {
            auth: {
                username: CLIENT_ID,
                password: CLIENT_SECRET
            },
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });

        const userResponse = await axios.get(USER_ENDPOINT, {
            headers: { 'Authorization': `Bearer ${tokenResponse.data.access_token}` }
        });
        console.log('User response:', userResponse.data);
        res.json(userResponse.data);
    } catch (error) {
        console.error('Error during token fetch or user info retrieval:', error.message);

        // 更详细地输出错误信息
        if (error.response) {
            console.error('Error response data:', error.response.data);
            console.error('Error response status:', error.response.status);
            console.error('Error response headers:', error.response.headers);
        } else if (error.request) {
            console.error('No response received:', error.request);
        } else {
            console.error('Error', error.message);
        }

        return res.status(500).send('Failed to fetch access token');
    }

});

app.listen(port, () => {
    console.log(`App listening at http://localhost:${port}`);
});

注意

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

6 个赞

码住 万一用得上

大佬牛啊,感谢分享

感谢大佬分享

这里附上NestJS的代码,亲测有效

oauth.strategy.ts
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class OAuth2Strategy {
  private readonly authorizationURL: string;
  private readonly tokenURL: string;
  private readonly clientID: string;
  private readonly clientSecret: string;
  private readonly callbackURL: string;
  private readonly userUrl: string;

  constructor(private readonly configService: ConfigService) {
    this.authorizationURL = this.configService.get('OAUTH_AUTHORIZATION_URL');
    this.tokenURL = this.configService.get('OAUTH_TOKEN_URL');
    this.clientID = this.configService.get('OAUTH_CLIENT_ID');
    this.clientSecret = this.configService.get('OAUTH_CLIENT_SECRET');
    this.callbackURL = this.configService.get('OAUTH_CALLBACK_URL');
    this.userUrl = this.configService.get('OAUTH_USER_INFO_URL');
  }

  // 获取授权 URL
  getAuthorizationURL(state?: string): string {
    const params = new URLSearchParams([
      ['client_id', this.clientID],
      ['response_type', 'code'],
      ['redirect_uri', this.callbackURL],
      ['state', state || ''],
    ]);

    return `${this.authorizationURL}?${params.toString()}`;
  }

  // 生成随机state, 用于防止攻击
  generateState(): string {
    return Math.random().toString(36).substring(2);
  }

  // 交换授权码为访问令牌
  async exchangeCodeForToken(code: string): Promise<string> {
    const authHeader = Buffer.from(
      `${this.clientID}:${this.clientSecret}`,
    ).toString('base64');
    const data = new URLSearchParams([
      ['grant_type', 'authorization_code'],
      ['code', code],
      ['redirect_uri', this.callbackURL],
    ]);

    const headers = {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: `Basic ${authHeader}`,
    };

    const response = await axios.post(this.tokenURL, data, { headers });

    if (response.status !== 200) {
      throw new Error('Failed to exchange code for token');
    }

    return response.data.access_token;
  }

  // 获取用户信息
  async getUserInfo(accessToken: string): Promise<any> {
    const headers = {
      Authorization: `Bearer ${accessToken}`,
    };

    const response = await axios.get(this.userUrl, { headers });

    if (response.status !== 200) {
      throw new Error('Failed to fetch user info');
    }

    return response.data;
  }
}

app.controller.ts
  @Controller()
  export class AppController {
    constructor(
      private readonly authService: AuthService,
      private readonly userService: UserService,
      private readonly oauth2Strategy: OAuth2Strategy,
    ) {}

    // LinuxDo OAuth2 登录
    @Get('auth/oauth2')
    @ApiOperation({
      summary: 'LinuxDo OAuth2 登录',
      description: 'LinuxDo OAuth2 登录',
    })
    @ApiResponse({ status: 200, description: '登录成功' })
    async oauth2(@Res() res: Response, @Session() session: any) {
      // 随机生成一个state并存储到session中
      session.state = this.oauth2Strategy.generateState();
      const authorizationURL = this.oauth2Strategy.getAuthorizationURL(
        session.state,
      );
  
      // 重定向
      res.redirect(authorizationURL);
    }
  
    // LinuxDo OAuth2 回调地址
    @Get('oauth2/callback')
    @ApiOperation({
      summary: 'LinuxDo OAuth2 回调地址',
      description: 'LinuxDo OAuth2 回调地址',
    })
    @ApiResponse({ status: 200, description: '登录成功' })
    async oauth2Callback(
      @Query('code') code: string,
      @Query('state') state: string,
      @Session() session: any,
    ) {
      if (state !== session.state) {
        throw new Error('状态码错误,谨防跨站请求伪造攻击!');
      }
  
      try {
        const accessToken = await this.oauth2Strategy.exchangeCodeForToken(code);
        const userInfo = await this.oauth2Strategy.getUserInfo(accessToken);
  
        // 根据需要处理用户信息
        console.log('userInfo', userInfo);
        // find or create user
        const res = await this.userService.findOrCreate({
          id: userInfo.id,
          username: userInfo.username,
          nickname: userInfo.name,
          email: userInfo.email,
          avatar: userInfo.avatar_url,
          trustLevel: userInfo.trust_level,
        });
        if (!res) {
          throw new InternalServerErrorException('用户信息获取失败');
        }
  
        // 成功认证后生成token
        const { access_token } = this.authService.login(userInfo);
        (res as any).access_token = access_token;
        return res;
      } catch (error) {
        // 处理错误
        console.error(error);
        return new UnauthorizedException('OAuth2登录失败');
      }
    }
  }

其中oauth.strategy.ts本质上就是封装了一个class类,任何基于NodeJS的web框架都可以将其简化成普通的class,然后直接在项目中使用

3 个赞

论坛大佬就是多, 各种人才

厉害了厉害了

这个是什么工具

From 软件开发 to 开发调优

wordpress怎么接入始皇的l站登录