feat: use cookie to store jwt token, better performance

This commit is contained in:
Simon Ding
2024-07-28 18:07:24 +08:00
parent b024b5f6dc
commit 3de2f89107
11 changed files with 73 additions and 217 deletions

View File

@@ -5,7 +5,6 @@ import (
"polaris/db" "polaris/db"
"polaris/log" "polaris/log"
"polaris/pkg/utils" "polaris/pkg/utils"
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -23,16 +22,15 @@ func (s *Server) authModdleware(c *gin.Context) {
c.Next() c.Next()
return return
} }
token, err := c.Cookie("token")
auth := c.GetHeader("Authorization") if err != nil {
if auth == "" { log.Errorf("token error: %v", err)
log.Infof("token is not present, abort")
c.AbortWithStatus(http.StatusForbidden) c.AbortWithStatus(http.StatusForbidden)
return return
} }
auth = strings.TrimPrefix(auth, "Bearer ")
//log.Debugf("current token: %v", auth) //log.Debugf("current token: %v", auth)
token, err := jwt.ParseWithClaims(auth, &jwt.RegisteredClaims{}, func(t *jwt.Token) (interface{}, error) { tokenParsed, err := jwt.ParseWithClaims(token, &jwt.RegisteredClaims{}, func(t *jwt.Token) (interface{}, error) {
return []byte(s.jwtSerect), nil return []byte(s.jwtSerect), nil
}) })
if err != nil { if err != nil {
@@ -40,15 +38,15 @@ func (s *Server) authModdleware(c *gin.Context) {
c.AbortWithStatus(http.StatusForbidden) c.AbortWithStatus(http.StatusForbidden)
return return
} }
if !token.Valid { if !tokenParsed.Valid {
log.Errorf("token is not valid: %v", auth) log.Errorf("token is not valid: %v", token)
c.AbortWithStatus(http.StatusForbidden) c.AbortWithStatus(http.StatusForbidden)
return return
} }
claim := token.Claims.(*jwt.RegisteredClaims) claim := tokenParsed.Claims.(*jwt.RegisteredClaims)
if time.Until(claim.ExpiresAt.Time) <= 0 { if time.Until(claim.ExpiresAt.Time) <= 0 {
log.Infof("token is no longer valid: %s", auth) log.Infof("token is no longer valid: %s", token)
c.AbortWithStatus(http.StatusForbidden) c.AbortWithStatus(http.StatusForbidden)
return return
} }
@@ -61,7 +59,6 @@ type LoginIn struct {
Password string `json:"password"` Password string `json:"password"`
} }
func (s *Server) Login(c *gin.Context) (interface{}, error) { func (s *Server) Login(c *gin.Context) (interface{}, error) {
var in LoginIn var in LoginIn
@@ -93,11 +90,23 @@ func (s *Server) Login(c *gin.Context) (interface{}, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "sign") return nil, errors.Wrap(err, "sign")
} }
c.SetSameSite(http.SameSiteNoneMode)
c.SetCookie("token", sig, 0, "/", "", true, false)
return gin.H{ return gin.H{
"token": sig, "token": sig,
}, nil }, nil
} }
func (s *Server) Logout(c *gin.Context) (interface{}, error) {
if !s.isAuthEnabled() {
return nil, errors.New( "auth is not enabled")
}
c.SetSameSite(http.SameSiteNoneMode)
c.SetCookie("token", "", -1, "/", "", true, false)
return nil, nil
}
type EnableAuthIn struct { type EnableAuthIn struct {
Enable bool `json:"enable"` Enable bool `json:"enable"`
User string `json:"user"` User string `json:"user"`

View File

@@ -63,6 +63,7 @@ func (s *Server) Serve() error {
setting := api.Group("/setting") setting := api.Group("/setting")
{ {
setting.GET("/logout", HttpHandler(s.Logout))
setting.POST("/general", HttpHandler(s.SetSetting)) setting.POST("/general", HttpHandler(s.SetSetting))
setting.GET("/general", HttpHandler(s.GetSetting)) setting.GET("/general", HttpHandler(s.GetSetting))
setting.POST("/auth", HttpHandler(s.EnableAuth)) setting.POST("/auth", HttpHandler(s.EnableAuth))

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:ui/activity.dart'; import 'package:ui/activity.dart';
import 'package:ui/login_page.dart'; import 'package:ui/login_page.dart';
import 'package:ui/movie_watchlist.dart'; import 'package:ui/movie_watchlist.dart';
@@ -177,41 +176,30 @@ class _MainSkeletonState extends State<MainSkeleton> {
(BuildContext context, SearchController controller) { (BuildContext context, SearchController controller) {
return [Text("dadada")]; return [Text("dadada")];
}), }),
FutureBuilder( MenuAnchor(
future: APIs.isLoggedIn(), menuChildren: [
builder: (context, snapshot) { MenuItemButton(
if (snapshot.hasData && snapshot.data == true) { leadingIcon: const Icon(Icons.exit_to_app),
return MenuAnchor( child: const Text("登出"),
menuChildren: [ onPressed: () async {
MenuItemButton( await APIs.logout();
leadingIcon: const Icon(Icons.exit_to_app), },
child: const Text("登出"), ),
onPressed: () async { ],
final SharedPreferences prefs = builder: (context, controller, child) {
await SharedPreferences.getInstance(); return TextButton(
await prefs.remove('token'); onPressed: () {
if (context.mounted) { if (controller.isOpen) {
context.go(LoginScreen.route); controller.close();
} } else {
}, controller.open();
), }
], },
builder: (context, controller, child) { child: const Icon(Icons.account_circle),
return TextButton( );
onPressed: () { },
if (controller.isOpen) { )
controller.close();
} else {
controller.open();
}
},
child: const Icon(Icons.account_circle),
);
},
);
}
return Container();
})
], ],
), ),
useDrawer: false, useDrawer: false,

