当前位置: 首页 > backend >正文

2.认证与授权升级方案及使用

目录

1.方案提出背景

传统Spring Security的缺点

Spring Security+JWT的优点:

适用场景

2.在Srping Security基础上扩展JWT(后端)

(1).引入依赖(基于Maven)

(2).编写 JWT 工具类

(3).JWT中的secret

[1].secret的核心作用

[2].secret的生成规则

[3].生产环境的安全配置方案

(4).配置 Spring Security 过滤器

(5).登录成功时成功JWT

[1].修改JwtUtil

[2].修改JwtAuthenticationFilter

[3].修改 MyLoginSuccessHandler.java

[4].修改SecurityConfig

3.在Srping Security基础上扩展JWT(前端)

(1).安装必要依赖

(2).JWT 工具函数 (api/auth.js)

(3).修改请求拦截器 

(4).用户状态管理 (stores/user.js)加到auth.js中

(5).整合 JWT 认证的路由守卫

(6).修改登录页面


1.方案提出背景

传统Spring Security的缺点

依赖会话管理:基于Session的认证机制,需要服务器存储会话信息,在分布式系统中可能面临会话同步问题。

扩展性受限:在微服务架构中,传统的Cookie-Session模式难以跨服务共享认证状态,需要额外解决方案如Session复制或集中存储。

CSRF防护负担:需要为每个状态变更请求配置CSRF令牌,增加开发复杂度,尤其在前后端分离架构中显得冗余。

移动端适配差:移动应用通常难以有效处理和管理Cookie,使得基于Session的认证机制适配性较差。

Spring Security+JWT的优点:

整合性与安全性

Spring Security与JWT结合提供了更灵活的认证和授权机制。传统Spring Security基于会话(Session)管理,依赖服务器存储用户状态,而JWT是无状态的,将用户信息加密存储在客户端令牌中,减轻服务器压力。

JWT的标准化结构(Header、Payload、Signature)确保数据完整性,签名机制防止篡改。Spring Security的过滤器链可无缝集成JWT验证逻辑,实现跨服务认证。

跨域与扩展性

JWT适合分布式系统和微服务架构,令牌可跨域传递,无需依赖集中式会话存储。传统Spring Security的会话管理在扩展时需考虑会话复制或共享存储(如Redis),增加复杂度。

JWT的Payload可自定义包含用户角色和权限,减少频繁查询数据库。Spring Security的GrantedAuthority可直接解析JWT中的权限声明,简化授权流程。

性能与无状态

JWT减少了服务器端的会话存储开销,每次请求只需验证令牌签名。Spring Security的会话管理需要维护会话生命周期,可能引发性能瓶颈。

无状态特性更适合RESTful API,避免CSRF攻击。Spring Security需额外配置CSRF防护,而JWT通过签名验证天然规避此类问题。

适用场景

  • Spring Security会话管理:适合单体应用或需严格会话控制的场景。
  • Spring Security+JWT:适合微服务、前后端分离或需高扩展性的系统。

2.在Srping Security基础上扩展JWT(后端)

(1).引入依赖(基于Maven)

在 pom.xml 中添加 JWT 相关依赖,常用 jjwt(Java JSON Web Token):

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.2</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope>
</dependency>

也可根据需求选择其他 JWT 工具库,如 nimbus-jose-jwt 等。

(2).编写 JWT 工具类

用于生成、解析 JWT 令牌,示例:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Component
public class JwtUtil {// 密钥,实际应配置在 application.yml 等配置文件中,这里为演示写死private static final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); // token 过期时间,单位毫秒,这里设为 1 小时,可根据需求调整private static final long EXPIRATION_TIME = 3600 * 1000; // 生成 JWT 令牌public String generateToken(String username) {Date now = new Date();Date expiration = new Date(now.getTime() + EXPIRATION_TIME);Map<String, Object> claims = new HashMap<>();claims.put("sub", username); // subject,一般存用户名等标识claims.put("iat", now); // issued atclaims.put("exp", expiration); // expirationreturn Jwts.builder().setClaims(claims).signWith(key, SignatureAlgorithm.HS256).compact();}// 解析 JWT 令牌,获取 claimspublic Claims parseToken(String token) {return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();}// 从令牌中获取用户名public String getUsernameFromToken(String token) {Claims claims = parseToken(token);return claims.getSubject();}// 验证令牌是否过期public boolean isTokenExpired(String token) {Claims claims = parseToken(token);Date expiration = claims.getExpiration();return expiration.before(new Date());}
}

