vue3整合SpringSecurity加JWT實(shí)現(xiàn)登錄認(rèn)證
前段時(shí)間寫了一篇spring security的詳細(xì)入門,但是沒有聯(lián)系實(shí)際。
所以這次在真實(shí)的項(xiàng)目中來演示一下怎樣使用springsecurity來實(shí)現(xiàn)我們最常用的登錄校驗(yàn)。本次演示使用現(xiàn)在市面上最常見的開發(fā)方式,前后端分離開發(fā)。前端使用vue3進(jìn)行構(gòu)建,用到了element-plus組件庫、axios封裝、pinia狀態(tài)管理、Router路由跳轉(zhuǎn)等技術(shù)。后端還是spring boot整合springsecurity+JWT來實(shí)現(xiàn)登錄校驗(yàn)。
本文適合有一定基礎(chǔ)的人來看,如果你對springsecurity安全框架還不是很了解,建議你先去看一下我之前寫過的spring security框架的快速入門:
springboot3整合SpringSecurity實(shí)現(xiàn)登錄校驗(yàn)與權(quán)限認(rèn)證(萬字超詳細(xì)講解)
技術(shù)棧版本:vue3.3.11、springboot3.1.5、spring security6.x
業(yè)務(wù)流程:

可以看到整個(gè)業(yè)務(wù)的流程還是比較簡單的,那么接下來就基于這個(gè)業(yè)務(wù)流程來進(jìn)行我們具體代碼的編寫和實(shí)現(xiàn);
前端:
新建一個(gè)vue項(xiàng)目,并引入一些具體的依賴;我們本次項(xiàng)目用到的有:element-plus、axios、pinia狀態(tài)管理、Router路由跳轉(zhuǎn)(注意我們在項(xiàng)目中使用到的pinia要引入持久化插件)
在vue項(xiàng)目中新建兩個(gè)組件:Login.vue(登錄組件,負(fù)責(zé)登錄頁面的展示)、Layout.vue(布局頁面,負(fù)責(zé)整體項(xiàng)目的布局,登錄成功之后就是跳轉(zhuǎn)到這個(gè)頁面)
路由的定義:在router文件夾下新建index.ts文件
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'login',
component: () => import('@/components/Login.vue')
},
{
path: '/layout',
name: 'layout',
component: () => import('@/components/Layout.vue')
}
]
})
export default router
定義Login登錄組件為默認(rèn)的組件,并定義Layout組件;
useToken的狀態(tài)封裝:在stoers文件夾下新建useToken.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
const useTokenStore = defineStore('token', ()=>{
const token=ref()
const removeToken=()=>{
token.value=''
}
return {token,removeToken}
},
{persist: true}
)
export default useTokenStore
axios的封裝:在utils文件夾在新建request.ts文件
import axios from "axios";
import useTokenStore from '@/stores/useToken'
import { ElMessage } from 'element-plus';
// 先建一個(gè)api
const api = axios.create({
baseURL: "http://localhost:8888",
timeout: 5000
});
// 發(fā)送請求前攔截
api.interceptors.request.use(
config =>{
const useToken = useTokenStore();
if(useToken.token){
console.log("請求頭toekn=====>", useToken.token);
// 設(shè)置請求頭
// config.headers['token'] = useToken.token;
config.headers.token = useToken.token;
}
return config;
},
error =>{
return Promise.reject(error);
}
)
// 響應(yīng)前攔截
api.interceptors.response.use(
response =>{
console.log("響應(yīng)數(shù)據(jù)", response);
if(response.data.code !=200){
ElMessage.error(response.data.message);
}
return response;
},
error =>{
return Promise.reject(error);
}
)
export default api;
在請求前攔截,主要是為了在請求頭中新增token。在request.ts中引入了useToken,并判斷如果token不為空,那么在請求頭中新增token。
在響應(yīng)前也進(jìn)行了一次攔截,如果后端返回的狀態(tài)碼不為200,那么就打印出錯(cuò)誤信息;
接下來就可以在Login.vue中進(jìn)行我們的登錄邏輯的具體編寫了(我直接將組件內(nèi)容進(jìn)行復(fù)制了,也不是什么太難的東西,主要還是element-plus的表單):
<template>
<div class="background" style="font-family:kaiti" >
<!-- 注冊表單 -->
<el-dialog v-model="isRegister" title="用戶注冊" width="30%">
<el-form label-width="120px" v-model="registerForm">
<el-form-item label="用戶名">
<el-input type="text" v-model="registerForm.username" >
<template #prefix>
<el-icon><Avatar /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="密碼">
<el-input type="password" v-model="registerForm.password" >
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="registerAdd" >提交</el-button>
<el-button @click="isRegister = false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
<!-- 登陸框 -->
<div class="login-box">
<el-form
label-width="100px"
:model="loginFrom"
style="max-width: 460px"
:rules="Loginrules"
ref="ruleFormRef"
>
<el-form-item label="用戶名" prop="username">
<el-input v-model="loginFrom.username" clearable >
<template #prefix>
<el-icon><Avatar /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="密碼" prop="password">
<el-input v-model="loginFrom.password" show-password clearable type="password" >
<template #prefix>
<el-icon><Lock /></el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item label="驗(yàn)證碼" prop="codeValue">
<el-input v-model="loginFrom.codeValue" style="width: 100px;" clearable >
</el-input>
<img :src="codeImage" @click="getCode" style="transform: scale(0.9);"/>
</el-form-item>
<el-button type="success" @click="getLogin(ruleFormRef)" style="transform: translateX(50px)" class="my-button">登錄</el-button>
<el-button type="primary" @click="isRegister=true" class="my-button">注冊</el-button>
</el-form>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref,onMounted,reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import useTokenStore from '@/stores/useToken'
import api from '@/utils/request'
import type { FormInstance, FormRules } from 'element-plus'
const ruleFormRef = ref<FormInstance>()
const loginFrom=ref({
username:'',
password:'',
codeKey:'',
codeValue:''
})
const Loginrules=reactive({
username: [
{ required: true, message: '請輸入用戶名', trigger: 'blur' }
],
password: [
{ required: true, message: '請輸入密碼', trigger: 'blur' },
{ min: 6, max: 12, message: '長度在 6 到 12 個(gè)字符', trigger: 'blur'}
],
codeValue: [
{ required: true, message: '請輸入驗(yàn)證碼', trigger: 'blur' }
]
})
const registerForm=ref({
username:'',
password:''
})
const codeImage=ref('')
const isRegister=ref(false)
const tokenStore = useTokenStore();
const router = useRouter()
const getLogin = async(formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
console.log('submit!')
} else {
ElMessage('請輸入完整信息')
return;
}
})
let {data}=await api.post('/user/login',loginFrom.value)
if(data.code==200){
ElMessage('登錄成功')
console.log(data);
tokenStore.token=data.data
router.replace({name:'layout'})
}else{
ElMessage('登錄失敗')
}
}
const getCode=async()=>{
let {data}=await api.get('/getCaptcha')
loginFrom.value.codeKey=data.data.codeKey
codeImage.value=data.data.codeValue
}
const registerAdd=async()=>{
let {data}=await api.post('/user/register',registerForm.value)
if(data.code==200){
ElMessage('注冊成功')
isRegister.value=false
}else{
ElMessage('注冊失敗')
isRegister.value=false
}
}
// 頁面加載完成獲取驗(yàn)證碼
onMounted(()=>{
getCode()
})
</script>這個(gè)頁面中,我還加入了一個(gè)圖形驗(yàn)證碼。還有一個(gè)注冊的表單。其他的就和普通的登錄一樣了;
這個(gè)頁面的最終效果如圖:

