一、JWT技术 ★★★★★
JWT介绍
JWT:JSON Web Token,是一种用户身份识别的令牌技术。用于解决web开发中身份识别问题。
JWT结构
由三部分组成:
Header.Body.signature
Header 头:里面存放的是令牌的类型、签名算法等等元数据信息
Body 体/载荷:里面存放的是用户的身份信息
Signature 签名:用于进行防篡改的校验的。
特性:
- 可以防篡改:因为令牌里含有签名,通过校验签名可以发现令牌是否被篡改
- 不能防泄漏:因为令牌里的数据使用的是Base64编码得到的,可以在进行解码还原
JWT的使用
1.添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
2.JWT的生成与解析
生成令牌:Jwts.builder().addClaim(用户身份信息map).setExpirtion(过期时间).signWith(签名算法,密钥).compact();
解析令牌:Jwts.parse().setSigningKey(密钥).parseClaimJws(令牌).getBody() 如果令牌过期或被篡改,会抛异常
public class DemoJwt {
public static void main(String[] args) throws InterruptedException {
//1. 生成JWT令牌
//1.1 准备载荷信息,即用户的身份信息
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("username", "tom");
//1.2 生成JWT令牌
String token = Jwts.builder()
//把载荷内容设置到JWT令牌里
.setClaims(claims)
//设置令牌的过期时间。这里设置有效期为:截止到当前时间+1000毫秒,即有效期只有1秒(可以根据实际情况自定义有效期)
.setExpiration(new Date(System.currentTimeMillis() + 1000))
//设置令牌的密钥。在将来解析令牌时,根据密钥校验令牌的签名,防止令牌被篡改
.signWith(SignatureAlgorithm.HS256, "itheima")
//生成令牌
.compact();
System.out.println("token = " + token);
//2. 校验与解析令牌:如果令牌过期,会抛出异常;如果令牌被篡改,会抛出异常;如果一切正常,会得到令牌里的载荷内容
Claims claimsRes = Jwts.parser()
//设置密钥。用于稍后的令牌签名校验,判断令牌是否被篡改了。如果令牌被篡改,会抛出异常
.setSigningKey("itheima")
//校验并解析令牌。如果令牌被篡改或已过期,会抛出异常
.parseClaimsJws(token)
//获取令牌载荷内容
.getBody();
System.out.println(claimsRes);
}
}
登录功能生成JWT令牌
分析JWT的使用过程
代码
@RestController
public class LoginController {
@Autowired
private EmpService empService;
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
Emp e = empService.login(emp);
if (e != null) {
Map<String, Object> claims = new HashMap<>();
claims.put("id", e.getId());
claims.put("username", e.getUsername());
String token = JwtUtils.generateJwt(claims);
return Result.success(token);
}
return Result.error("帐号或密码错误");
}
}
二、过滤器Filter
JavaEE原始技术,如果开发时没有使用框架,就要使用过滤器进行拦截过滤。
使用步骤
-
创建Java类:
-
实现javax.servlet.Filter接口,重写接口的doFilter方法
- doFilter方法参数 doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- ServletRequest:代表请求的request对象。如果要接收请求里的数据,就使用request对象。使用时强转成HttpServletRequest
getMethod():获取请求方式getRequestURI():获取请求路径getHeader(String name):获取请求头的值
- ServletResponse:代表响应的response对象。如果要给客户端返回数据,就使用response对象。要强转成HttpServletResponse
setStatus(int code):设置响应状态码setContentType("application/json;charset=utf-8"):告诉客户端本次响应的内容是json格式,并且是utf-8getWriter().print(String str):给客户端响应的内容
- FilterChain :过滤器链对象。
doFilter(request, response):放行到下一个过滤器。如果后边没有过滤器了,就放行到目标资源
- ServletRequest:代表请求的request对象。如果要接收请求里的数据,就使用request对象。使用时强转成HttpServletRequest
- doFilter方法参数 doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-
类上加注解@WebFilter
拦截范围
@WebFilter(urlPatterns = "拦截范围")@WebFilter(value = "拦截范围")@WebFilter("拦截范围")
拦截范围的写法,常用的有:
拦截范围 语法示例 作用 精确拦截 /emps只拦截客户端对 /emps的请求范围拦截 /emps/*(必须以/开头以*结尾)拦截客户端对 /emps下的资源请求
比如/emps,/emps/1,/emps/xx/yy等等
-
-
在引导类上加@ServletComponentScan
执行过程
使用Filter校验登录状态
分析:
- 获取本次请求的uri路径
- 如果是登录(/login):直接放行
- 判断是否携带token,如果没有携带不放行
- 解析校验token是否正确,不正确不放行
- token检查一切正常,放行
代码示例:
@WebFilter("/*")
public class LonginFilterTest1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//1.获取本次请求的URI路径
String uri = httpServletRequest.getRequestURI();
//2.判断是不是登录
if("/login".equals(uri)){
//如果是登录,直接放行
chain.doFilter(request, response);
return;
}
//3.判断是否携带token,如果没有携带不放行
String token = httpServletRequest.getHeader("token");
if(token == null || "".equals(token)){
//setStatus():设置响应状态码
httpServletResponse.setStatus(401);
//getWriter().println()给客户端响应的内容
httpServletResponse.getWriter().println(JSON.toJSONString("未登录"));
return;
}
//解析校验token是否正确,如果不正确不放行
try {
JwtUtils.parseJWT(token);
} catch (Exception e) {
httpServletResponse.setStatus(401);
httpServletResponse.getWriter().println(JSON.toJSONString("token解析异常"));
return;
}
//token一切正常,直接放行
chain.doFilter(request, response);
}
}
三、拦截器interceptor ★★★★★
由SpringMVC框架(SpringBootWeb起步依赖)提供的技术
使用步骤
-
创建拦截器类:创建Java类,实现HandlerInterceptor接口,重写接口里的方法(需要哪个,就重写哪个)
- preHandle方法:在目标方法执行前的预处理方法
- postHandle方法:在目标方法执行成功后的后处理方法
- afterCompletion方法:在服务端给客户端返回响应之前执行的最终方法
-
配置拦截器
-
创建Java类,作为Spring的配置类
-
添加注解@Configuration,把类标记为Spring的配置类
-
实现接口WebMvcConfigurer,作为Web层框架SpringMVC的配置类,必须实现此接口
-
重写接口的addInterceptor(IntegerceptorRegistry registry)方法
在方法里配置拦截器
registry.addInterceptor(拦截器对象) .addPathPatterns("拦截范围") .excludePathPatterns("排除不拦截的范围");
-
拦截范围
| 写法 | 示例 | 作用 |
|---|---|---|
| 拦截一级匹配路径 | /emp/* | 只拦截一级目录 拦截 /emp/1 不拦截/emp/xx/yy |
| 拦截任意级匹配路径 | /emp/** | 拦截任意级目录 拦截/emp,/emp/1,emp/xx/yy... |
使用Interceptor校验登录状态
分析:
-
创建拦截器LoginInterceptor
- 如果本次请求是登录,就直接放行
- 获取本次请求的token
- 如果没有token:直接返回响应结果
- 如果token过期或非法:直接返回响应结果
- 一切正常放行
-
配置拦截器
配置拦截范围
LoginInterceptor
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 如果本次请求是登录,就直接放行
String uri = request.getRequestURI();
if ("/login".equals(uri)) {
log.info("本次请求是登录,直接放行");
return true;
}
//2. 获取本次请求携带的token
String token = request.getHeader("token");
// 如果没有token:直接返回响应结果
if (!StringUtils.hasText(token)) {
response.setContentType("application/json;charset=utf-8");
response.setStatus(401);
response.getWriter().println(JSON.toJSONString(Result.error("未登录")));
return false;
}
// 如果token过期或非法:直接返回响应结果
try {
JwtUtils.parseJWT(token);
} catch (Exception e) {
response.setContentType("application/json;charset=utf-8");
response.setStatus(401);
response.getWriter().println(JSON.toJSONString(Result.error("请重新登录")));
return false;
}
//3. 放行
return true;
}
}
WebConfig里配置拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**");
}
}
四、过滤器和拦截器的区别
| 过滤器 | 拦截器 | |
|---|---|---|
| 技术来源 | Java EE本身提供的技术规范 | SpringMVC框架(Spring Boot web)提供 |
| 适用范围 | 任意JavaWeb都可以使用 | 只有项目引用了SpringMVC矿建,才可以使用 |
| 接口规范 | 需要实现javax.servlet.Filter接口 | 需要实现HandlerInterceptor接口 |
| 拦截范围 | 可以拦截客户端的所有请求 | 只能拦截SpringMVC管理的资源 |
五、用到的API
request对象
我们通常说“request”对象,指的是 Tomcat帮我们把HTTP请求的信息封装成的对象。Tomcat提供了两个类:
- ServletRequest接口:封装请求信息的基础对象接口
- HttpServletRequest接口:继承了 ServletRequest,提供了更多操作HTTP请求的方式
我们通过request对象,可以HTTP请求的一切信息
| 方法 | 说明 |
|---|---|
| getMethod() | 获取请求方式 |
| getRequestURI() | 获取本次请求的资源路径 |
| getHeader(String name) | 根据请求头的名称,获取请求头的值 |
response对象
我们通常说的“response”对象,指的是Tomcat帮我们提供的一个响应对象,可以看成一个容器
我们得到这个对象以后,向这个对象里设置的数据,最终会被Tomcat转换成HTTP响应,返回给客户端。
- ServletResponse接口:用于封装响应信息的对象接口
- HttpServletResponse接口:继承了 ServletResponse,提供了更多操作HTTP响应的方法
| 方法 | 说明 |
|---|---|
| setStatus(int code) | 设置响应状态码。401未认证(未登录) |
| setHeader(String name, String value) | 设置响应头 |
| setContentType(String contentType) | 设置“Content-Type”响应头的值,通常用于防止响应内容乱码 |
| getWriter().print(String str) | 使用字符输出流,设置响应体内容。通常用于响应文本内容 |
| getOutputStream() | 使用字节输出流,设置响应体内容。通常用于响应二进制内容 |
JSON类
服务端通常需要 json格式字符串 和 javaBean对象之间进行转换。可以使用一些小工具:
-
Jackson:SpringMVC框架内置的工具包。
ObjectMapper om = new ObjectMapper();
Java对象转json字符串:String jsonStr = om.writeValueAsString(Java对象)
json字符串转Java对象:Xxx xxx = om.readValue(json字符串, Xxx.class)
-
FastJson:Alibaba提供的工具包。需要额外添加依赖坐标
Java对象转json字符串: String jsonStr = JSON.toJSONString(Java对象)
json字符串转Java对象:Xxx xxx = JSON.parseObject(json字符串, Xxx.class)
-
Gson:Google提供的工具包。国外使用比较多,也需要额外添加依赖坐标
Gson gson = new Gson();
通过gson的方法,实现Java对象和json字符串的互相转换