From 881be1b0f9e97076db181bd130914e73dc8c2d39 Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Mon, 8 May 2023 09:37:13 +0800 Subject: [PATCH] Added login expiration reminder. Add logout reminder. --- Pinnacle/pom.xml | 4 ++ .../permission/LoginController.java | 5 +- .../pinnacle/entity/common/ResponseCode.java | 2 + .../filter/JwtAuthenticationTokenFilter.java | 21 ++++---- .../AuthenticationEntryPointHandler.java | 2 +- .../service/permission/ILoginService.java | 2 +- .../permission/impl/LoginServiceImpl.java | 12 ++--- .../com/cfive/pinnacle/utils/WebUtil.java | 1 - ui/src/constants/Common.constants.ts | 48 ++++++++++++++++++- ui/src/main.ts | 2 + ui/src/pages/Login.vue | 42 ++++++++-------- ui/src/pages/Main.vue | 9 +++- ui/src/services/index.ts | 27 +++++++---- ui/src/utils/auth.ts | 26 +++------- ui/src/utils/common.ts | 5 ++ 15 files changed, 138 insertions(+), 70 deletions(-) diff --git a/Pinnacle/pom.xml b/Pinnacle/pom.xml index 8ee37b4..7a8736c 100644 --- a/Pinnacle/pom.xml +++ b/Pinnacle/pom.xml @@ -92,6 +92,10 @@ java-jwt 4.3.0 + + org.springframework.boot + spring-boot-starter-actuator + diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/controller/permission/LoginController.java b/Pinnacle/src/main/java/com/cfive/pinnacle/controller/permission/LoginController.java index 5898543..5c8bea9 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/controller/permission/LoginController.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/controller/permission/LoginController.java @@ -5,6 +5,7 @@ import com.cfive.pinnacle.entity.common.ResponseCode; import com.cfive.pinnacle.entity.common.ResponseResult; import com.cfive.pinnacle.service.permission.ILoginService; import com.cfive.pinnacle.utils.WebUtil; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -28,8 +29,8 @@ public class LoginController { } @RequestMapping("/logout") - public ResponseResult logout() { - boolean result = loginService.logout(); + public ResponseResult logout(HttpServletRequest request) { + boolean result = loginService.logout(request.getHeader("token")); if (result) { return ResponseResult.build(ResponseCode.LOGOUT_SUCCESS, "Logout Success", null); } else { diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/entity/common/ResponseCode.java b/Pinnacle/src/main/java/com/cfive/pinnacle/entity/common/ResponseCode.java index 4ff73df..4c31828 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/entity/common/ResponseCode.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/entity/common/ResponseCode.java @@ -10,6 +10,8 @@ public class ResponseCode { public static final int LOGIN_USERNAME_PASSWORD_ERROR = 20011; public static final int LOGOUT_SUCCESS = 20015; public static final int LOGOUT_FAILED = 20016; + public static final int TOKEN_IS_ILLEGAL = 20017; + public static final int TOKEN_HAS_EXPIRED = 20018; public static final int DATABASE_SELECT_OK = 20021; public static final int DATABASE_SAVE_OK = 20022; public static final int DATABASE_UPDATE_OK = 20023; diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/filter/JwtAuthenticationTokenFilter.java b/Pinnacle/src/main/java/com/cfive/pinnacle/filter/JwtAuthenticationTokenFilter.java index a083aec..8b53e53 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/filter/JwtAuthenticationTokenFilter.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/filter/JwtAuthenticationTokenFilter.java @@ -1,10 +1,12 @@ package com.cfive.pinnacle.filter; -import com.auth0.jwt.interfaces.DecodedJWT; +import com.cfive.pinnacle.entity.common.ResponseCode; import com.cfive.pinnacle.entity.permission.LoginUser; import com.cfive.pinnacle.utils.JwtUtil; import com.cfive.pinnacle.utils.RedisCache; +import com.cfive.pinnacle.utils.WebUtil; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Nonnull; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -29,26 +31,29 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + protected void doFilterInternal(HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain filterChain) throws ServletException, IOException { String token = request.getHeader("token"); if (!StringUtils.hasText(token)) { filterChain.doFilter(request, response); return; } - String userId; try { - DecodedJWT decodedJWT = JwtUtil.parseJWT(token); - userId = decodedJWT.getSubject(); + JwtUtil.parseJWT(token); } catch (Exception e) { - throw new RuntimeException("Token is illegal"); + String objectResponse = WebUtil.objectResponse(ResponseCode.TOKEN_IS_ILLEGAL, "Token is illegal", null); + WebUtil.renderString(response, objectResponse); + return; } - String redisKey = "login:" + userId; + String redisKey = "login:" + token; LoginUser loginUser = new ObjectMapper().convertValue(redisCache.getCacheObject(redisKey), LoginUser.class); if (Objects.isNull(loginUser)) { - throw new RuntimeException("Not logged in"); + String objectResponse = WebUtil.objectResponse(ResponseCode.TOKEN_HAS_EXPIRED, "Token has expired", null); + WebUtil.renderString(response, objectResponse); + return; } + // Todo 权限 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null); SecurityContextHolder.getContext().setAuthentication(authenticationToken); diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/handler/AuthenticationEntryPointHandler.java b/Pinnacle/src/main/java/com/cfive/pinnacle/handler/AuthenticationEntryPointHandler.java index 30f9231..da63c08 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/handler/AuthenticationEntryPointHandler.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/handler/AuthenticationEntryPointHandler.java @@ -18,7 +18,7 @@ public class AuthenticationEntryPointHandler implements AuthenticationEntryPoint public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { String objectResponse; if (authException instanceof BadCredentialsException) { - objectResponse = WebUtil.objectResponse(ResponseCode.LOGOUT_FAILED, authException.getMessage(), null); + objectResponse = WebUtil.objectResponse(ResponseCode.LOGIN_USERNAME_PASSWORD_ERROR, authException.getMessage(), null); } else if (authException instanceof InsufficientAuthenticationException) { objectResponse = WebUtil.objectResponse(ResponseCode.UNAUTHORIZED, authException.getMessage(), null); } else { diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/ILoginService.java b/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/ILoginService.java index 196eeb5..b791258 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/ILoginService.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/ILoginService.java @@ -7,5 +7,5 @@ import java.util.HashMap; public interface ILoginService { HashMap login(User user); - boolean logout(); + boolean logout(String token); } diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/impl/LoginServiceImpl.java b/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/impl/LoginServiceImpl.java index 2c8c1ea..96760df 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/impl/LoginServiceImpl.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/service/permission/impl/LoginServiceImpl.java @@ -9,11 +9,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Objects; +import java.util.concurrent.TimeUnit; @Service public class LoginServiceImpl implements ILoginService { @@ -46,17 +46,13 @@ public class LoginServiceImpl implements ILoginService { HashMap hashMap = new HashMap<>(); hashMap.put("token", jwt); - redisCache.setCacheObject("login:" + userId, loginUser); + redisCache.setCacheObject("login:" + jwt, loginUser, 10, TimeUnit.MINUTES); return hashMap; } @Override - public boolean logout() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - LoginUser loginUser = (LoginUser) authentication.getPrincipal(); - - Long userId = loginUser.getUser().getId(); - return redisCache.deleteObject("login:" + userId); + public boolean logout(String token) { + return redisCache.deleteObject("login:" + token); } } diff --git a/Pinnacle/src/main/java/com/cfive/pinnacle/utils/WebUtil.java b/Pinnacle/src/main/java/com/cfive/pinnacle/utils/WebUtil.java index 0c9cd53..da62fe1 100644 --- a/Pinnacle/src/main/java/com/cfive/pinnacle/utils/WebUtil.java +++ b/Pinnacle/src/main/java/com/cfive/pinnacle/utils/WebUtil.java @@ -1,6 +1,5 @@ package com.cfive.pinnacle.utils; -import com.cfive.pinnacle.entity.User; import com.cfive.pinnacle.entity.common.ResponseResult; import com.cfive.pinnacle.entity.permission.LoginUser; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/ui/src/constants/Common.constants.ts b/ui/src/constants/Common.constants.ts index 4f949c1..a68a935 100644 --- a/ui/src/constants/Common.constants.ts +++ b/ui/src/constants/Common.constants.ts @@ -10,6 +10,31 @@ const SIZE_ICON_MD = '24px' const SIZE_ICON_LG = '32px' const SIZE_ICON_XL = '64px' +// Response Code +const SYSTEM_OK = 20000 +const LOGIN_SUCCESS = 20010 +const LOGIN_USERNAME_PASSWORD_ERROR = 20011 +const LOGOUT_SUCCESS = 20015 +const LOGOUT_FAILED = 20016 +const TOKEN_IS_ILLEGAL = 20017 +const TOKEN_HAS_EXPIRED = 20018 +const DATABASE_SELECT_OK = 20021 +const DATABASE_SAVE_OK = 20022 +const DATABASE_UPDATE_OK = 20023 +const DATABASE_DELETE_OK = 20024 +const DATABASE_SELECT_ERROR = 20031 +const DATABASE_SAVE_ERROR = 20032 +const DATABASE_UPDATE_ERROR = 20033 +const DATABASE_DELETE_ERROR = 20034 +const DATABASE_TIMEOUT_ERROR = 20035 +const DATABASE_CONNECT_ERROR = 20036 + +const UNAUTHORIZED = 30010 +const ACCESS_DENIED = 30030 + +const SYSTEM_ERROR = 50001 +const SYSTEM_TIMEOUT = 50002 + export { PRODUCTION_NAME, TOKEN_NAME, @@ -21,5 +46,26 @@ export { SIZE_ICON_SM, SIZE_ICON_MD, SIZE_ICON_LG, - SIZE_ICON_XL + SIZE_ICON_XL, + SYSTEM_OK, + LOGIN_SUCCESS, + LOGIN_USERNAME_PASSWORD_ERROR, + LOGOUT_SUCCESS, + LOGOUT_FAILED, + TOKEN_IS_ILLEGAL, + TOKEN_HAS_EXPIRED, + DATABASE_SELECT_OK, + DATABASE_SAVE_OK, + DATABASE_UPDATE_OK, + DATABASE_DELETE_OK, + DATABASE_SELECT_ERROR, + DATABASE_SAVE_ERROR, + DATABASE_UPDATE_ERROR, + DATABASE_DELETE_ERROR, + DATABASE_TIMEOUT_ERROR, + DATABASE_CONNECT_ERROR, + UNAUTHORIZED, + ACCESS_DENIED, + SYSTEM_ERROR, + SYSTEM_TIMEOUT } diff --git a/ui/src/main.ts b/ui/src/main.ts index c09550d..50220f9 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -5,6 +5,8 @@ import router from '@/router' import '@/assets/css/base.css' import '@/assets/css/common.css' +import 'element-plus/theme-chalk/el-message.css' + const app = createApp(App) app.use(router).mount('#app') diff --git a/ui/src/pages/Login.vue b/ui/src/pages/Login.vue index b6a6e8a..349d885 100644 --- a/ui/src/pages/Login.vue +++ b/ui/src/pages/Login.vue @@ -80,8 +80,8 @@ import { getCaptchaSrc, login, verifyCaptcha } from '@/utils/auth' import backShape from '@/assets/svg/back-shape.svg' import { ElMessage } from 'element-plus' -import 'element-plus/theme-chalk/el-message.css' -import { PRODUCTION_NAME } from '@/constants/Common.constants' +import { LOGIN_SUCCESS, PRODUCTION_NAME } from '@/constants/Common.constants' +import { setToken } from '@/utils/common' export default { name: 'LoginPage', @@ -102,7 +102,8 @@ export default { getNewCaptcha() { this.captchaSrc = getCaptchaSrc() }, - async login() { + login() { + const _this = this if (!this.userName) { ElMessage.error({ dangerouslyUseHTMLString: true, @@ -131,22 +132,25 @@ export default { }) return } - if (await login(this.userName, this.password)) { - ElMessage.success({ - dangerouslyUseHTMLString: true, - message: '登录成功' - }) - this.loggingIn = true - const _this = this - setTimeout(function () { - _this.$router.push('/') - }, 1500) - } else { - ElMessage.error({ - dangerouslyUseHTMLString: true, - message: '用户名密码 错误' - }) - } + login(this.userName, this.password).then((res) => { + const data = res.data + if (data.code === LOGIN_SUCCESS) { + setToken(data.data.token) + ElMessage.success({ + dangerouslyUseHTMLString: true, + message: '登录成功' + }) + this.loggingIn = true + setTimeout(function () { + _this.$router.push('/') + }, 1500) + } else { + ElMessage.error({ + dangerouslyUseHTMLString: true, + message: '用户名密码 错误' + }) + } + }) } } } diff --git a/ui/src/pages/Main.vue b/ui/src/pages/Main.vue index 54d4a0d..efd8e42 100644 --- a/ui/src/pages/Main.vue +++ b/ui/src/pages/Main.vue @@ -156,6 +156,7 @@ import { } from '@/constants/Common.constants.js' import _ from 'lodash' import { getUsername, logout } from '@/utils/auth' +import { ElMessage } from 'element-plus' export default { name: 'MainFrame', @@ -187,7 +188,13 @@ export default { }, logout() { logout() - this.$router.push({ name: 'Login' }) + ElMessage.success({ + dangerouslyUseHTMLString: true, + message: '退出登录' + }) + setTimeout(() => { + this.$router.push({ name: 'Login' }) + }, 1500) } }, mounted() { diff --git a/ui/src/services/index.ts b/ui/src/services/index.ts index 8813f04..6a338c7 100644 --- a/ui/src/services/index.ts +++ b/ui/src/services/index.ts @@ -1,6 +1,8 @@ import axios, { type AxiosError } from 'axios' -import { getToken, removeToken } from '@/utils/common' +import { clearLocalStorage, getToken } from '@/utils/common' import router from '@/router' +import { TOKEN_HAS_EXPIRED, TOKEN_IS_ILLEGAL, UNAUTHORIZED } from '@/constants/Common.constants' +import { ElMessage } from 'element-plus' const service = axios.create({ baseURL: 'http://localhost:8621', @@ -23,17 +25,26 @@ service.interceptors.request.use( service.interceptors.response.use( (response) => { + switch (response.data.code) { + case UNAUTHORIZED: + case TOKEN_IS_ILLEGAL: + case TOKEN_HAS_EXPIRED: + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + console.log(`request error: ${response.data.code} - ${response.data.msg}`) + clearLocalStorage() + ElMessage.error({ + dangerouslyUseHTMLString: true, + message: '登录已过期' + }) + setTimeout(function () { + void router.push({ name: 'Login' }) + }, 1500) + } return response }, async (error) => { if (error.response != null) { - // eslint-disable-next-line @typescript-eslint/restrict-template-expressions - console.log(`request error: ${error.response.code} - ${error.response.msg}`) - switch (error.response.code) { - case 30010: - removeToken() - await router.push({ name: 'Login' }) - } + /* empty */ } return await Promise.reject(error?.response?.data) } diff --git a/ui/src/utils/auth.ts b/ui/src/utils/auth.ts index 6495edb..4e5bda1 100644 --- a/ui/src/utils/auth.ts +++ b/ui/src/utils/auth.ts @@ -1,33 +1,19 @@ import type { Captcha } from './common' -import { - getCaptcha, - getLocalStorage, - getToken, - removeLocalStorage, - setLocalStorage, - setToken -} from './common' +import { clearLocalStorage, getCaptcha, getLocalStorage, setLocalStorage } from './common' import { TOKEN_NAME } from '@/constants/Common.constants' import _ from 'lodash' import request from '@/services' let captcha: Captcha -async function login(username: string, passwd: string): Promise { - removeLocalStorage('username') - await request.post('/login', { username, passwd }).then((res: any) => { - const response = res.data - if (response.code === 20010) { - setToken(response.data.token) - } - }) - - return !_.isEmpty(getToken()) +async function login(username: string, passwd: string): Promise { + return await request.post('/login', { username, passwd }) } function logout(): void { - removeLocalStorage(TOKEN_NAME) - removeLocalStorage('username') + void request.get('/logout').finally(() => { + clearLocalStorage() + }) } function getLoginStatus(): boolean { diff --git a/ui/src/utils/common.ts b/ui/src/utils/common.ts index d37d57c..fd1182f 100644 --- a/ui/src/utils/common.ts +++ b/ui/src/utils/common.ts @@ -77,6 +77,10 @@ function removeToken(): void { removeLocalStorage(TOKEN_NAME) } +function clearLocalStorage(): void { + localStorage.clear() +} + function randomInt(start: number, end: number): number { if (start > end) { const t = start @@ -149,5 +153,6 @@ export { removeCookie, removeLocalStorage, removeToken, + clearLocalStorage, getCaptcha }