欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3整合SpringSecurity加JWT實(shí)現(xiàn)登錄認(rèn)證

 更新時(shí)間:2025年04月13日 11:26:09   作者:張喬24  
本文主要介紹了vue3整合SpringSecurity加JWT實(shí)現(xiàn)登錄認(rèn)證,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前段時(shí)間寫(xiě)了一篇spring security的詳細(xì)入門,但是沒(méi)有聯(lián)系實(shí)際。

所以這次在真實(shí)的項(xiàng)目中來(lái)演示一下怎樣使用springsecurity來(lái)實(shí)現(xiàn)我們最常用的登錄校驗(yàn)。本次演示使用現(xiàn)在市面上最常見(jiàn)的開(kāi)發(fā)方式,前后端分離開(kāi)發(fā)。前端使用vue3進(jìn)行構(gòu)建,用到了element-plus組件庫(kù)、axios封裝、pinia狀態(tài)管理、Router路由跳轉(zhuǎn)等技術(shù)。后端還是spring boot整合springsecurity+JWT來(lái)實(shí)現(xiàn)登錄校驗(yàn)。

本文適合有一定基礎(chǔ)的人來(lái)看,如果你對(duì)springsecurity安全框架還不是很了解,建議你先去看一下我之前寫(xiě)過(guò)的spring security框架的快速入門:

springboot3整合SpringSecurity實(shí)現(xiàn)登錄校驗(yàn)與權(quán)限認(rèn)證(萬(wàn)字超詳細(xì)講解)

技術(shù)棧版本:vue3.3.11、springboot3.1.5、spring security6.x

業(yè)務(wù)流程:

可以看到整個(gè)業(yè)務(wù)的流程還是比較簡(jiǎn)單的,那么接下來(lái)就基于這個(gè)業(yè)務(wù)流程來(lái)進(jìn)行我們具體代碼的編寫(xiě)和實(shí)現(xiàn);

前端:

新建一個(gè)vue項(xiàng)目,并引入一些具體的依賴;我們本次項(xiàng)目用到的有:element-plus、axios、pinia狀態(tài)管理、Router路由跳轉(zhuǎn)(注意我們?cè)陧?xiàng)目中使用到的pinia要引入持久化插件)

vue3中pinia的使用及持久化(詳細(xì)解釋)

在vue項(xiàng)目中新建兩個(gè)組件:Login.vue(登錄組件,負(fù)責(zé)登錄頁(yè)面的展示)、Layout.vue(布局頁(yè)面,負(fù)責(zé)整體項(xiàng)目的布局,登錄成功之后就是跳轉(zhuǎn)到這個(gè)頁(yè)面)