实际项目中,密钥、过期时间等应配置在 application.yml 里,通过 @Value 注入,比如:

jwt:secret: your_secret_key_here # 实际要足够复杂、安全,可通过加密方式配置expiration: 3600000 # 过期时间,毫秒

(3).JWT中的secret

在 JWT 配置中,secret是非常关键的安全参数,它用于对 JWT 进行签名和验证,必须保证足够安全。以下是关于secret的详细说明及配置建议:

[1].secret的核心作用

  • 签名与验证:JWT 的第三部分(签名)通过secret对前两部分(头部、载荷)进行加密签名,确保令牌未被篡改。
  • 安全基石:如果secret泄露,攻击者可伪造合法 JWT,直接绕过认证机制。

[2].secret的生成规则

 长度与复杂度要求

  • 推荐长度:至少 256 位(32 字节),例如使用 64 位随机字符串(如base64编码的随机字节序列)。
  • 字符要求:包含大小写字母、数字、特殊字符(如!@#$%^&*()_+-=[]{}|;':",.<>/?),避免纯字母或数字。

    安全生成方式

      工具生成

    •   使用 Java 的SecureRandom生成随机字节序列:
    import java.security.SecureRandom;
    import java.util.Base64;SecureRandom random = new SecureRandom();
    byte[] bytes = new byte[32]; // 32字节=256位
    random.nextBytes(bytes);
    String secret = Base64.getEncoder().encodeToString(bytes);
    System.out.println(secret);
    • 使用命令行工具(如 Linux/macOS):
    # 生成32字节随机字符串(base64编码)
    openssl rand -base64 32
    • 禁止行为

      • 避免使用硬编码的简单字符串(如"123456""jwtsecret")。
      • 避免使用与项目相关的可猜测信息(如项目名、公司名、日期)。

    [3].生产环境的安全配置方案

    1. 避免明文存储

    • 环境变量:将secret存入服务器环境变量(如JWT_SECRET_KEY),代码中通过System.getenv("JWT_SECRET_KEY")获取。
    • 配置中心:使用配置中心(如 Spring Cloud Config、Apollo)加密存储,避免直接写入配置文件。

    2.Spring Boot 项目示例(以 application.yml 为例)

    # 原始明文配置(仅用于开发环境,禁止生产使用)
    jwt:secret: ${JWT_SECRET_KEY:your_secure_random_key_here}  # 优先读取环境变量,默认值需为强密码expiration: 3600000# 生产环境推荐方案:通过环境变量注入
    # 启动命令示例:java -DJWT_SECRET_KEY=your_actual_secret -jar app.jar

    3.代码中读取 secret 的示例(Spring Security + JWT)

    @Configuration
    public class JwtConfig {private final String secret;public JwtConfig(@Value("${jwt.secret}") String secret) {this.secret = secret;}// JWT令牌生成器public String generateToken(Authentication authentication) {// 使用secret进行签名return Jwts.builder().setSubject(authentication.getName()).setExpiration(new Date(System.currentTimeMillis() + expiration)).signWith(SignatureAlgorithm.HS256, secret).compact();}
    }

    (4).修改工具类,通过 @Value 注入:

    import org.springframework.beans.factory.annotation.Value;
    // 其他依赖...@Component
    public class JwtUtil {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private long expiration;private Key key;@PostConstructpublic void init() {key = Keys.hmacShaKeyFor(secret.getBytes());}// 生成 JWT 令牌public String generateToken(String username) {//... 逻辑类似,只是用注入的 expiration 等Date now = new Date();Date expirationDate = new Date(now.getTime() + expiration);//...}// 其他方法...
    }

    改好之后的:

    package com.jac.screen.util;import io.jsonwebtoken.*;
    import io.jsonwebtoken.security.Keys;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
    import java.util.Date;@Component
    public class JwtUtil {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private long expiration;private SecretKey key;// 新增:初始化密钥(如果没其他初始化逻辑,也可直接在构造器调用)public void initKey() {this.key = Keys.hmacShaKeyFor(secret.getBytes());}// 生成 JWT 令牌public String generateToken(String username) {Date now = new Date();Date expirationDate = new Date(now.getTime() + expiration);return Jwts.builder().setSubject(username).setIssuedAt(now).setExpiration(expirationDate).signWith(key, SignatureAlgorithm.HS256).compact();}// 新增:解析为 Jws<Claims>(供过滤器使用)public Jws<Claims> parseJws(String token) {return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);}// 原有方法保持不变(如果有的话)public Claims parseToken(String token) {return parseJws(token).getBody();}public boolean isTokenExpired(String token) {Date expiration = parseJws(token).getBody().getExpiration();return expiration.before(new Date());}public String getUsernameFromToken(String token) {return parseJws(token).getBody().getSubject();}// 获取密钥(如果需要的话)public SecretKey getKey() {return key;}
    }

    (4).配置 Spring Security 过滤器

    [1].创建 JWT 认证过滤器,用于解析请求中的 JWT 令牌,进行身份验证:

    @Component
    public class JwtAuthenticationFilter extends OncePerRequestFilter {private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);private static final List<String> WHITE_LIST = Arrays.asList("/public/api","/auth/login");private final JwtUtil jwtUtil;private final UserDetailsService userDetailsService;public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService;}@Overrideprotected boolean shouldNotFilter(HttpServletRequest request) {String path = request.getRequestURI();return WHITE_LIST.stream().anyMatch(path::startsWith);}private String extractToken(HttpServletRequest request) {String header = request.getHeader("Authorization");if (header != null && header.startsWith("Bearer ")) {return header.substring(7);}return null;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {// 跳过白名单路径if (shouldNotFilter(request)) {filterChain.doFilter(request, response);return;}String token = extractToken(request);if (token == null) {filterChain.doFilter(request, response);return;}try {// 关键修改:调用 parseJws 而非 parseTokenJws<Claims> jws = jwtUtil.parseJws(token);Claims claims = jws.getBody();if (jwtUtil.isTokenExpired(token)) {// 传入 JwsHeader 和 Claimsthrow new ExpiredJwtException(jws.getHeader(), claims, "Token已过期");}String username = claims.getSubject();UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (userDetails == null) {throw new UsernameNotFoundException("用户不存在: " + username);}UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);} catch (ExpiredJwtException e) {logger.warn("JWT过期: {}", e.getMessage());SecurityContextHolder.clearContext();response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "令牌已过期");return;} catch (JwtException | UsernameNotFoundException e) {logger.error("JWT验证失败: {}", e.getMessage());SecurityContextHolder.clearContext();response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效令牌");return;}filterChain.doFilter(request, response);}
    }

    [2].在 Spring Security 配置类(继承 WebSecurityConfigurerAdapter 或使用新的配置方式 )中,配置该过滤器

        @Beanprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception{// 自定义表单登录http.formLogin(form->{form.usernameParameter("username").passwordParameter("password").loginProcessingUrl("/admin/login").successHandler(new MyLoginSuccessHandler()).failureHandler(new MyLoginFailureHandler());});// 权限拦截配置http.authorizeHttpRequests(resp->{///login","/admin/loginresp.requestMatchers("/*").permitAll(); // 登录请求不需要认证resp.anyRequest().authenticated(); // 其余请求都需要认证});// 退出登录配置http.logout(logout->{logout.logoutUrl("/admin/logout") // 注销的路径.logoutSuccessHandler(new MyLogoutSuccessHandler()) // 登出成功处理器.clearAuthentication(true) // 清除认证数据.invalidateHttpSession(true); // 清除session});// 异常处理http.exceptionHandling(exception->{exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未登录处理器.accessDeniedHandler(new MyAccessDeniedHandler()); // 权限不足处理器});// 跨域访问http.cors(cors -> cors.configurationSource(request -> {CorsConfiguration config = new CorsConfiguration();config.setAllowedOrigins(List.of("http://localhost:6021")); // 允许的域名config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的 HTTP 方法config.setAllowedHeaders(List.of("*")); // 允许的请求头config.setAllowCredentials(true); // 允许携带凭证(如 cookies)config.setMaxAge(3600L); // 预检请求的缓存时间(秒)return config;}));// 添加 JWT 认证过滤器,在 UsernamePasswordAuthenticationFilter 之前执行http.addFilterBefore(new JwtAuthenticationFilter(new JwtUtil(), new MyUserDatailService()), UsernamePasswordAuthenticationFilter.class);// 关闭csrf防护http.csrf(csrf-> csrf.disable());return http.build();}
    

    (5).登录成功时成功JWT

    [1].修改JwtUtil

    @Component
    public class JwtUtil {@Value("${jwt.secret}")private String secret;@Value("${jwt.expiration}")private long expiration;private SecretKey key;// 新增:初始化密钥(如果没其他初始化逻辑,也可直接在构造器调用)// 添加 @PostConstruct 注解确保密钥自动初始化@PostConstructpublic void initKey() {this.key = Keys.hmacShaKeyFor(secret.getBytes());}// 生成 JWT 令牌public String generateToken(String username) {Date now = new Date();Date expirationDate = new Date(now.getTime() + expiration);return Jwts.builder().setSubject(username).setIssuedAt(now).setExpiration(expirationDate).signWith(key, SignatureAlgorithm.HS256).compact();}// 新增:解析为 Jws<Claims>(供过滤器使用)public Jws<Claims> parseJws(String token) {return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);}// 原有方法保持不变(如果有的话)public Claims parseToken(String token) {return parseJws(token).getBody();}public boolean isTokenExpired(String token) {Date expiration = parseJws(token).getBody().getExpiration();return expiration.before(new Date());}public String getUsernameFromToken(String token) {return parseJws(token).getBody().getSubject();}// 获取密钥(如果需要的话)public SecretKey getKey() {return key;}// 新增获取过期时间的方法,供外部获取jwt的过期时间配置值public long getExpiration() {return expiration;}
    }

    [2].修改JwtAuthenticationFilter

    @Component
    public class JwtAuthenticationFilter extends OncePerRequestFilter {private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);private static final List<String> WHITE_LIST = Arrays.asList("/public/api","/auth/login");private final JwtUtil jwtUtil;private final UserDetailsService userDetailsService;public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService;}@Overrideprotected boolean shouldNotFilter(HttpServletRequest request) {String path = request.getRequestURI();return WHITE_LIST.stream().anyMatch(path::startsWith);}private String extractToken(HttpServletRequest request) {String header = request.getHeader("Authorization");if (header != null && header.startsWith("Bearer ")) {return header.substring(7);}return null;}@Overrideprotected void doFilterInternal(HttpServletRequest request,HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {// 跳过白名单路径if (shouldNotFilter(request)) {filterChain.doFilter(request, response);return;}String token = extractToken(request);if (token == null) {filterChain.doFilter(request, response);return;}try {// 关键修改:调用 parseJws 而非 parseTokenJws<Claims> jws = jwtUtil.parseJws(token);Claims claims = jws.getBody();if (jwtUtil.isTokenExpired(token)) {// 传入 JwsHeader 和 Claimsthrow new ExpiredJwtException(jws.getHeader(), claims, "Token已过期");}String username = claims.getSubject();UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (userDetails == null) {throw new UsernameNotFoundException("用户不存在: " + username);}UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication);} catch (ExpiredJwtException e) {logger.warn("JWT过期: {}", e.getMessage());SecurityContextHolder.clearContext();response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "令牌已过期");return;} catch (JwtException | UsernameNotFoundException e) {logger.error("JWT验证失败: {}", e.getMessage());SecurityContextHolder.clearContext();response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "无效令牌");return;}filterChain.doFilter(request, response);}
    }

    [3].修改 MyLoginSuccessHandler.java

    public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {private final JwtUtil jwtUtil;private final Gson gson = new Gson();// 注入 JwtUtilpublic MyLoginSuccessHandler(JwtUtil jwtUtil) {this.jwtUtil = jwtUtil;// 初始化 JWT 密钥(如果没在其他地方初始化)this.jwtUtil.initKey();}@Overridepublic void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication) throws IOException, ServletException {// 从 Authentication 中获取用户信息User user = (User) authentication.getPrincipal();String username = user.getUsername();// 生成 JWT 令牌String token = jwtUtil.generateToken(username);// 构造响应数据(包含 JWT)Map<String, Object> data = new HashMap<>();data.put("token", token);data.put("username", username);data.put("roles", user.getAuthorities());data.put("expiration", new Date(System.currentTimeMillis() + jwtUtil.getExpiration()));Result result = new Result();result.setCode(200);result.setMessage("登录成功");result.setData(data); // 将 JWT 放入响应数据response.setContentType("application/json;charset=utf-8");response.getWriter().write(gson.toJson(result));}
    }

    [4].修改SecurityConfig

    
    /*** Security配置类*/
    @Configuration
    @EnableMethodSecurity
    public class SecurityConfig {// SpringSecurity配置private final JwtUtil jwtUtil; // 注入 JwtUtilprivate final MyUserDetailsService userDetailsService; // 注意类名正确:MyUserDetailsService// 构造器注入public SecurityConfig(JwtUtil jwtUtil, MyUserDetailsService userDetailsService1) {this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService1;}@Beanprotected SecurityFilterChain filterChain(HttpSecurity http) throws Exception{// 自定义表单登录http.formLogin(form->{form.usernameParameter("username").passwordParameter("password").loginProcessingUrl("/admin/login").successHandler(new MyLoginSuccessHandler(jwtUtil)).failureHandler(new MyLoginFailureHandler());});// 权限拦截配置http.authorizeHttpRequests(resp->{///login","/admin/loginresp.requestMatchers("/*").permitAll(); // 登录请求不需要认证resp.anyRequest().authenticated(); // 其余请求都需要认证});// 退出登录配置http.logout(logout->{logout.logoutUrl("/admin/logout") // 注销的路径.logoutSuccessHandler(new MyLogoutSuccessHandler()) // 登出成功处理器.clearAuthentication(true) // 清除认证数据.invalidateHttpSession(true); // 清除session});// 异常处理http.exceptionHandling(exception->{exception.authenticationEntryPoint(new MyAuthenticationEntryPoint()) // 未登录处理器.accessDeniedHandler(new MyAccessDeniedHandler()); // 权限不足处理器});// 跨域访问http.cors(cors -> cors.configurationSource(request -> {CorsConfiguration config = new CorsConfiguration();config.setAllowedOrigins(List.of("http://localhost:6021")); // 允许的域名config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的 HTTP 方法config.setAllowedHeaders(List.of("*")); // 允许的请求头config.setAllowCredentials(true); // 允许携带凭证(如 cookies)config.setMaxAge(3600L); // 预检请求的缓存时间(秒)return config;}));// 正确:使用构造器注入的 Beanhttp.addFilterBefore(new JwtAuthenticationFilter(jwtUtil, userDetailsService), UsernamePasswordAuthenticationFilter.class);// 关闭csrf防护http.csrf(csrf-> csrf.disable());return http.build();}// 加密工具@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
    }
    

    3.在Srping Security基础上扩展JWT(前端)

    以下是前端集成 JWT 认证的完整步骤,以 Vue 3 + Axios 为例(其他框架如 React、Angular 逻辑类似):

    (1).安装必要依赖

    npm install axios vue-router pinia # 或 yarn add axios vue-router pinia

    (2).JWT 工具函数 (api/auth.js)

    // utils/auth.js
    export function getToken() {return localStorage.getItem('token');
    }export function setToken(token) {localStorage.setItem('token', token);
    }export function removeToken() {localStorage.removeItem('token');
    }export function getTokenExpirationDate(token) {if (!token) return null;try {const decoded = JSON.parse(atob(token.split('.')[1]));if (decoded.exp) {return new Date(decoded.exp * 1000);}return null;} catch (error) {return null;}
    }export function isTokenExpired(token) {const expiration = getTokenExpirationDate(token);return expiration < new Date();
    }

    (3).修改请求拦截器 

    import axios from 'axios'
    import { ElMessage } from 'element-plus'
    import router from '@/router'; // 引入路由实例// 创建 axios 实例
    const service = axios.create({baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 从环境变量获取 API 基础路径timeout: 15000, // 请求超时时间headers: {'Content-Type': 'application/json'}
    })// 请求拦截器
    service.interceptors.request.use(config => {// 在这里可以添加 token 等认证信息const token = localStorage.getItem('token');if (token) {config.headers['Authorization'] = `Bearer ${token}`;}return config},error => {console.error('请求错误:', error)return Promise.reject(error)}
    )// 增强型响应拦截器 - 关键修改点
    service.interceptors.response.use(response => {// 1. 基础响应结构校验if (!response || !response.data) {ElMessage.error('响应数据格式异常');return Promise.reject(new Error('响应数据格式异常'));}const res = response.data;// 2. 业务状态码校验if (res.code === 200 || res.code === 0) {// 确保res.data存在才返回,避免null/undefinedreturn res.data || res;} else {// 处理业务错误ElMessage({message: res.message || '请求失败',type: 'error',duration: 3000});// 根据业务状态码进行不同处理switch (res.code) {case 401: // 未认证(如 token 过期)localStorage.removeItem('token');router.push({path: '/login',query: { redirect: router.currentRoute.value.fullPath }});break;case 403: // 权限不足router.push('/403');break;case 500: // 服务器内部错误ElMessage.error('服务器内部错误,请稍后重试');break;default:break;}return Promise.reject(new Error(res.message || '请求失败'));}},error => {console.error('响应错误:', error);// 处理网络错误if (!error.response) {ElMessage.error('网络连接异常,请检查网络设置');return Promise.reject(new Error('网络连接异常'));}const { status, data } = error.response;ElMessage({message: data.message || `请求失败 (${status})`,type: 'error',duration: 3000});return Promise.reject(error);}
    )export default service

    (4).用户状态管理 (stores/user.js)加到auth.js中

    import request from '@/utils/request';
    import { getToken, setToken, removeToken, isTokenExpired } from '../utils/auth';// 用户状态(替代 Pinia 存储)
    const userState = {token: getToken(),name: '',avatar: '',roles: []
    };/*** 登录API* @param {Object} data - 登录参数 { username, password }* @returns {Promise<boolean>} - 登录是否成功*/
    export function login(data) {return new Promise((resolve, reject) => {request({url: '/admin/login',method: 'post',data: data}).then(response => {const { token } = response.data.data;setToken(token);userState.token = token;resolve(true);}).catch(error => {console.error('登录失败:', error);reject(false);});});
    }/*** 登出API* @returns {Promise<boolean>} - 登出是否成功*/
    export function logout() {return new Promise((resolve, reject) => {request({url: '/admin/logout',method: 'post'}).then(() => {removeToken();userState.token = '';userState.name = '';userState.roles = [];resolve(true);}).catch(error => {console.error('登出失败:', error);removeToken();userState.token = '';resolve(false);});});
    }/*** 获取用户信息API* @returns {Promise<Object>} - 用户信息*/
    export function getInfo() {return new Promise((resolve, reject) => {if (!userState.token || isTokenExpired(userState.token)) {reject(new Error('未登录或令牌已过期'));return;}request({url: '/api/user/info',method: 'get'}).then(response => {const { username, roles, avatar } = response.data.data;userState.name = username;userState.roles = roles;userState.avatar = avatar;resolve(response.data);}).catch(error => {console.error('获取用户信息失败:', error);reject(error);});});
    }/*** 获取当前用户状态* @returns {Object} - 用户状态 { token, name, avatar, roles }*/
    export function getUserState() {return { ...userState };
    }/*** 检查是否已登录* @returns {boolean} - 是否已登录*/
    export function isLoggedIn() {return !!userState.token && !isTokenExpired(userState.token);
    }/*** 检查是否有权限* @param {string} permission - 权限标识* @returns {boolean} - 是否有权限*/
    export function hasPermission(permission) {return userState.roles.includes(permission);
    }

    (5).整合 JWT 认证的路由守卫

    import { createRouter, createWebHashHistory } from 'vue-router'
    import Layout from '@/layout/index.vue'
    import config from '@/config'
    import { getUserState, isLoggedIn, login, getInfo } from '@/api/auth' // 导入认证工具
    import { ElMessage } from 'element-plus'// 前置路由守卫
    router.beforeEach(async (to, from, next) => {// 1. 检查是否需要认证if (to.meta.requiresAuth) {const user = getUserState();// 2. 检查登录状态if (!isLoggedIn()) {ElMessage.warning('请先登录');return next({path: '/login',query: { redirect: to.fullPath }});}// 3. 检查用户信息是否已加载if (!user.roles || user.roles.length === 0) {try {await getInfo(); // 加载用户信息和权限next();} catch (error) {ElMessage.error('获取用户信息失败,请重新登录');next({ path: '/login' });}return;}// 4. 检查角色权限(如果定义了 roles)if (to.meta.roles && to.meta.roles.length > 0) {const hasRole = user.roles.some(role => to.meta.roles.includes(role));if (!hasRole) {return next({ name: '403' });}}next(); // 权限验证通过} else {// 不需要认证的路由直接通过next();}
    });

    (6).修改登录页面

    [1].首先需要在登录成功后获取 JWT 令牌并存储到本地:

    
    <script setup>const form = ref(null);
    const router = useRouter()
    const loading = ref(false); // 登录加载状态
    const modelForm = reactive({username: '', password: ''});
    const rules = reactive({username: [{required: true, message: '用户名不能为空'}],password: [{required: true, message: '密码不能为空'}],
    });const onSubmit = async () => {form.value.validate(async (valid) => {if (valid) {loading.value = true;try {// 调用登录APIconst response = await login({username: modelForm.username,password: modelForm.password});// 假设响应中包含token字段const {token} = response.data.data;// 存储JWT令牌setToken(token);ElMessage.success('登录成功');// 跳转到首页router.push('/user/admin');} catch (error) {console.error('登录失败', error);ElMessage.error('用户名或密码错误');} finally {loading.value = false;}}})
    }
    </script>

    http://www.xdnf.cn/news/18728.html

    相关文章:

  • 力扣(有效括号)
  • 用蒙特卡洛法求解三门问题和Π
  • GPIO子系统自主实现(简单版)
  • 开发避坑指南(36):Java字符串Base64编码实战指南
  • 迭代器设计模式
  • 《XXL-Job 全面介绍:Java 开发中的分布式任务调度框架》
  • 【互动屏幕】为什么现在数字展厅偏爱地面互动装置?
  • 嵌入式Linux内核编译与配置
  • 神经网络与梯度算法:深度学习的底层逻辑与实战解析
  • 微论-神经网络中记忆的演变
  • “Datawhale AI夏令营--coze空间
  • Java 探针的原理
  • 深入解析:为什么应该避免使用 atoi、atol 和 atof 函数
  • 《C++ Primer 第五版》省略符号(...)
  • 【小增长电商技术分享】电商支付宝批量转账工具技术测评:架构特性、合规风险与选型方法论,支付宝官方|小增长|云方付|易推客省心返
  • vi/vim 查找字符串
  • Ajax笔记(上)
  • Spark面试题
  • Redis面试精讲 Day 30:Redis面试真题解析与答题技巧
  • 南京魔数团:AR技术引领远程协作新纪元
  • Java网络编程:从入门到精通
  • STM32之DMA详解
  • 算法题记录01:
  • 8月25日
  • 专题:2025人工智能2.0智能体驱动ERP、生成式AI经济现状落地报告|附400+份报告PDF、原数据表汇总下载
  • [论文阅读]RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation
  • k8s的etcd备份脚本
  • AR技术赋能农业机械智能运维
  • 电机控制::基于编码器的速度计算与滤波::RLS
  • 【C++】第二十六节—C++11(中) | 右值引用和移动语义(续集)+lambda