一、基础版jwt登录

前端拦截器,在src/utils 中新建一个request.js 文件, 重新封装 axios。包含基础地址、超时时间;请求拦截器和响应拦截器。

// 封装axios
import axios from 'axios'
import store from '@/store'
import { Message } from 'element-ui'

// 获取当前环境
let isDev = process.env.NODE_ENV === 'development';

const service = axios.create({
  baseURL: isDev ? process.env.VUE_APP_DEV_BASE_API : process.env.VUE_APP_PRO_BASE_API,
  timeout: 20000 
})

// 请求拦截器
service.interceptors.request.use(config => {
  // 注入token
  if (store.getters.token) {
    config.headers.Authorization = `Bearer ${store.getters.token}`
  }
  return config;
}, error => {
  Message.error({type: 'error', message: error.message})
  return Promise.reject(error)
})


// 响应拦截器
service.interceptors.response.use(response => {
  // console.log(response)
  const {data, message, code} = response.data;
  if (code == 200) {
    return response.data;
  } else {
    // if (code == 401) {
    //   Message.error({type: 'error', message: '鉴权失败'})
    // } else if (code == 1001) {
    //   Message.error({type: 'error', message: message})
    // } else {
      Message.error({type: 'error', message: message})
    // }
    return response.data;
    // return Promise.reject(new Error(message))
  }
}, error => {
  Message.error({type: 'error', message: error.message})
  return Promise.reject(error)
})


export default service;

前端登录接口

// 登录
export function reqLogin(dataParams) {
  return request({
    url: '/user/login',
    method: 'post',
    data: dataParams

  })
}

1.后端登录接口

@PostMapping("/login")
    public ResultBean login(@RequestBody Map<String, String> params) {
        return ResultBean.success("登录成功", userService.login(params.get("username"), params.get("password")));
    }

2. 登录业务逻辑

判断用户名是否存在,如果存在判断密码是否正确,如果正确则生成 token返回给前端。

 // 登录
    @Override
    public Map<String, Object> login(String username, String password) {
        HashMap<String, Object> userObj = new HashMap<>();
        Users user = new Users();
        Users u = userMapper.selectUserInfoByUsername(username);
        // 判断用户名是否正确
        if (u == null) {
            throw new ErrorException("账号不存在");
        }
        // 判断密码是否正确
        if (!password.equals(u.getPassword())) {
            throw new ErrorException("密码错误");
        }
        // 生成令牌
        String token = JWTUtils.generatorJWT(u.getUsername());
        // 返回用户信息
        user.setId(u.getId());
        user.setUsername(u.getUsername());
        user.setName(u.getName());
        userObj.put("info", user);
        userObj.put("token", token);
        return userObj;
    }

3. 拦截器

新建LoginInterceptor 自定义拦截器。每次请求会被 LoginInterceptor 拦截器拦截,获取请求中的请求头携带的 token,校验是否token过期

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
        //1. 从请求头中获取令牌
        String authHeader = request.getHeader("Authorization");  // Authorization 是前后端定义好的,前端每次都会携带 Authorization 
        String token = null;
        if (authHeader != null && authHeader.startsWith("Bearer ")) {  // Bearer 是token的类型
            token = authHeader.substring(7);
        }
        //2. 校验 Jwt 令牌
        try {
            Claims claims = checkJWT(token);
            String username = claims.get("username").toString();  // 员工工号
            request.setAttribute("username", username);
            return true;   // 放行
        } catch (Exception e) {
            response.setStatus(SC_UNAUTHORIZED);  // 401状态码, 校验失败
            return false;
        }
    }
}

4. 新建自定义拦截器

新建自定义拦截器WebConfig,并且继承 WebMvcConfigurationSupport,重写 addInterceptors 和 addResourceHandlers 方法。

@Configuration
public class WebConfig  extends WebMvcConfigurationSupport {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/user/login"
                );
    }

    // 静态资源映射
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/**");
        registry.addResourceHandler("/templates/**")
                .addResourceLocations("classpath:/**");
    }
}

新建一个 jwtUtils 工具包,用于生成 token 和 解析 token用

@Slf4j
public class JWTUtils {
    private static final String SIGNATURE = "test";
    private static final long EXPIRATION = 24 * 60 * 60 * 1000;  // 24h

    // 生成 令牌
    public static String generatorJWT(String username) {
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")
                .claim("username", username)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))  // 过期时间
                .signWith(SignatureAlgorithm.HS256, SIGNATURE)// 签名算法
                .compact();
    }

    // 解析令牌
    public static Claims checkJWT(String token) {
        return Jwts.parser()
                .setSigningKey(SIGNATURE)
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
    }
}

到这为止算是完成了,但是会有一个问题,假设用户此时正在操作系统,但是这是正好token失效了,就会跳转到登录页面,这对用户来说是非常不友好的,所以可以采用双token解决这个问题,在token即将过期时自动续期token,避免用户频繁登录。

二、双 token 方案

基于Spring Boot的双Token认证(Access Token + Refresh Token)结合Cookie的完整认证方案。该方案通过Access Token进行短期访问控制,通过Refresh Token进行长期会话维持

Token类型

Access Token (访问令牌):短期有效(默认30分钟),用于API访问认证,安全风险小,即使泄露过期也容易失效。
Refresh Token (刷新令牌):长期有效(默认24小时),用于获取新的Access Token;支持活动宽限期(默认30分钟),活跃用户自动续期,存储需要更安全。
 

存入Cookie中的内容说明
Access Token Cookie (AT):

Name:AT
Path:设置为白名单路径(如/file/downFile, /file/downFiles等)
用途:仅在这些特定路径下浏览器才会自动携带Cookie
场景:主要用于文件下载接口,解决img标签等无法自定义Header的问题


Refresh Token Cookie (RT):

Name:RT
Path:设置为/auth/refreshToken
用途:仅在刷新Token接口时浏览器自动携带
安全:限制了Cookie的发送范围,避免在其他接口暴露

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