Search

Flutter x SpringSecurity x JWT

Flutter x SpringSecurity x JWT

Flutter

pubspec.yaml

# ์•ˆ์ „ํ•œ ์ €์žฅ์†Œ flutter_secure_storage: ^5.0.0 # provider (์ƒํƒœ ๊ณต์œ  - ๋กœ๊ทธ์ธ ์ธ์ฆ์ •๋ณด ๊ณต์œ ) provider: ^6.1.1 # http http: ^1.2.0
YAML
๋ณต์‚ฌ

models

โ€ข
user.dart
class User { int? no; String? userId; String? userPw; String? userPwCheck; String? name; String? email; DateTime? regDate; DateTime? updDate; int? enabled; List<Auth>? authList; User({ this.no, this.userId, this.userPw, this.userPwCheck, this.name, this.email, this.regDate, this.updDate, this.enabled, this.authList, }); factory User.fromJson(Map<String, dynamic> json) { return User( no: json['no'], userId: json['userId'], userPw: json['userPw'], userPwCheck: json['userPwCheck'], name: json['name'], email: json['email'], regDate: json['regDate'] != null ? DateTime.parse(json['regDate']) : null, updDate: json['updDate'] != null ? DateTime.parse(json['updDate']) : null, enabled: json['enabled'], authList: (json['authList'] as List<dynamic>) .map((authJson) => Auth.fromJson(authJson)) .toList(), ); } Map<String, dynamic> toJson() { return { 'no': no, 'userId': userId, 'userPw': userPw, 'userPwCheck': userPwCheck, 'name': name, 'email': email, 'regDate': regDate?.toIso8601String(), 'updDate': updDate?.toIso8601String(), 'enabled': enabled, 'authList': authList?.map((auth) => auth.toJson()).toList(), }; } } class Auth { int? authNo; String? userId; String? auth; Auth({ this.authNo, this.userId, this.auth, }); factory Auth.fromJson(Map<String, dynamic> json) { return Auth( authNo: json['authNo'], userId: json['userId'], auth: json['auth'], ); } Map<String, dynamic> toJson() { return { 'authNo': authNo, 'userId': userId, 'auth': auth, }; } }
Dart
๋ณต์‚ฌ

Provider