路由的定義:在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ā)送請(qǐng)求前攔截
api.interceptors.request.use(
    config =>{
const useToken = useTokenStore();
if(useToken.token){
    console.log("請(qǐng)求頭toekn=====>", useToken.token);
    // 設(shè)置請(qǐng)求頭
    // 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;

在請(qǐng)求前攔截,主要是為了在請(qǐng)求頭中新增token。在request.ts中引入了useToken,并判斷如果token不為空,那么在請(qǐng)求頭中新增token。

在響應(yīng)前也進(jìn)行了一次攔截,如果后端返回的狀態(tài)碼不為200,那么就打印出錯(cuò)誤信息;

接下來(lái)就可以在Login.vue中進(jìn)行我們的登錄邏輯的具體編寫(xiě)了(我直接將組件內(nèi)容進(jìn)行復(fù)制了,也不是什么太難的東西,主要還是element-plus的表單):

<template>
    <div class="background" style="font-family:kaiti" >

<!-- 注冊(cè)表單 -->
<el-dialog v-model="isRegister" title="用戶注冊(cè)" 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">注冊(cè)</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: '請(qǐng)輸入用戶名', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '請(qǐng)輸入密碼', trigger: 'blur' },
    { min: 6, max: 12, message: '長(zhǎng)度在 6 到 12 個(gè)字符', trigger: 'blur'}
  ],
  codeValue: [
    { required: true, message: '請(qǐng)輸入驗(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('請(qǐng)輸入完整信息')
      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('注冊(cè)成功')
  isRegister.value=false
}else{

  ElMessage('注冊(cè)失敗')
  isRegister.value=false
  }

}

// 頁(yè)面加載完成獲取驗(yàn)證碼

onMounted(()=>{
getCode()

})


</script>

這個(gè)頁(yè)面中,我還加入了一個(gè)圖形驗(yàn)證碼。還有一個(gè)注冊(cè)的表單。其他的就和普通的登錄一樣了;

這個(gè)頁(yè)面的最終效果如圖:

Layout.vue頁(yè)面中,我們只進(jìn)行兩個(gè)方法的測(cè)試;一個(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ù)庫(kù):

我新建一個(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
);

這張表中只有簡(jiǎn)單的用戶名,密碼,和用戶是否過(guò)期等字段;

后端:

新建一個(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的工具類,來(lái)生成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是否過(guò)期
    * */
    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類,用來(lái)在每次請(qǐng)求前攔截請(qǐng)求,并從中獲取token,并判斷這個(gè)token是否是我們用戶表中的token;

如果是,那么將用戶信息存儲(chǔ)到security中,這樣后面的過(guò)濾器就可以獲取到用戶信息了,如果不是,那么直接放行。我們會(huì)將這個(gè)攔截器加入到UsernamePasswordAuthenticationFilter過(guò)濾器之前。

@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 {
        //獲取請(qǐng)求頭中的token
        String token = request.getHeader("token");
        System.out.println("前端的token信息=======>"+token);
        //如果token為空直接放行,由于用戶信息沒(méi)有存放在SecurityContextHolder.getContext()中所以后面的過(guò)濾器依舊認(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(),后面的過(guò)濾器就可以獲得用戶信息了。這表明當(dāng)前這個(gè)用戶是登錄過(guò)的,后續(xù)的攔截器就不用再攔截了
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,null);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(request,response);
    }
}

security配置類的設(shè)置:

(由于我們本次采用前后端分離的方式來(lái)進(jìn)行開(kāi)發(fā),所以不在需要使用spring security默認(rèn)提供的formLogin 方法)

formLogin 方法是 Spring Security 中用于配置基于表單的登錄認(rèn)證的一種方式。它通常用于傳統(tǒng)的 Web 應(yīng)用程序,其中前端頁(yè)面由后端動(dòng)態(tài)生成,并且用戶在頁(yè)面中輸入用戶名和密碼來(lái)進(jìn)行登錄。在這種情況下,Spring Security 負(fù)責(zé)處理登錄請(qǐng)求、驗(yàn)證用戶身份、生成會(huì)話等操作。

但是,在前后端分離的開(kāi)發(fā)模式中,前端和后端是完全分離的,前端負(fù)責(zé)渲染界面和處理用戶交互,后端負(fù)責(zé)提供 API 接口和數(shù)據(jù)服務(wù)。因此,通常不會(huì)使用 formLogin 方法,因?yàn)槲覀兊那岸瞬粫?huì)通過(guò)后端渲染的頁(yè)面來(lái)進(jìn)行登錄。后端只需要返回一些相應(yīng)的數(shù)據(jù)和狀態(tài),有關(guān)頁(yè)面的跳轉(zhuǎn)和渲染是由前端(vue3)來(lái)實(shí)現(xiàn)的。

@Configuration
@EnableWebSecurity
public class MyServiceConfig {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

 


    /*
    * security的過(guò)濾器鏈
    * */
@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());
        });
//自定義過(guò)濾器放在UsernamePasswordAuthenticationFilter過(guò)濾器之前
    http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);






    return http.build();
}

@Autowired
private MyUserDetailServerImpl myUserDetailsService;



/*
* 驗(yàn)證管理器
* */
    @Bean
    public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){
        DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//將編寫(xiě)的UserDetailsService注入進(jìn)來(lái)
        provider.setUserDetailsService(myUserDetailsService);
//將使用的密碼編譯器加入進(jìn)來(lái)
        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è)置了跨域問(wèn)題、攔截器鏈的配置(并將一些需要放行的接口放行,將我們自定義的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中存儲(chǔ)的用戶數(shù)據(jù)
redisTemplate.delete(Integer.toString(userId));

return Result.success();
}

}

在UserController控制器中,由于登錄方法比較復(fù)雜,我將登錄方法重新在service中重寫(xiě)了,剩下的獲取用戶信息、用戶注冊(cè)、退出登錄都直接在UseController中實(shí)現(xiàn)了;

service中重寫(xiě)的登錄方法:

@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)證碼正確。那么在判斷傳回來(lái)的用戶名和密碼。如果都正確,那么用Jwt返回一個(gè)token,token中攜帶的是用戶的id;

至此,我們所有的前后端代碼都已經(jīng)寫(xiě)完了。那么,讓我們具體的實(shí)驗(yàn)一下;

運(yùn)行:

由于我剛創(chuàng)建的表,還沒(méi)有添加數(shù)據(jù),那么我現(xiàn)在前端點(diǎn)擊注冊(cè),寫(xiě)入幾條用戶信息;

寫(xiě)入信息之后,我使用剛注冊(cè)過(guò)的用戶來(lái)登錄一下:

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

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

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

