什么是拦截器?
- 是一种动态拦截方法调用的机制,类似于过滤器。
- 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行。
拦截器的作用:
- 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。
拦截器与过滤器主要作用:
是用来对请求进行拦截的,如果是登录请求则直接放行,如果是其他请求,进行拦截,判断该请求携带的token是否没过期,是否正确,如果没过期并且是正确的,则进行放行该请求
快速入门:
步骤:
1.定义拦截器
自定义拦截器:实现HandlerIntercrptor接口,并重写所有方法
//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
//目标资源方法执行前执行。 返回true:放行 返回false:不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle .... ");
return true; //true表示放行
}
//目标资源方法执行后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ... ");
}
//视图渲染完毕后执行,最后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion .... ");
}
}
注意:
preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行
postHandle方法:目标资源方法执行后执行
afterCompletion方法:视图渲染完毕后执行,最后执行
2.注册配置拦截器
注册配置拦截器**:实现WebMvcConfigurer接口,并重写addInterceptors方法
@Configuration
public class WebConfig implements WebMvcConfigurer {
//自定义的拦截器对象
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
}
}
重新启动SpringBoot服务,打开postman测试:
接下来我们再来做一个测试:将拦截器中返回值改为false
使用postman,再次点击send发送请求后,没有响应数据,说明请求被拦截了没有放行
拦截路径
在拦截器中除了可以设置/**
拦截所有资源外,还有一些常见拦截路径设置:
拦截路径 | 含义 | 举例 |
---|---|---|
/* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
/** | 任意级路径 | 能匹配/depts,/depts/1,/depts/1/2 |
/depts/* | /depts下的一级路径 | 能匹配/depts/1,不能匹配/depts/1/2,/depts |
/depts/** | /depts下的任意级路径 | 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1 |
执行流程
介绍完拦截路径的配置之后,接下来我们再来介绍拦截器的执行流程。通过执行流程,大家就能够清晰的知道过滤器与拦截器的执行时机。
- 当我们打开浏览器来访问部署在web服务器当中的web应用时,此时我们所定义的过滤器会拦截到这次请求。拦截到这次请求之后,它会先执行放行前的逻辑,然后再执行放行操作。而由于我们当前是基于springboot开发的,所以放行之后是进入到了spring的环境当中,也就是要来访问我们所定义的controller当中的接口方法。
- Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller。
- 当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住。执行
preHandle()
方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。 - 在controller当中的方法执行完毕之后,再回过来执行
postHandle()
这个方法以及afterCompletion()
方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据。
登录校验(是否登录成功)案例:(1.自定义登录拦截器 2.注册配置拦截器 3.编写登录代码)
拦截器实现登录校验步骤:
- 获取请求路径
- 判断是否是登录请求
- 如果是登录请求则直接放行
- 如果不是则获取登录成功后生成的JWT令牌(token)
- 判断令牌是否存在且有效,如果不存在其无效则返回未登录结果
- 如果令牌存在且有效,则进行解析token,解析失败返回未登录结果
- 解析成功,放行
代码实现:
a. 实现登录基本功能
package com.itheima.controller;
import com.itheima.pojo.Emp;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import com.itheima.until.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
Emp emp1 = empService.login(emp);
if (emp1 != null) {//登录成功
//自定义信息
Map<String , Object> claims = new HashMap<>();
claims.put("id", emp1.getId());
claims.put("username",emp1.getUsername());
claims.put("name",emp1.getName());
//敏感性信息不能放到token中
//claims.put("password",emp1.getPassword())
String token = JwtUtils.generateJwt(claims);
return Result.success(token);
}
//登录失败
return Result.error("账户或密码错误");
}
}
b. 生成token,并返回
package com.itheima.until;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
private static String signKey = "itheima";//签名密钥
private static Long expire = 43200000L; //有效时间
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
* SignatureAlgorithm.HS256 算法
*/
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令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}
c. 定义一个配置类,配置拦截器
package com.itheima.config;
import com.itheima.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
//注册配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
/**
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自定义拦截器对象
registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
//设置拦截器拦截的请求路径( /** 表示拦截所有请求)
//excludePathPatterns("不拦截路径")`方法,指定哪些资源不需要拦截。
}
}
d. 定义拦截器,并进行解析token,返回解析成功,则放行该请求
package com.itheima.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.until.JwtUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取令牌token
String token = request.getHeader("Token");
//判断token是否为空
if (!StringUtils.hasLength(token)) {//令牌为空
//令牌不存在,未登录
Result result = Result.error("NOT_LOGIN");
//把对象转换成json字符串
String s = JSONObject.toJSONString(result);
//把内容响应给浏览器
response.getWriter().write(s);
return false;//不用执行下面代码
}
//token不为空 ,校验token
try {
JwtUtils.parseJWT(token);
//解析成功
} catch (Exception e) {
//解析失败,未登录,或登录已过期
Result result = Result.error("NOT_LOGIN");
//把对象转换成字符串
String s = JSONObject.toJSONString(request);
//把内容响应给浏览器
response.getWriter().write(s);
return false;//不放行
}
//校验成功且token不为空,放行,可以访问具体内容(如:访问员工列表,校验成功则可以看见所有员工列表)
return true;
}
}
e. 解析token的代码
package com.itheima.until;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtils {
private static String signKey = "itheima";//签名密钥
private static Long expire = 43200000L; //有效时间
/**
* 生成JWT令牌
* @param claims JWT第二部分负载 payload 中存储的内容
* @return
* SignatureAlgorithm.HS256 算法
*/
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令牌
* @param jwt JWT令牌
* @return JWT第二部分负载 payload 中存储的内容
*/
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)//指定签名密钥
.parseClaimsJws(jwt)//指定令牌Token
.getBody();
return claims;
}
}