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
๋ณต์ฌ