我修改了請(qǐng)求頭中的token信息,可以看到立馬這個(gè)請(qǐng)求就被攔截了。并爆出了403錯(cuò)誤;

現(xiàn)在,我點(diǎn)擊“退出登錄”按鈕,它應(yīng)該刪除useToken中的token值,并且后端也會(huì)刪除redis中的值,并且跳轉(zhuǎn)到登錄頁(yè)面。后端也會(huì)刪除redsi中存儲(chǔ)的用戶數(shù)據(jù);

現(xiàn)在,我們所有的任務(wù)都已經(jīng)完成了。

具體的前后端源碼放在碼云上面了,有需要的可以自行下載:

Vue-Security: 前后端分離的Security

我再整體理一下具體的思路:

前端發(fā)送請(qǐng)求后端,如果是登錄請(qǐng)求,那么直接走登錄接口即可,我將登錄接口進(jìn)行了方行,任何人都可以訪問(wèn)到登錄接口,并且執(zhí)行登錄接口的邏輯;如果登錄成功,會(huì)返回一個(gè)token,前后會(huì)將這個(gè)token存到useToken中,并且再以后的每次請(qǐng)求中都攜帶token;如果登錄失敗,返回一個(gè)報(bào)錯(cuò)信息即可。

如果前端發(fā)送的不是登錄接口,但是前端攜帶可正確的token,那么會(huì)被我們自定義的Jwt攔截器攔截,并從中讀取用戶信息,放到security中共后續(xù)的攔截器使用;如果沒(méi)有攜帶token,或者token不正確,那么后端會(huì)直接返回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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue組件間通信全面講解

    vue組件間通信全面講解

    這篇文章主要介紹了vue組件間通信全面講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • vue中使用require動(dòng)態(tài)獲取圖片地址方式

    vue中使用require動(dòng)態(tài)獲取圖片地址方式

    這篇文章主要介紹了vue中使用require動(dòng)態(tài)獲取圖片地址方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • 手把手教你Vue3實(shí)現(xiàn)路由跳轉(zhuǎn)

    手把手教你Vue3實(shí)現(xiàn)路由跳轉(zhuǎn)

    Vue Router是Vue.js的官方路由器,它與Vue.js核心深度集成,使使用Vue.js構(gòu)建單頁(yè)應(yīng)用程序變得輕而易舉,下面這篇文章主要給大家介紹了關(guān)于Vue3實(shí)現(xiàn)路由跳轉(zhuǎn)的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • Vue組件中常見(jiàn)的props默認(rèn)值陷阱問(wèn)題

    Vue組件中常見(jiàn)的props默認(rèn)值陷阱問(wèn)題

    這篇文章主要介紹了避免Vue組件中常見(jiàn)的props默認(rèn)值陷阱,本文通過(guò)問(wèn)題展示及解決方案給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • vue中的eventBus會(huì)不會(huì)產(chǎn)生內(nèi)存泄漏你知道嗎

    vue中的eventBus會(huì)不會(huì)產(chǎn)生內(nèi)存泄漏你知道嗎

    這篇文章主要為大家詳細(xì)介紹了vue中的eventBus會(huì)不會(huì)產(chǎn)生內(nèi)存泄漏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2022-02-02
  • vue3中defineEmits與defineProps的用法實(shí)例

    vue3中defineEmits與defineProps的用法實(shí)例

    這篇文章主要介紹了vue3中defineEmits/defineProps的用法實(shí)例,需要的朋友可以參考下
    2023-12-12
  • vue3封裝el-pagination分頁(yè)組件的完整代碼

    vue3封裝el-pagination分頁(yè)組件的完整代碼

    這篇文章主要介紹了vue3封裝el-pagination分頁(yè)組件的完整代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流

    Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流

    本文主要介紹了Vue+LogicFlow+Flowable實(shí)現(xiàn)工作流,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-12-12
  • vue.js將unix時(shí)間戳轉(zhuǎn)換為自定義時(shí)間格式

    vue.js將unix時(shí)間戳轉(zhuǎn)換為自定義時(shí)間格式

    這篇文章主要介紹了vue.js將unix時(shí)間戳轉(zhuǎn)換為自定義時(shí)間格式的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • 使用idea創(chuàng)建vue項(xiàng)目的圖文教程

    使用idea創(chuàng)建vue項(xiàng)目的圖文教程

    Vue.js是一套構(gòu)建用戶界面的框架,只關(guān)注視圖層,它不僅易于上手,還便于與第三方庫(kù)或既有項(xiàng)目整合,下面這篇文章主要給大家介紹了關(guān)于使用idea創(chuàng)建vue項(xiàng)目的相關(guān)資料,需要的朋友可以參考下
    2022-08-08

最新評(píng)論