View File

@@ -45,8 +45,8 @@ class _MovieDetailsPageState extends ConsumerState<MovieDetailsPage> {
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
opacity: 0.5, opacity: 0.5,
image: NetworkImage( image: NetworkImage(
"${APIs.imagesUrl}/${details.id}/backdrop.jpg", "${APIs.imagesUrl}/${details.id}/backdrop.jpg",
headers: APIs.authHeaders))), ))),
child: Padding( child: Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Row( child: Row(
@@ -58,7 +58,6 @@ class _MovieDetailsPageState extends ConsumerState<MovieDetailsPage> {
child: Image.network( child: Image.network(
"${APIs.imagesUrl}/${details.id}/poster.jpg", "${APIs.imagesUrl}/${details.id}/poster.jpg",
fit: BoxFit.contain, fit: BoxFit.contain,
headers: APIs.authHeaders,
), ),
), ),
), ),

View File

@@ -2,8 +2,6 @@ import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:quiver/strings.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:ui/providers/server_response.dart'; import 'package:ui/providers/server_response.dart';
class APIs { class APIs {
@@ -26,6 +24,7 @@ class APIs {
static final delDownloadClientUrl = "$_baseUrl/api/v1/downloader/del/"; static final delDownloadClientUrl = "$_baseUrl/api/v1/downloader/del/";
static final storageUrl = "$_baseUrl/api/v1/storage/"; static final storageUrl = "$_baseUrl/api/v1/storage/";
static final loginUrl = "$_baseUrl/api/login"; static final loginUrl = "$_baseUrl/api/login";
static final logoutUrl = "$_baseUrl/api/v1/setting/logout";
static final loginSettingUrl = "$_baseUrl/api/v1/setting/auth"; static final loginSettingUrl = "$_baseUrl/api/v1/setting/auth";
static final activityUrl = "$_baseUrl/api/v1/activity/"; static final activityUrl = "$_baseUrl/api/v1/activity/";
static final activityMediaUrl = "$_baseUrl/api/v1/activity/media/"; static final activityMediaUrl = "$_baseUrl/api/v1/activity/media/";
@@ -49,53 +48,20 @@ class APIs {
return "http://127.0.0.1:8080"; return "http://127.0.0.1:8080";
} }
static Dio? gDio; static Dio getDio() {
static Map<String, String> authHeaders = {};
static Future<bool> isLoggedIn() async {
return isNotBlank(await getToken());
}
static Future<String> getToken() async {
var token = authHeaders["Authorization"];
if (isBlank(token)) {
final SharedPreferences prefs = await SharedPreferences.getInstance();
var t = prefs.getString("token");
if (isNotBlank(t)) {
authHeaders["Authorization"] = t!;
token = t;
}
}
return token ?? "";
}
static Future<Dio> getDio() async {
if (gDio != null) {
return gDio!;
}
var token = await getToken();
var dio = Dio(); var dio = Dio();
dio.interceptors.add(InterceptorsWrapper( dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = token;
return handler.next(options);
},
onError: (error, handler) { onError: (error, handler) {
if (error.response?.statusCode != null && if (error.response?.statusCode != null &&
error.response?.statusCode! == 403) { error.response?.statusCode! == 403) {
final context = navigatorKey.currentContext; final context = navigatorKey.currentContext;
if (context != null) { if (context != null) {
context.go('/login'); context.go('/login');
gDio = null;
} }
} }
return handler.next(error); return handler.next(error);
}, },
)); ));
if (isNotBlank(token)) {
gDio = dio;
}
return dio; return dio;
} }
@@ -108,9 +74,19 @@ class APIs {
if (sp.code != 0) { if (sp.code != 0) {
throw sp.message; throw sp.message;
} }
final SharedPreferences prefs = await SharedPreferences.getInstance(); }
var t = sp.data["token"];
authHeaders["Authorization"] = "Bearer $t"; static Future<void> logout() async {
prefs.setString("token", "Bearer $t"); var resp = await getDio().get(APIs.logoutUrl);
var sp = ServerResponse.fromJson(resp.data);
if (sp.code != 0) {
throw sp.message;
}
final context = navigatorKey.currentContext;
if (context != null) {
context.go('/login');
}
} }
} }

