在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 { @Autowired UserMapper 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; //数据 data public 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); } @Override public String toString() { return "Result{" + "code=" + code + ", msg='" + msg + '\'' + ", data=" + data + '}'; } }
- 在controller包下添加LoginController类
@RestController @Slf4j public class LoginController { //依赖业务层对象 @Autowired private 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 { @Override public 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.获取请求url String 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和过滤器 完成登录验证的功能了.