Flutter中如何使用运行时生成的PKCE变量?
解决PKCE code challenge变量未定义问题
问题根源
你遇到的codeChallenge未定义错误,核心原因有两个:
- 在
main函数中调用createCodeChallenge()时,没有保存返回结果,也未将其传递给LogInPage 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
相关产品推荐
相关产品推荐

