Springboot+JWT验证 前后端
前言
使用Springboot+JWT+vue 完成前后端分离验证
一、JWT 是什么?
JWT 主要用于用户登录鉴权,是一种认证机制,让后台知道该请求是来自于受信的客户端
验证流程:
1.用户使用账号、密码登录应用,登录的请求发送到 Authentication Server。
2.Authentication Server 进行用户验证,然后创建 JWT 字符串返回给客户端。
3.客户端请求接口时,在请求头带上 JWT。
4.Application Server 验证 JWT 合法性,如果合法则继续调用应用接口返回结果。
数据结构:xxxxx.yyyyy.zzzzz(是一段字符串,分为三个部分,以 “.” 隔开)
开发流程
1.编写JwtInterceptor生成规则
2.编写InterceptorConfig增加规则,进行拦截
3.编写TokenUtils 生成token返回给前端
二、使用步骤
1.引入库
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.3.0</version>
</dependency>
2.JwtInterceptor
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.qinggedemoone.entity.User;
import com.example.qinggedemoone.exception.ServiceException;
import com.example.qinggedemoone.mapper.UserMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//以拦截器方式进行继承
public class JwtInterceptor implements HandlerInterceptor {
@Resource
private UserMapper userMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token=request.getHeader("token"); //前端header中传过来的参数
if(StrUtil.isBlank(token)){
token=request.getParameter("token"); //url参数 ?token=xxxx
}
System.out.println("==token==:"+token);
//执行认证
if(StrUtil.isBlank(token)){
throw new ServiceException("401","请登录");
}
//获取token中的user id
String userId;
try{
//getAudience相当于一个仓库,存储了很多信息
//get(0) 获取audience中的第一个数据
userId= JWT.decode(token).getAudience().get(0);
}catch (JWTDecodeException j){
throw new ServiceException("401","请登录");
}
//更具token中的userId查询数据
User user = userMapper.selectById(userId);
System.out.println("userId:"+userId);
System.out.println("++user++"+user);
if(user==null){
throw new ServiceException("401","请登录");
}
//通过用户密码 加密 生成一个验证器
/*
* 使用用户密码(user.getPassword())作为密钥来创建一个 HMAC256 算法实例,
* 并基于这个算法实例生成一个 JWT 验证器(JWTVerifier)。JWT.require(Algorithm)
* 方法返回一个 JWT 构建器,通过调用 .build() 方法来构建验证器。
* Algorithm:算法
* */
JWTVerifier build = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try{
build.verify(token); //进行校验
}catch (JWTVerificationException e){
throw new ServiceException("401","请登录");
}
return true;
}
}
3.InterceptorConfig
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.WebMvcConfigurationSupport;
/*
* InterceptorConfig拦截
* JwtInterceptor定义拦截规则
* */
/*@Configuration配置类*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
/*
* 实现addInterceptors方法 addInterceptor增加拦截器规则
* addPathPatterns:拦截路径 /**拦截所有的请求路径
* */
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**").excludePathPatterns("/login","/register");
super.addInterceptors(registry);
}
/* @Bean注入到容器中*/
@Bean
public JwtInterceptor jwtInterceptor(){
return new JwtInterceptor();
}
}
4.定义生成token的方法
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.qinggedemoone.entity.User;
import com.example.qinggedemoone.mapper.UserMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
@Component //加入到spring容器中
public class TokenUtils {
//静态的 可以直接通过类去访问 下面有方法是静态的 所以要在此声明静态的方法
//静态方法只能访问静态变量
private static UserMapper staticUserMapper;
@Resource //要想拿到容器中的对象 必须类上添加@Component注解
UserMapper userMapper;
@PostConstruct
public void setUserService(){
staticUserMapper=userMapper;
}
/*生成token*/
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对象
* 只要请求有token 我就可以通过token来判断当前登录的用户的所有信息
* */
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.selectByUsername(userId);
}
}catch (Exception e){
return null;
}
return null;
}
}
接口请求
@Autowired
UserService userService;
@PostMapping("/login")
public Result login(@RequestBody User user){
if(StrUtil.isBlank(user.getUsername())||StrUtil.isBlank(user.getPassword())){
return Result.error("输入数据不合法");
}
User login = userService.login(user);
return Result.success(login);
}
UserService login方法中生成token
@Override
public User login(User user) {
//根据用户名查询数据库的用户信息
User user1 = userMapper.selectByUsername(user.getUsername());
if(user1==null){
//抛出一个自定义异常
throw new ServiceException("账号不存在");
}
if(!user.getPassword().equals(user1.getPassword())){
throw new ServiceException("用户名或密码错误");
}
//当通过之后生成token
String token = TokenUtils.createToken(user1.getId().toString(), user1.getPassword());
user1.setToken(token);
return user1;
}
插播一段注意事项:后端一定要配置跨域
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
private static final long MAX_AGE=24*60*60;
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration=new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); //设置访问原地址
corsConfiguration.addAllowedHeader("*"); //设置访问源请求头
corsConfiguration.addAllowedMethod("*"); //设置访问原请求方法
corsConfiguration.setMaxAge(MAX_AGE);
source.registerCorsConfiguration("/**",corsConfiguration); //对接口配置跨域设置
return new CorsFilter(source);
}
}
前端中的内容
request.js
import router from '@/router';
import axios from 'axios'
// 创建可一个新的axios对象
const request = axios.create({
baseURL: 'http://localhost:8080', // 后端的接口地址 ip:port
timeout: 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") || '{}')
config.headers['token'] = user.token // 设置请求头
return config
}, error => {
console.error('request error: ' + error) // for debug
return Promise.reject(error)
});
// 在响应返回后,通过拦截器统一处理结果。
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
response => {
let res = response.data;
// 兼容服务端返回的字符串数据
if (typeof res === 'string') {
res = res ? JSON.parse(res) : res
}
//如果接口返回401 直接跳转到登录页面
if(res.code==='401'){
router.push('/login')
}
return res;
},
error => {
console.error('response error: ' + error) // for debug
return Promise.reject(error)
}
)
export default request
前端登录函数
login(){
console.log(this.code)
console.log(this.SRcode)
if(this.code.toLowerCase()==this.SRcode.toLowerCase()){
console.log('验证码输入正确')
}else{
this.$message.error('验证码错误');
}
request.post('/login',this.user).then(res=>{
console.log(res)
if(res.code=='200'){
this.$router.push('/')
this.$message.success('登录成功');
localStorage.setItem("honey-user",JSON.stringify(res.data)) //存储用户数据,key-value方式存储
}if(res.code=='500'){
this.$message.error('用户名或密码错误');
}
})
}