会话技术
会话:用户打开浏览器,访问web服务器,会话建立。直到一方断开连接,会话结束。一次会话可以包含多次请求。
会话跟踪:一种维护浏览器状态的方法。服务器需要识别多次请求是否来自同一浏览器,使同一会话的多次请求可以共享数据。
会话跟踪方案:Cookie、Session、令牌技术
JWT令牌技术
作用
在通信双发安全的传输json数据格式的信息。
组成
第一部分:header(头):记录令牌类型、签名算法。
第二部分:payload(有效荷载),携带自定义信息、默认值等。
第三部分:Signature(签名),防止Token被篡改。(将header、payload和指定的密钥,通 过指定的签名算法而来)
header和payload都是通过把Base64而来。
Base64:是一种基于64个可打印字符(A-Z a-z 0-9 + /)来表示二进制数据的编码方式。
JWT应用场景:登陆认证
(1)登录成功后,生成令牌
(2)后续每个请求都要携带JWT令牌,服务器每次收到请求之前,先校验令牌。在controller层生成令牌,响应给前端。
注:如果JWT令牌解析校验报错,则JWT令牌 被篡改或者失效了。
在生成令牌之前,需要先引入依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
log.info("登陆成功。。");
Emp e = loginServer.login(emp);
if (e != null) {
Map<String,Object> map = new HashMap<>();
map.put("id",e.getId());
map.put("name",e.getName());
map.put("password",e.getPassword());
String jwt = JwtUtils.generateJwt(map);
return Result.success(jwt);
}
return Result.error("用户名或密码错误");
}
过滤器Filter
使用步骤
1、定义Filter。定义一个类,实现Filter类,重写其方法
2、配置Filter。Filter类上加上@WebFilter注解,主类(引导类)上加上@ServletComponentScan开启Servlet组件支持。
@ServletComponentScan
@SpringBootApplication
public class TliasApplication {
public static void main(String[] args) {
SpringApplication.run(TliasApplication.class, args);
}
}
Filter拦截路径
如:@WebFilter(urlPatterns = "/*")
过滤器链
一个web应用中,可以配置多个过滤器,形成一个过滤器链。过滤器的优先级是按照过滤器名的自然排序。(字典排序)
登录校验Filter-流程
Filter代码实现
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
import anli.utils.JwtUtils;
import com.alibaba.fastjson.JSONObject;
import anli.Pojos.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
chain.doFilter(request,response);
return;
}
//3.获取请求头中的令牌(token)。
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//6.放行。
log.info("令牌合法, 放行");
chain.doFilter(request, response);
}
}
拦截器Interceptor
拦截器使用步骤
1、定义拦截器,实现HandlerInterceptor接口,重写方法。
2、配置拦截器
配置拦截器需要在HandlerInterceptor类加上@Component
定义配置类
import com.itheima.interceptor.LoginCheckInterceptor;
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 LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
}
实现拦截器
拦截器也用过滤器实现的,需要包含过滤器的配置。
import com.alibaba.fastjson.JSONObject;
//import Result;
//import JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
return true;
}
//3.获取请求头中的令牌(token)。
String jwt = req.getHeader("token");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//6.放行。
log.info("令牌合法, 放行");
return 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...");
}
}