Layout.vue頁面中,我們只進(jìn)行兩個(gè)方法的測試;一個(gè)是獲取當(dāng)前用戶的具體信息,一個(gè)是退出登錄的按鈕;
<template>
<div class="common-layout">
<el-container>
<el-header height="100px">
頭部
<el-button type="primary" @click="getUserInfo">獲取用戶信息</el-button>
<el-button type="success" @click="Logout">退出登錄</el-button>
</el-header>
<el-container>
<el-aside width="200px">
菜單欄
</el-aside>
<el-main>
展示區(qū)
</el-main>
</el-container>
</el-container>
</div>
</template>
<script lang="ts" setup name="Layout">
import { ref } from 'vue'
import api from '@/utils/request'
import {ElMessage} from 'element-plus'
import { useRouter } from 'vue-router'
import useToeknStore from '@/stores/useToken'
const router = useRouter()
const Logout =async () => {
let data= api.get("/user/logout")
if(data.data.code==200){
ElMessage.success('退出成功')
// 清除token
useToeknStore().removeToken
router.replace({name:'login'})
}
else{
ElMessage.error('退出失敗')
}
}
const getUserInfo = async() => {
let data=await api.get("/user/info")
console.log('@',data);
}
</script>
數(shù)據(jù)庫:
我新建一個(gè)數(shù)據(jù)表,用于登錄校驗(yàn):
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
status INT DEFAULT 0
);
這張表中只有簡單的用戶名,密碼,和用戶是否過期等字段;
后端:
新建一個(gè)spring boot項(xiàng)目,并導(dǎo)入以下的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.3.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.21</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>后端使用MybatisPlus做用戶的增、刪、改、查等?;A(chǔ)的controller、service、mapper,我就不再這里進(jìn)行贅述了;
新建一個(gè)類MyTUserDetail ,繼承UserDetail:
@Data
public class MyTUserDetail implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
private Users Users;
@JsonIgnore //json忽略
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@JsonIgnore
@Override
public String getPassword() {
return this.getUsers().getPassword();
}
@JsonIgnore
@Override
public String getUsername() {
return this.getUsers().getUsername();
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return this.getUsers().getStatus()==0;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return this.getUsers().getStatus()==0;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return this.getUsers().getStatus()==0;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return this.getUsers().getStatus()==0;
}
}
新建一個(gè)類MyUserDetailServerImpl,實(shí)現(xiàn)MyUserDetailServer接口的loadUserByUsername方法
@Service
public class MyUserDetailServerImpl implements MyUserDetailServer {
@Autowired
UserMapper userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.selectOne(new LambdaQueryWrapper<User>().
eq(username != null, User::getUsername, username));
if (tUser == null) {
throw new UsernameNotFoundException("用戶名不存在");
}
MyTUserDetail myTUserDetail=new MyTUserDetail();
myTUserDetail.setUser(user);
return myTUserDetail;
}
}
新建一個(gè)JwtUtils的工具類,來生成token;
@Component
public class JwtUtil {
private final String secret="zhangqiao";
private final Long expiration=36000000L;
public String generateToken(Integer id) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
.withSubject(String.valueOf(id))
.withIssuedAt(now)
.withExpiresAt(expiryDate)
.sign(algorithm);
}
public Integer getUsernameFromToken(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
return Integer.valueOf(jwt.getSubject());
} catch (JWTDecodeException e) {
return null;
}
}
/*
* 判斷token是否過期
* */
public boolean isTokenValid(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWT.require(algorithm).build().verify(token);
return true;
} catch (Exception e) {
return false;
}
}
/*
* 刷新token
* */
public String refreshToken(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
String username = jwt.getSubject();
Algorithm algorithm = Algorithm.HMAC256(secret);
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return JWT.create()
.withSubject(username)
.withIssuedAt(now)
.withExpiresAt(expiryDate)
.sign(algorithm);
} catch (JWTDecodeException e) {
return null;
}
}
}新建一個(gè)Jwt的攔截類,繼承一個(gè)OncePerRequestFilter類,用來在每次請求前攔截請求,并從中獲取token,并判斷這個(gè)token是否是我們用戶表中的token;
如果是,那么將用戶信息存儲到security中,這樣后面的過濾器就可以獲取到用戶信息了,如果不是,那么直接放行。我們會將這個(gè)攔截器加入到UsernamePasswordAuthenticationFilter過濾器之前。
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//獲取請求頭中的token
String token = request.getHeader("token");
System.out.println("前端的token信息=======>"+token);
//如果token為空直接放行,由于用戶信息沒有存放在SecurityContextHolder.getContext()中所以后面的過濾器依舊認(rèn)證失敗符合要求
if(!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
// 解析Jwt中的用戶id
Integer userId = jwtUtil.getUsernameFromToken(token);
//從redis中獲取用戶信息
String redisUser = redisTemplate.opsForValue().get(String.valueOf(userId));
if(!StringUtils.hasText(redisUser)){
filterChain.doFilter(request,response);
return;
}
MyTUserDetail myTUserDetail= JSON.parseObject(redisUser, MyTUserDetail.class);
//將用戶信息存放在SecurityContextHolder.getContext(),后面的過濾器就可以獲得用戶信息了。這表明當(dāng)前這個(gè)用戶是登錄過的,后續(xù)的攔截器就不用再攔截了
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,null);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
filterChain.doFilter(request,response);
}
}security配置類的設(shè)置:
(由于我們本次采用前后端分離的方式來進(jìn)行開發(fā),所以不在需要使用spring security默認(rèn)提供的formLogin 方法)
formLogin 方法是 Spring Security 中用于配置基于表單的登錄認(rèn)證的一種方式。它通常用于傳統(tǒng)的 Web 應(yīng)用程序,其中前端頁面由后端動態(tài)生成,并且用戶在頁面中輸入用戶名和密碼來進(jìn)行登錄。在這種情況下,Spring Security 負(fù)責(zé)處理登錄請求、驗(yàn)證用戶身份、生成會話等操作。
但是,在前后端分離的開發(fā)模式中,前端和后端是完全分離的,前端負(fù)責(zé)渲染界面和處理用戶交互,后端負(fù)責(zé)提供 API 接口和數(shù)據(jù)服務(wù)。因此,通常不會使用 formLogin 方法,因?yàn)槲覀兊那岸瞬粫ㄟ^后端渲染的頁面來進(jìn)行登錄。后端只需要返回一些相應(yīng)的數(shù)據(jù)和狀態(tài),有關(guān)頁面的跳轉(zhuǎn)和渲染是由前端(vue3)來實(shí)現(xiàn)的。
@Configuration
@EnableWebSecurity
public class MyServiceConfig {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
/*
* security的過濾器鏈
* */
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)throws Exception {
http.csrf(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests((auth) ->
auth
.requestMatchers("/getCaptcha","user/login","user/register").permitAll()
.anyRequest().authenticated()
);
http.cors(cors->{
cors.configurationSource(corsConfigurationSource());
});
//自定義過濾器放在UsernamePasswordAuthenticationFilter過濾器之前
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Autowired
private MyUserDetailServerImpl myUserDetailsService;
/*
* 驗(yàn)證管理器
* */
@Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){
DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//將編寫的UserDetailsService注入進(jìn)來
provider.setUserDetailsService(myUserDetailsService);
//將使用的密碼編譯器加入進(jìn)來
provider.setPasswordEncoder(passwordEncoder);
//將provider放置到AuthenticationManager 中
ProviderManager providerManager=new ProviderManager(provider);
return providerManager;
}
//跨域配置
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
/*
* 密碼加密器*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
在security的配置類中,設(shè)置了跨域問題、攔截器鏈的配置(并將一些需要放行的接口放行,將我們自定義的Jwt攔截器加入了security攔截鏈)、密碼編譯器、AuthenticationManager 驗(yàn)證管理等等一系列配置;
Usercontroller控制器:
@RestController
@RequestMapping("/user")
public class UsersController {
@Autowired
private IUsersService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private JwtUtils jwtUtils;
@PostMapping("/login")
public Result<String> login(@RequestBody DtoLogin dtoLogin) {
System.out.println(dtoLogin);
String token = userService.login(dtoLogin);
return Result.successData(token);
}
@PostMapping("/register")
public Result register(@RequestBody DtoLogin dtoLogin) {
System.out.println(dtoLogin);
Users users = new Users();
users.setUsername(dtoLogin.getUsername());
users.setPassword(passwordEncoder.encode(dtoLogin.getPassword()));
userService.save(users);
return Result.success();
}
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
private JwtUtil jwtUtil;
@GetMapping("/info")
public Result info(@RequestHeader("token")String token){
System.out.println("controller層獲取到的token=======>"+token);
Integer id = jwtUtil.getUsernameFromToken(token);
String redisUser = redisTemplate.opsForValue().get(String.valueOf(id));
MyTUserDetail myTUserDetail = JSON.parseObject(redisUser, MyTUserDetail.class);
return Result.successData(myTUserDetail);
}
@GetMapping("user/logout")
public Result logout(@RequestHeader("token")String token){
// 解析Jwt中的用戶id
Integer userId = jwtUtil.getUsernameFromToken(token);
//清除SpringSecurity上下文
SecurityContextHolder.clearContext();
//刪除redis中存儲的用戶數(shù)據(jù)
redisTemplate.delete(Integer.toString(userId));
return Result.success();
}
}
在UserController控制器中,由于登錄方法比較復(fù)雜,我將登錄方法重新在service中重寫了,剩下的獲取用戶信息、用戶注冊、退出登錄都直接在UseController中實(shí)現(xiàn)了;
service中重寫的登錄方法:
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements IUsersService {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Autowired
AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@Override
public String login(DtoLogin dtoLogin) {
String codeRedis = redisTemplate.opsForValue().get(dtoLogin.getCodeKey());
if (!dtoLogin.getCodeValue().equals(codeRedis)){
throw new ResultException(400,"驗(yàn)證碼錯(cuò)誤");
}
// 驗(yàn)證碼正確,刪除redis中的驗(yàn)證碼
redisTemplate.delete(dtoLogin.getCodeKey());
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(dtoLogin.getUsername(),dtoLogin.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(authenticate==null){
throw new ResultException(400,"用戶名或密碼錯(cuò)誤");
}
// 獲取返回的用戶信息
Object principal = authenticate.getPrincipal();
MyTUserDetail myTUserDetail=(MyTUserDetail) principal;
System.out.println(myTUserDetail);
// 使用Jwt生成token,并將用戶的id傳入
String token = jwtUtil.generateToken(myTUserDetail.getUsers().getId());
redisTemplate.opsForValue().
set(String.valueOf(myTUserDetail.getUsers().getId()), JSON.toJSONString(myTUserDetail),1, TimeUnit.DAYS);
return token;
}
}由于我們還是用了驗(yàn)證碼,所以在這個(gè)登錄方法中先判斷了驗(yàn)證碼、如果驗(yàn)證碼正確。那么在判斷傳回來的用戶名和密碼。如果都正確,那么用Jwt返回一個(gè)token,token中攜帶的是用戶的id;
至此,我們所有的前后端代碼都已經(jīng)寫完了。那么,讓我們具體的實(shí)驗(yàn)一下;
運(yùn)行:
由于我剛創(chuàng)建的表,還沒有添加數(shù)據(jù),那么我現(xiàn)在前端點(diǎn)擊注冊,寫入幾條用戶信息;

寫入信息之后,我使用剛注冊過的用戶來登錄一下:

注冊成功之后,就會進(jìn)入到我們自定義個(gè)Layout.vue組件內(nèi):

現(xiàn)在,我點(diǎn)擊“獲取用戶信息”按鈕,因?yàn)檫@個(gè)路徑我們并沒有放行,那么他訪問時(shí)就會被我們自定義的Jwt攔截器攔截,并驗(yàn)證它請求頭中攜帶的token是否正確。如果正確,則放行。如果不正確,那么就會放行到登錄攔截器中。

可以看到,在控制臺中打印出了用戶的信息。這是肯定的,因?yàn)樗@次請求攜帶的token是正確的,那么如果我們修改一下token的值,他還能正常訪問到用戶信息這個(gè)接口嗎?

我修改了請求頭中的token信息,可以看到立馬這個(gè)請求就被攔截了。并爆出了403錯(cuò)誤;
現(xiàn)在,我點(diǎn)擊“退出登錄”按鈕,它應(yīng)該刪除useToken中的token值,并且后端也會刪除redis中的值,并且跳轉(zhuǎn)到登錄頁面。后端也會刪除redsi中存儲的用戶數(shù)據(jù);

現(xiàn)在,我們所有的任務(wù)都已經(jīng)完成了。
具體的前后端源碼放在碼云上面了,有需要的可以自行下載:
我再整體理一下具體的思路:
前端發(fā)送請求后端,如果是登錄請求,那么直接走登錄接口即可,我將登錄接口進(jìn)行了方行,任何人都可以訪問到登錄接口,并且執(zhí)行登錄接口的邏輯;如果登錄成功,會返回一個(gè)token,前后會將這個(gè)token存到useToken中,并且再以后的每次請求中都攜帶token;如果登錄失敗,返回一個(gè)報(bào)錯(cuò)信息即可。
如果前端發(fā)送的不是登錄接口,但是前端攜帶可正確的token,那么會被我們自定義的Jwt攔截器攔截,并從中讀取用戶信息,放到security中共后續(xù)的攔截器使用;如果沒有攜帶token,或者token不正確,那么后端會直接返回403的狀態(tài)碼提示;
后續(xù):權(quán)限校驗(yàn)
前后端分離,使用vue3整合SpringSecurity加JWT實(shí)現(xiàn)權(quán)限校驗(yàn)-CSDN博客
到此這篇關(guān)于vue3整合SpringSecurity加JWT實(shí)現(xiàn)登錄認(rèn)證的文章就介紹到這了,更多相關(guān)vue3 SpringSecurity登錄認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
手把手教你Vue3實(shí)現(xiàn)路由跳轉(zhuǎn)
Vue Router是Vue.js的官方路由器,它與Vue.js核心深度集成,使使用Vue.js構(gòu)建單頁應(yīng)用程序變得輕而易舉,下面這篇文章主要給大家介紹了關(guān)于Vue3實(shí)現(xiàn)路由跳轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下2022-08-08
vue中的eventBus會不會產(chǎn)生內(nèi)存泄漏你知道嗎
這篇文章主要為大家詳細(xì)介紹了vue中的eventBus會不會產(chǎn)生內(nèi)存泄漏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
vue3中defineEmits與defineProps的用法實(shí)例
這篇文章主要介紹了vue3中defineEmits/defineProps的用法實(shí)例,需要的朋友可以參考下2023-12-12
Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流
本文主要介紹了Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12
vue.js將unix時(shí)間戳轉(zhuǎn)換為自定義時(shí)間格式
這篇文章主要介紹了vue.js將unix時(shí)間戳轉(zhuǎn)換為自定義時(shí)間格式的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
使用idea創(chuàng)建vue項(xiàng)目的圖文教程
Vue.js是一套構(gòu)建用戶界面的框架,只關(guān)注視圖層,它不僅易于上手,還便于與第三方庫或既有項(xiàng)目整合,下面這篇文章主要給大家介紹了關(guān)于使用idea創(chuàng)建vue項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2022-08-08