View File

@@ -50,7 +50,6 @@ class _SearchPageState extends ConsumerState<SearchPage> {
child: Image.network( child: Image.network(
"${APIs.tmdbImgBaseUrl}${item.posterPath}", "${APIs.tmdbImgBaseUrl}${item.posterPath}",
fit: BoxFit.contain, fit: BoxFit.contain,
headers: APIs.authHeaders,
), ),
), ),
), ),

View File

@@ -49,9 +49,7 @@ class _SystemPageState extends ConsumerState<SystemPage> {
DataCell(Text((item.size ?? 0).readableFileSize())), DataCell(Text((item.size ?? 0).readableFileSize())),
DataCell(InkWell( DataCell(InkWell(
child: const Icon(Icons.download), child: const Icon(Icons.download),
onTap: () => launchUrl(uri, onTap: () => launchUrl(uri),
webViewConfiguration: WebViewConfiguration(
headers: APIs.authHeaders)),
)) ))
]); ]);
})); }));

View File

@@ -155,8 +155,7 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
opacity: 0.5, opacity: 0.5,
image: NetworkImage( image: NetworkImage(
"${APIs.imagesUrl}/${details.id}/backdrop.jpg", "${APIs.imagesUrl}/${details.id}/backdrop.jpg"))),
headers: APIs.authHeaders))),
child: Padding( child: Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Row( child: Row(
@@ -167,8 +166,7 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Image.network( child: Image.network(
"${APIs.imagesUrl}/${details.id}/poster.jpg", "${APIs.imagesUrl}/${details.id}/poster.jpg",
fit: BoxFit.contain, fit: BoxFit.contain
headers: APIs.authHeaders,
), ),
), ),
), ),

View File

@@ -34,7 +34,10 @@ class WelcomePage extends ConsumerWidget {
Container( Container(
height: MediaQuery.of(context).size.height * 0.6, height: MediaQuery.of(context).size.height * 0.6,
alignment: Alignment.center, alignment: Alignment.center,
child: const Text("啥都没有...", style: TextStyle(fontSize: 16),)) child: const Text(
"啥都没有...",
style: TextStyle(fontSize: 16),
))
] ]
: List.generate(value.length, (i) { : List.generate(value.length, (i) {
var item = value[i]; var item = value[i];
@@ -56,10 +59,8 @@ class WelcomePage extends ConsumerWidget {
width: 140, width: 140,
height: 210, height: 210,
child: Image.network( child: Image.network(
"${APIs.imagesUrl}/${item.id}/poster.jpg", "${APIs.imagesUrl}/${item.id}/poster.jpg",
fit: BoxFit.fill, fit: BoxFit.fill),
headers: APIs.authHeaders,
),
), ),
SizedBox( SizedBox(
width: 140, width: 140,

View File

@@ -89,22 +89,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -325,30 +309,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
percent_indicator: percent_indicator:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -365,14 +325,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "8.3.0" version: "8.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.5"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -405,62 +357,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "2.5.1" version: "2.5.1"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.3"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.3.2"
sign_in_button: sign_in_button:
dependency: transitive dependency: transitive
description: description:
@@ -626,14 +522,6 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.4"
sdks: sdks:
dart: ">=3.4.3 <4.0.0" dart: ">=3.4.3 <4.0.0"
flutter: ">=3.22.0" flutter: ">=3.22.0"

View File

@@ -40,7 +40,6 @@ dependencies:
flutter_riverpod: ^2.5.1 flutter_riverpod: ^2.5.1
quiver: ^3.2.1 quiver: ^3.2.1
flutter_login: ^5.0.0 flutter_login: ^5.0.0
shared_preferences: ^2.2.3
percent_indicator: ^4.2.3 percent_indicator: ^4.2.3
intl: ^0.19.0 intl: ^0.19.0
flutter_adaptive_scaffold: ^0.1.11+1 flutter_adaptive_scaffold: ^0.1.11+1