vue+springboot登錄與注冊功能的實現
注:對于JWT的學習,首先要完成注冊和登錄的功能,本篇博客是基于上述博客的進階學習,代碼頁也是在原有的基礎上進行擴展
①在pom.xml添加依賴
<!-- JWT -->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version>
</dependency>
②在common文件夾下定義一個JwtInterceptor攔截器java文件
Jwtlnterceptor:?
package com.example.springboot.common;import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.example.springboot.entity.User;
import com.example.springboot.exception.ServiceException;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import com.example.springboot.mapper.UserMapper;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JwtInterceptor implements HandlerInterceptor {@Resourceprivate UserMapper userMapper;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("token");if (StrUtil.isBlank(token)) {token = request.getParameter("token");}// 如果不是映射到方法直接通過
// if (handler instanceof HandlerMethod) {
// AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);
// if (annotation != null) {
// return true;
// }
// }// 執行認證if (StrUtil.isBlank(token)) {throw new ServiceException("401", "請登錄");}// 獲取 token 中的 user idString userId;try {userId = JWT.decode(token).getAudience().get(0);} catch (JWTDecodeException j) {throw new ServiceException("401", "請登錄");}// 根據token中的userid查詢數據庫User user = userMapper.selectbyid(Integer.valueOf(userId));if (user == null) {throw new ServiceException("401", "請登錄");}// 用戶密碼加簽驗證 tokenJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();try {jwtVerifier.verify(token); // 驗證token} catch (JWTVerificationException e) {throw new ServiceException("401", "請登錄");}return true;}
}
③修改自定義異常
GlobalExeception:
package com.example.springboot.exception;import com.example.springboot.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;@ControllerAdvice
public class GlobalExeception {@ExceptionHandler(ServiceException.class)@ResponseBodypublic Result serviceException(ServiceException e){return Result.error(e.getCode(),e.getMessage());}
}
ServiceException:
package com.example.springboot.exception;import lombok.Getter;@Getter
public class ServiceException extends RuntimeException{private final String code;public ServiceException(String msg){super(msg);this.code="500";}public ServiceException(String code,String msg){super(msg);this.code=code;}
}
?④配置攔截器 InterceptorConfig
InterceptorConfig:?
package com.example.springboot.common;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/login");super.addInterceptors(registry);}@Beanpublic JwtInterceptor jwtInterceptor() {return new JwtInterceptor();}}
?⑤新建一個工具類TokenUtils
?TokenUtils:
package com.example.springboot.utils;import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.springboot.entity.User;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.example.springboot.mapper.UserMapper;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;@Component
public class TokenUtils {private static UserMapper staticUserMapper;@ResourceUserMapper userMapper;@PostConstructpublic void setUserService() {staticUserMapper = userMapper;}/*** 生成token** @return*/public static String createToken(String userId, String sign) {return JWT.create().withAudience(userId) // 將 user id 保存到 token 里面,作為載荷.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小時后token過期.sign(Algorithm.HMAC256(sign)); // 以 password 作為 token 的密鑰}/*** 獲取當前登錄的用戶信息** @return user對象*/public static User getCurrentUser() {try {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader("token");if (StrUtil.isNotBlank(token)) {String userId = JWT.decode(token).getAudience().get(0);return staticUserMapper.selectbyid(Integer.valueOf(userId));}} catch (Exception e) {return null;}return null;}
}
修改UserService和User:
UserService:
package com.example.springboot.service;import com.example.springboot.entity.User;
import com.example.springboot.exception.ServiceException;
import com.example.springboot.mapper.UserMapper;
import com.example.springboot.utils.TokenUtils;
import jdk.nashorn.internal.parser.Token;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;import java.util.List;@Service
public class UserService {@AutowiredUserMapper userMapper;public void insertUser(User user){userMapper.insert(user);}public void updateUser(User user) {userMapper.updateUser(user);}public void deleteUser(Integer id) {userMapper.deleteUser(id);}public void batchdeleteUser(List<Integer> ids) {for(Integer id : ids){userMapper.deleteUser(id);}}public List<User> selectall() {return userMapper.selectall();}public User selectbyid(Integer id) {return userMapper.selectbyid(id);}public List<User> selectbyname(String name) {return userMapper.selectbyname(name);}public List<User> selectbymore(String username, String name) {return userMapper.selectbymore(username,name);}public List<User> selectbymo(String username, String name) {return userMapper.selectbymo(username,name);}public User login(User user) {User dbuser=userMapper.selectbyUsername(user.getUsername());if(dbuser == null){throw new ServiceException("賬號不存在");}if(!user.getPassword().equals(dbuser.getPassword())){throw new ServiceException("賬號或者密碼錯誤");}String token=TokenUtils.createToken(dbuser.getId().toString(),dbuser.getPassword());dbuser.setToken(token);return dbuser;}public User register(User user) {User dbuser=userMapper.selectbyUsername(user.getUsername());if(dbuser != null){throw new ServiceException("用戶名已存在");}userMapper.insert(user);return user;}
}
User:
package com.example.springboot.entity;import lombok.AllArgsConstructor;
import lombok.Data;@Data
public class User {private Integer id;private String username;private String password;private String name;private String phone;private String email;private String address;private String avatar;private String token;
}
登錄試下,在預覽發現token:
請求的數據會存在應用程序的本地存儲中:?
⑥在vue中修改request.js
import axios from 'axios'
import router from "@/router";// 創建可一個新的axios對象
const request = axios.create({baseURL: 'http://localhost:9090', // 后端的接口地址 ip:porttimeout: 30000
})// request 攔截器
// 可以自請求發送前對請求做一些處理
// 比如統一加token,對請求參數統一加密
request.interceptors.request.use(config => {config.headers['Content-Type'] = 'application/json;charset=utf-8';let user=JSON.parse(localStorage.getItem("honey-user")||'{}')// let user = localStorage.getItem("user") ? JSON.parse(localStorage.getItem("user")) : nullconfig.headers['token'] = user.token // 設置請求頭return config
}, error => {console.error('request error: ' + error) // for debugreturn Promise.reject(error)
});// response 攔截器
// 可以在接口響應后統一處理結果
request.interceptors.response.use(response => {let res = response.data;// 兼容服務端返回的字符串數據if (typeof res === 'string') {res = res ? JSON.parse(res) : res}if(res.code === '401'){router.push('/login')}return res;},error => {console.error('response error: ' + error) // for debugreturn Promise.reject(error)}
)export default request
管理系統代碼要做如下修改:退出登錄的時候要清除數據
<template><div><el-container><!-- 側邊欄 --><el-aside :width="asideWidth" style="min-height: 100vh; background-color: #001529"><div style="height: 60px; color: white; display: flex; align-items: center; justify-content: center"><img src="@/assets/logo1.png" alt="" style="width: 40px; height: 40px"><span class="logo-title" v-show="!isCollapse">honey2024</span></div><el-menu :collapse="isCollapse" :collapse-transition="false" router background-color="#001529" text-color="rgba(255, 255, 255, 0.65)" active-text-color="#fff" style="border: none" :default-active="$route.path"><el-menu-item index="/"><i class="el-icon-menu"></i><span slot="title">系統首頁</span></el-menu-item><el-menu-item index="/1"><i class="el-icon-house"></i><span slot="title">系統首頁</span></el-menu-item><el-menu-item index="/2"><i class="el-icon-house"></i><span slot="title">系統首頁</span></el-menu-item><el-submenu index="3"><template slot="title"><i class="el-icon-menu"></i><span>信息管理</span></template><el-menu-item>用戶信息</el-menu-item><el-menu-item>管理員信息</el-menu-item><el-menu-item index="/">系統首頁</el-menu-item></el-submenu></el-menu></el-aside><el-container><!-- 頭部區域--><el-header><i :class="collapseIcon" style="font-size: 26px" @click="handleCollapse"></i><el-breadcrumb separator-class="el-icon-arrow-right" style="margin-left: 20px"><el-breadcrumb-item :to="{ path: '/' }">首頁</el-breadcrumb-item><el-breadcrumb-item :to="{ path: '/user' }">用戶管理</el-breadcrumb-item></el-breadcrumb><div style="flex: 1; width: 0; display: flex; align-items: center; justify-content: flex-end"><i class="el-icon-quanping" style="font-size: 26px" @click="handleFull"></i><el-dropdown placement="bottom"><div style="display: flex; align-items: center; cursor: default"><img src="@/assets/logo1.png" alt="" style="width: 40px; height: 40px; margin: 0 5px"><span>管理員</span></div><el-dropdown-menu slot="dropdown"><el-dropdown-item>個人信息</el-dropdown-item><el-dropdown-item>修改密碼</el-dropdown-item><el-dropdown-item @click.native="logout">退出登錄</el-dropdown-item></el-dropdown-menu></el-dropdown></div></el-header><!-- 主體區域--><el-main><div style="box-shadow: 0 0 10px rgba(0,0,0,.1); padding: 10px 20px; border-radius: 5px; margin-bottom: 10px">早安,騷年,祝你開心每一天!</div><div style="display: flex;"><el-card style="width: 50%;margin-right: 10px;"><div slot="header" class="clearfix"><span>青哥哥帶你做畢設2024</span></div><div>2024畢設正式開始了!青哥哥帶你手把手敲出來!<div style="margin-top: 20px"><div style="margin: 10px 0"><strong>主題色</strong></div><el-button type="primary">按鈕</el-button><el-button type="success">按鈕</el-button><el-button type="warning">按鈕</el-button><el-button type="danger">按鈕</el-button><el-button type="info">按鈕</el-button></div></div></el-card><el-card style="width: 50%;"><div slot="header" class="clearfix"><span>渲染用戶的數據</span></div><div><el-table :data="users"><el-table-column label="ID" prop="id"/><el-table-column label="用戶名" prop="username"/><el-table-column label="姓名" prop="name"/><el-table-column label="地址" prop="address"/></el-table></div></el-card></div></el-main></el-container></el-container></div>
</template><script>
import axios from "axios";
import request from '@/utils/request'export default {name: 'HomeView',data() {return {isCollapse: false, // 不收縮asideWidth: '200px',collapseIcon: 'el-icon-s-fold',users: []}},mounted() {// axios.get('http://localhost:9090/user/selectall').then(res=>{// console.log(res.data);// this.users=res.data.data// })request.get('/user/selectall').then(res => {this.users = res.data})},methods: {logout() {localStorage.removeItem("honey-user")this.$router.push('/login')},handleFull() {document.documentElement.requestFullscreen()},handleCollapse() {this.isCollapse = !this.isCollapsethis.asideWidth = this.isCollapse ? '64px' : '200px'this.collapseIcon = this.isCollapse ? 'el-icon-s-unfold' : 'el-icon-s-fold'}}
}
</script><style>
.el-menu--inline {background-color: #000c17 !important;
}.el-menu--inline .el-menu-item {background-color: #000c17 !important;padding-left: 49px !important;
}.el-menu-item:hover, .el-submenu__title:hover {color: #fff !important;
}.el-submenu__title:hover i {color: #fff !important;
}.el-menu-item:hover i {color: #fff !important;
}.el-menu-item.is-active {background-color: #1890ff !important;border-radius: 5px !important;width: calc(100% - 8px);margin-left: 4px;
}.el-menu-item.is-active i, .el-menu-item.is-active .el-tooltip {margin-left: -4px;
}.el-menu-item {height: 40px !important;line-height: 40px !important;
}.el-submenu__title {height: 40px !important;line-height: 40px !important;
}.el-submenu .el-menu-item {min-width: 0 !important;
}.el-menu--inline .el-menu-item.is-active {padding-left: 45px !important;
}/*.el-submenu__icon-arrow {*/
/* margin-top: -5px;*/
/*}*/.el-aside {transition: width .3s;box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
}.logo-title {margin-left: 5px;font-size: 20px;transition: all .3s; /* 0.3s */
}.el-header {box-shadow: 2px 0 6px rgba(0, 21, 41, .35);display: flex;align-items: center;
}
</style>
此時還有個小問題:在注冊頁注冊的時候,會顯示登錄失敗
?⑦新建自定義注解(作用:在攔截器需要放行的地方放行)
AuthAuccess:
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthAccess {
}
Jwtlnterceptor:
package com.example.springboot.common;
import org.springframework.web.method.HandlerMethod;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.example.springboot.entity.User;
import com.example.springboot.exception.ServiceException;
import org.springframework.web.servlet.HandlerInterceptor;
import com.example.springboot.mapper.UserMapper;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class JwtInterceptor implements HandlerInterceptor {@Resourceprivate UserMapper userMapper;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String token = request.getHeader("token");if (StrUtil.isBlank(token)) {token = request.getParameter("token");}// 如果不是映射到方法直接通過if (handler instanceof HandlerMethod) {AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class);if (annotation != null) {return true;}}// 執行認證if (StrUtil.isBlank(token)) {throw new ServiceException("401", "請登錄");}// 獲取 token 中的 user idString userId;try {userId = JWT.decode(token).getAudience().get(0);} catch (JWTDecodeException j) {throw new ServiceException("401", "請登錄");}// 根據token中的userid查詢數據庫User user = userMapper.selectbyid(Integer.valueOf(userId));if (user == null) {throw new ServiceException("401", "請登錄");}// 用戶密碼加簽驗證 tokenJWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();try {jwtVerifier.verify(token); // 驗證token} catch (JWTVerificationException e) {throw new ServiceException("401", "請登錄");}return true;}
}
?WebController:引用自定義注解地方就表示放行
package com.example.springboot.controller;import cn.hutool.core.util.StrUtil;
import com.example.springboot.common.AuthAccess;
import com.example.springboot.common.Result;
import com.example.springboot.entity.User;
import com.example.springboot.exception.ServiceException;
import com.example.springboot.service.UserService;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;@RestController
public class WebController {@ResourceUserService userService;@AuthAccess@GetMapping("/")public Result hello(){return Result.success("success");}@PostMapping("/login")public Result login(@RequestBody User user){if(StrUtil.isBlank(user.getUsername())||StrUtil.isBlank(user.getPassword())){return Result.error("數據輸入錯誤");}user=userService.login(user);return Result.success(user);}@AuthAccess@PostMapping("/register")public Result register(@RequestBody User user){if(StrUtil.isBlank(user.getUsername())||StrUtil.isBlank(user.getPassword())){throw new ServiceException("輸入不合法");}if(user.getUsername().length()>10||user.getPassword().length()>20){throw new ServiceException("長度過長");}user=userService.register(user);return Result.success(user);}
}
如果不使用自定義注解的方法,也可以直接修改InterceptorConfig:
package com.example.springboot.common;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {@Overrideprotected void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor()).addPathPatterns("/**").excludePathPatterns("/login","/register");super.addInterceptors(registry);}@Beanpublic JwtInterceptor jwtInterceptor() {return new JwtInterceptor();}}