โ€ข
user_provider.dart
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:jwp_app/models/user.dart'; import 'package:http/http.dart' as http; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class UserProvider extends ChangeNotifier { // ๋กœ๊ทธ์ธ ์ •๋ณด late User _userInfo; // ๋กœ๊ทธ์ธ ์ƒํƒœ bool _loginStat = false; // getter // get : getter ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•˜๋Š” ํ‚ค์›Œ๋“œ User get userInfo => _userInfo; // ์ „์—ญ๋ณ€์ˆ˜ bool get isLogin => _loginStat; // ์ „์—ญ๋ณ€์ˆ˜ // ๐Ÿ”’ ์•ˆ์ „ํ•œ ์ €์žฅ์†Œ final storage = const FlutterSecureStorage(); /// ๐Ÿ” ๋กœ๊ทธ์ธ ์š”์ฒญ /// 1. ์š”์ฒญ ๋ฐ ์‘๋‹ต /// โžก username, password /// โฌ… jwt token /// /// 2. jwt ํ† ํฐ์„ SecureStorage ์— ์ €์žฅ Future<void> login(String username, String password) async { const url = 'http://10.0.2.2:8080/login'; final requestUrl = Uri.parse('$url?username=$username&password=$password'); try { // ๋กœ๊ทธ์ธ ์š”์ฒญ final response = await http.get(requestUrl); if (response.statusCode == 200) { print('๋กœ๊ทธ์ธ ์„ฑ๊ณต...'); // HTTP ์š”์ฒญ์ด ์„ฑ๊ณตํ–ˆ์„ ๋•Œ final authorizationHeader = response.headers['authorization']; if (authorizationHeader != null) { // Authorization ํ—ค๋”์—์„œ "Bearer "๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  JWT ํ† ํฐ ๊ฐ’์„ ์ถ”์ถœ final jwtToken = authorizationHeader.replaceFirst('Bearer ', ''); // ์—ฌ๊ธฐ์„œ jwtToken์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. print('JWT Token: $jwtToken'); // jwt ์ €์žฅ await storage.write(key: 'jwtToken', value: jwtToken); _loginStat = true; } else { // Authorization ํ—ค๋”๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ print('Authorization ํ—ค๋”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.'); } } else if( response.statusCode == 403 ) { print('์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค...'); } else { print('๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ๋˜๋Š” ์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๋กœ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค...'); } } catch (error) { print("๋กœ๊ทธ์ธ ์‹คํŒจ $error"); } // ๊ณต์œ ๋œ ์ƒํƒœ๋ฅผ ๊ฐ€์ง„ ์œ„์ ฏ ๋‹ค์‹œ ๋นŒ๋“œ notifyListeners(); } /// ๐Ÿ‘ฉโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ /// 1. ๐Ÿ’ jwt โžก ์„œ๋ฒ„ /// 2. ํด๋ผ์ด์–ธํŠธ โฌ… ๐Ÿ‘ฉโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ /// 3. ๐Ÿ‘ฉโ€๐Ÿ’ผ๐Ÿ‘จโ€๐Ÿ’ผ(userInfo) โžก _userInfo [provider] ์ €์žฅ Future<void> getUserInfo() async { final url = 'http://10.0.2.2:8080/users/info'; try { // ์ €์žฅ๋œ jwt ๊ฐ€์ ธ์˜ค๊ธฐ String? token = await storage.read(key: 'jwtToken'); print('์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ ์ „: jwt - $token'); final response = await http.get( Uri.parse(url), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, ); if (response.statusCode == 200) { // ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™”์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ var utf8Decoded = utf8.decode( response.bodyBytes ); var result = json.decode(utf8Decoded); final userInfo = result; print('User Info: $userInfo'); // provider ์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ // userInfo โžก _userInfo ๋กœ ์ €์žฅ // provider ๋“ฑ๋ก _userInfo = User.fromJson(userInfo); print(_userInfo); } else { // HTTP ์š”์ฒญ์ด ์‹คํŒจํ–ˆ์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ print('HTTP ์š”์ฒญ ์‹คํŒจ: ${response.statusCode}'); print('์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ ์„ฑ๊ณต'); } }catch (error) { print('์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ ์‹คํŒจ $error'); } notifyListeners(); } // ๋กœ๊ทธ์•„์›ƒ Future<void> logout() async { try { // โฌ…๐Ÿ‘จโ€๐Ÿ’ผ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ // jwt ํ† ํฐ ์‚ญ์ œ await storage.delete(key: 'jwtToken'); // ์‚ฌ์šฉ์ž ์ •๋ณด ์ดˆ๊ธฐํ™” _userInfo = User(); // ๋กœ๊ทธ์ธ ์ƒํƒœ ์ดˆ๊ธฐํ™” _loginStat = false; print('๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต'); } catch (error) { print('๋กœ๊ทธ์•„์›ƒ ์‹คํŒจ'); } notifyListeners(); } }
Dart
๋ณต์‚ฌ

main.dart

