基于 jwt 的登录认证
一、基础版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的发送范围,避免在其他接口暴露

更多推荐




所有评论(0)