You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何使用运行时生成的PKCE变量?

解决PKCE code challenge变量未定义问题

问题根源

你遇到的codeChallenge未定义错误,核心原因有两个:

  1. 在main函数中调用createCodeChallenge()时,没有保存返回结果,也未将其传递给LogInPage
  2. LogInPage代码中直接使用$codeChallenge,但该变量在当前作用域内从未声明或赋值

快速修复方案(直接在登录页生成)

如果只是快速解决变量未定义的问题,可以直接在按钮点击时生成并使用codeChallenge:

class LogInPage extends StatelessWidget {
  const LogInPage({super.key}); 

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text("Log in"),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text("Log in"),
          onPressed: () {
            // 点击按钮时生成code challenge
            final codeChallenge = createCodeChallenge();
            launchUrl(
               Uri.parse('https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=d0bc225b046cdf15a2fb29f7d36c513c&code_challenge=$codeChallenge&state=RequestID34'),
                mode: LaunchMode.inAppWebView);
          },
        ),
      ),
    );
  }
}

标准PKCE实现(推荐)

注意:你当前的createCodeChallenge()函数仅生成随机字符串,不符合PKCE的官方规范。标准PKCE要求code_challenge是code_verifier的SHA-256哈希值(经过Base64URL编码),且后续获取token时必须使用code_verifier。以下是完整的合规实现:

1. 添加依赖

在pubspec.yaml中添加必要依赖:

dependencies:
  flutter:
    sdk: flutter
  crypto: ^3.0.3
  shared_preferences: ^2.2.2

2. 修正PKCE生成函数

import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:shared_preferences/shared_preferences.dart';

const String _charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';

// 生成code_verifier(随机字符串,用于后续token交换)
String createCodeVerifier() {
  return List.generate(128, (i) => _charset[Random.secure().nextInt(_charset.length)]).join();
}

// 从code_verifier生成合规的code_challenge
String createCodeChallenge(String codeVerifier) {
  final bytes = utf8.encode(codeVerifier);
  final digest = sha256.convert(bytes);
  // 转换为Base64URL编码(移除末尾=,替换+为-,/为_)
  return base64UrlEncode(digest.bytes).replaceAll('=', '');
}

3. 修改登录页为StatefulWidget

class LogInPage extends StatefulWidget {
  const LogInPage({super.key});

  @override
  State<LogInPage> createState() => _LogInPageState();
}

class _LogInPageState extends State<LogInPage> {
  late String _codeVerifier;
  late String _codeChallenge;

  @override
  void initState() {
    super.initState();
    // 初始化时生成PKCE参数
    _codeVerifier = createCodeVerifier();
    _codeChallenge = createCodeChallenge(_codeVerifier);
    // 将code_verifier保存到本地,后续换token时需要使用
    _saveCodeVerifier();
  }

  Future<void> _saveCodeVerifier() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('code_verifier', _codeVerifier);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: const Text("Log in"),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text("Log in"),
          onPressed: () {
            launchUrl(
              Uri.parse('https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=d0bc225b046cdf15a2fb29f7d36c513c&code_challenge=$_codeChallenge&code_challenge_method=S256&state=RequestID34'),
              mode: LaunchMode.inAppWebView,
            );
          },
        ),
      ),
    );
  }
}

关键注意事项

  • 标准PKCE必须携带code_challenge_method=S256参数,否则服务端可能无法识别你的code_challenge
  • code_verifier必须妥善保存,后续用授权码换取token时必须提交该值
  • 不要在main函数中生成PKCE参数,因为无法将其传递给后续页面的Widget

内容的提问来源于stack exchange,提问作者Kyle Gasser

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 07:51:09