import 'package:flutter/material.dart'; import 'package:jwp_app/provider/user_provider.dart'; import 'package:jwp_app/screens/home_screen.dart'; import 'package:jwp_app/screens/login_screen.dart'; import 'package:provider/provider.dart'; void main() { runApp( // โœ…โœ…โœ… ํ”„๋กœ๋ฐ”์ด๋” ์ถ”๊ฐ€ ChangeNotifierProvider( create: (context) => UserProvider(), child: const MyApp() ) ); } class MyApp extends StatelessWidget { const MyApp({super.key}); Widget build(BuildContext context) { return MaterialApp( title: 'JWT ํ† ํฐ ๋กœ๊ทธ์ธ', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), initialRoute: '/', routes: { '/' : (context) => HomeScreen(), '/login' : (context) => LoginScreen(), }, ); } }
Dart
๋ณต์‚ฌ

Screens

โ€ข
home_screen.dart
โ€ข
login_screen.dart
โ€ข
home_screen.dart
import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:jwp_app/provider/user_provider.dart'; import 'package:provider/provider.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { // FlutterSecureStorage : ์•ˆ์ „ํ•œ ์ €์žฅ์†Œ final storage = const FlutterSecureStorage(); String jwtToken = ""; void initState() { super.initState(); loadJwtToken(); } /** * ๐Ÿ’ ์ €์žฅ๋œ JWT ํ† ํฐ ์ฝ์–ด์˜ค๊ธฐ */ Future<void> loadJwtToken() async { // ์ €์žฅ๋œ JWT ํ† ํฐ ์ฝ๊ธฐ String? token = await storage.read(key: 'jwtToken'); // ์ €์žฅ๋œ ํ† ํฐ์ด ์—†์œผ๋ฉด โžก ๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ if( token == null || token == '' ) { print('๋ฏธ๋ฆฌ ์ €์žฅ๋œ jwt ํ† ํฐ ์—†์Œ'); print('๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™...'); Navigator.pushReplacementNamed(context, '/login'); return; } // ์ €์žฅ๋œ ํ† ํฐ์ด ์žˆ์œผ๋ฉด โžก ์„œ๋ฒ„๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ setState(() { jwtToken = token ?? ""; }); } Future<void> saveJwtToken(String token) async { await storage.write(key: 'jwtToken', value: token); } Widget build(BuildContext context) { // listen: (provider ๊ตฌ๋…์—ฌ๋ถ€) // - true : provider ์—์„œ notifyListeners() ํ˜ธ์ถœ ์‹œ, consumer ๋ฆฌ๋ Œ๋”๋ง โญ• // - false : provider ์—์„œ notifyListeners() ํ˜ธ์ถœ ์‹œ, consumer ๋ฆฌ๋ Œ๋”๋ง โŒ UserProvider userProvider = Provider.of<UserProvider>(context, listen: true); return Scaffold( appBar: AppBar( title: Text('JWT ํ† ํฐ'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('์‚ฌ์šฉ์ž ์ •๋ณด'), Text('userId : ${userProvider.userInfo.userId ?? '์—†์Œ'}'), Text('name : ${userProvider.userInfo.name ?? '์—†์Œ'}'), Text('email : ${userProvider.userInfo.email ?? '์—†์Œ'}'), Text('JWT Token: $jwtToken'), !userProvider.isLogin ? ElevatedButton( onPressed: () async { print('๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™...'); Navigator.pushReplacementNamed(context, '/login'); }, child: Text('๋กœ๊ทธ์ธ'), ) : ElevatedButton( onPressed: () async { print('๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ...'); userProvider.logout(); }, child: Text('๋กœ๊ทธ์•„์›ƒ'), ), ], ), ), ); } }
Dart
๋ณต์‚ฌ
โ€ข
login_screen.dart
import 'package:flutter/material.dart'; import 'package:jwp_app/provider/user_provider.dart'; import 'package:provider/provider.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); _LoginScreenState createState() => _LoginScreenState(); } class _LoginScreenState extends State<LoginScreen> { TextEditingController _usernameController = TextEditingController(); TextEditingController _passwordController = TextEditingController(); Widget build(BuildContext context) { UserProvider userProvider = Provider.of<UserProvider>(context, listen: false); return Scaffold( appBar: AppBar( title: Text('Login'), ), body: Padding( padding: EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: _usernameController, decoration: InputDecoration( labelText: 'Username', ), ), SizedBox(height: 16.0), TextField( controller: _passwordController, obscureText: true, decoration: InputDecoration( labelText: 'Password', ), ), SizedBox(height: 32.0), ElevatedButton( onPressed: () { // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํด๋ฆญ _performLogin(userProvider); }, child: Text('Login'), ), ], ), ), ); } // ๋ณด๋ฅ˜ void _performLogin(UserProvider userProvider) async { // ์—ฌ๊ธฐ์— ์‹ค์ œ ๋กœ๊ทธ์ธ ๋กœ์ง์„ ๊ตฌํ˜„ String username = _usernameController.text; String password = _passwordController.text; print('Username: $username'); print('Password: $password'); await userProvider.login(username, password); if( userProvider.isLogin ) { print('๋กœ๊ทธ์ธ ์—ฌ๋ถ€ : ${userProvider.isLogin}'); await userProvider.getUserInfo(); print('์œ ์ €์ •๋ณด ์ €์žฅ ์™„๋ฃŒ...'); print( userProvider.userInfo ); Navigator.pushReplacementNamed(context, '/'); } } }
Dart
๋ณต์‚ฌ