在Spring boot中 使用JWT和过滤器实现登录认证
一、登录获得JWT
- 在navicat中运行如下sql,准备一张user表
-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (`id` int(11) NOT NULL,`username` varchar(50) NOT NULL,`password` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'root', '123', '1');
- 导入pom.xml坐标
<!-- JWT依赖坐标--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- 解析JSON--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.15.2</version></dependency>
- 在工utils包下创建一个用于生成和解析JWT 令牌的工具类
public class JwtUtils {private static String signKey = "baisepengyuyan";//签名密钥(这个可以自定义)private static Long expire = 86400000L; //有效时间 1 天 = 24 小时 * 60 分钟 * 60 秒 * 1000 毫秒 = 86,400,000 milliseconds/*生成JWT令牌*/public static String generateJwt(Map<String, Object> claims){String jwt = Jwts.builder().addClaims(claims)//自定义信息(有效载荷).signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部).setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间.compact();return jwt;}/*解析JWT令牌 */public static Claims parseJWT(String jwt){Claims claims = Jwts.parser().setSigningKey(signKey)//指定签名密钥.parseClaimsJws(jwt)//指定令牌Token.getBody();return claims; //返回令牌中 payload 中存储的内容(登录用户的信息)}
}
- 在pojo包下创建user类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private int id;private String name;private String username;private String password;
}
- 在mapper包下添加 UserMapper接口
@Mapper
public interface UserMapper {@Select("select id ,username ,password from t_user where username = #{username} and password= #{password}")User getLoginUser(User user);
}
- 在service包下添加 UserService类
@Service
public class UserService {@AutowiredUserMapper userMapper;public User login(User user) {User loginUser = userMapper.getLoginUser(user);if (loginUser == null )return null;return user;}
}
- 在utils包下添加统一响应结果封装类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {private Integer code ;//1 成功 , 0 失败private String msg; //提示信息private Object data; //数据 datapublic static Result success(Object data){return new Result(1, "success", data);}public static Result success(){return new Result(1, "success", null);}public static Result error(String msg){return new Result(0, msg, null);}@Overridepublic String toString() {return "Result{" +"code=" + code +", msg='" + msg + '\'' +", data=" + data +'}';}
}
- 在controller包下添加LoginController类
@RestController
@Slf4j
public class LoginController {//依赖业务层对象@Autowiredprivate UserService userService;@PostMapping("/login")public Result login(@RequestBody User user) {//调用业务层:登录功能User loginEmp = userService.login(user);//判断:登录用户是否存在if(loginEmp !=null ){//自定义信息Map<String , Object> claims = new HashMap<>();claims.put("id", loginEmp.getId());claims.put("username",loginEmp.getUsername());claims.put("name",loginEmp.getName());//使用JWT工具类,生成身份令牌String token = JwtUtils.generateJwt(claims);return Result.success(token);}return Result.error("用户名或密码错误");}
}
这样登录获取token的接口就写好了,接下来我们来用post工具来测一下这个登录接口
这里我使用的工具是Apifox
这样我们就拿到了 这个token (这个token的有效期是一天, 有效时间的长短可以在JWT工具类那里设置)
eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjpudWxsLCJpZCI6MCwidXNlcm5hbWUiOiJtYWkiLCJleHAiOjE2OTg2NTg1OTh9.aFDGnKvMOjgpZjdgXcQfaDM1ONupOvdwYtYNxPAaN6s
这样我们就将这个登录获取JWT的功能开发好了,但是, 现在我们仍然可以访问别的请求路径,不被拦截,所以我们要引入过滤器.
二、引入过滤器
引入过滤器的就是为了,限制为未登录的用户。不让他们访问除了登录以外的资源。
那现在我们写一个测试用例,用于测试, 看看没引入过滤器和引入过滤器的区别
- 在LoginController类下添加这个方法
@GetMapping("/getResource")public Result getUser(){return Result.success("成功访问/getResource");}
添加完这个方法后 我们尝试访问 http://localhost:8080/getResource
这是我们可以看到用户在未登录的情况下, 仍然可以正常访问我们的资源, 这是我们不希望看到的.
那么我们开始引入过滤器
- 在启动类上添加 @ServletComponentScan注解
@ServletComponentScan
- 创建一个filter包 ,在这个包下创建 LoginCheckFilter类
@Slf4j
@WebFilter(urlPatterns = "/*") //拦截所有请求
public class LoginCheckFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {//前置:强制转换为http协议的请求对象、响应对象 (转换原因:要使用子类中特有方法)HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//创建json解析对象ObjectMapper objectMapper = new ObjectMapper();//1.获取请求urlString url = request.getRequestURL().toString();log.info("请求路径:{}", url); //请求路径:http://localhost:8080/login//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行if(url.contains("/login")){chain.doFilter(request, response);//放行请求return;//结束当前方法的执行}//3.获取请求头中的令牌(token)String token = request.getHeader("Authorization");log.info("从请求头中获取的令牌:{}",token);//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)if(!StringUtils.hasLength(token)){log.info("Token不存在");Result responseResult = Result.error("NOT_LOGIN");//把Result对象转换为JSON格式字符串String json = objectMapper.writeValueAsString(responseResult);response.setContentType("application/json;charset=utf-8");//响应response.getWriter().write(json);return;}//5.解析token,如果解析失败,返回错误结果(未登录)try {if (token.startsWith("Bearer ")) {token = token.substring(7);log.info("切除Bearer 令牌:{}",token);}JwtUtils.parseJWT(token);}catch (Exception e){log.info("令牌解析失败!");Result responseResult = Result.error("NOT_LOGIN");//把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)String json = objectMapper.writeValueAsString(responseResult);response.setContentType("application/json;charset=utf-8");//响应response.getWriter().write(json);return;}//6.放行chain.doFilter(request, response);}
}
阅读代码我们可以发现, 我们拦截了所有请求,唯独给登录请求放行
现在我们再来尝试访问
但是新的问题来了 , 我们要怎么访问这个资源呢? 对, 使用刚刚我们登录后返回的JWT令牌
我们在每次请求时都在HTTP的请求头中携带JWT令牌过去
访问 http://localhost:8080/login 获得令牌
拿到令牌后 我们访问http://localhost:8080/getResource
在请求中 添加Authorization请求头 值为 Bearer + 空格 + JWT
我们可以看到 在实际的请求总 我们多谢了一个请求头 请求头和请求参数是不一样的!!!
到此 我们已经完成了一个Spring Boot整合JWT和过滤器 完成登录验证的功能了.