会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:
客户端会话跟踪技术:Cookie
服务端会话跟踪技术:Session
令牌技术
会话跟踪方案对比:
Cookie-优点:HTTP协议中支持的技术
缺点:移动端APP无法使用Cookie
不安全,用户可以自己禁用Cookie
Cookie不能跨域
Session-优点:存储在服务端,安全
缺点:服务器集群环境下无法直接使用Session
Cookie的缺点
优点:支持PC端、移动端
解决集群环境下的认证问题
减轻服务器端存储压力
缺点:需要自己实现
JWT令牌
全称:JSON Web Token
定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
组成:
第一部分:Header(头),记录令牌类型,签名算法等。
例如:{"alg":"HS256","type":"JWT"}
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。
例如:{"id":"1","usename":"Tom"}
第三部分:Signature(签名),防止Token被篡改、确保安全性。
将header、payload,并加入指定密钥,通过指定签名算法计算而来
JWT
场景:登录认证
1.登录成功后,生成令牌
2.后续每个请求,都要携带JWT令牌,系统在每次请求处理之前,先校验令牌,同过后,再处理。
向pom.xml引入依赖:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
@Testpublic void testGenJwt() {Map<String, Object> claims = new HashMap<>();claims.put("id", 1);claims.put("name", "tom");String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "itbignyi").setClaims(claims).setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)).compact();System.out.println(jwt);
@Testpublic void testParseJwt() {Claims claims = Jwts.parser().setSigningKey("itbignyi").parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTY5NDUzMjAwOH0.D4TjgQSvqZJlUNioEKxUn8euvxu9gqnSo4jfsJGuL7Q").getBody();System.out.println(claims);
注意事项:JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥时匹配的。
如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或者失效了,令牌非法
将代码写入:
过滤器Filter
概念:Filter过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、处理敏感字符等。
定义Filter:定义一个类,实现Filter接口,并重写其所有方法
配置Filter:Filter类上加@WebFilter注解,配置拦截资源的路径,引导类上加@ServletComponerntScan开启Servlet组件支持。
package com.itheima.filter;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {Filter.super.destroy();}
}
Filter拦截路径:
Filter可以根据需求,配置不同的拦截资源路径:
过滤器链:
介绍:一个Web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序
登录校验
登录校验Filter-流程:
1.获取请求url2.判断请求url中是否包含login,如果包含,说明登录操作,放行3.获取请求头中的令牌(token)4.判断令牌是否存在,如果不存在,返回错误结果(未登录)5.解析token,如果解析失败,返回错误结果(未登录)6.放行
package com.itheima.filter;import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;import javax.imageio.spi.ServiceRegistry;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@Slf4j
@WebFilter(urlPatterns = "/*")public class LoginCheckFilter implements Filter {@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest req = (HttpServletRequest) servletRequest;HttpServletResponse res = (HttpServletResponse) servletResponse;
// 1.获取请求urlString url = req.getRequestURI().toString();log.info("请求的url:{}", url);
// 2.判断请求url中是否包含login,如果包含,说明登录操作,放行if (url.contains("login")) {log.info("登录操作,放行...");filterChain.doFilter(servletRequest,servletResponse);return;}
// 3.获取请求头中的令牌(token)String jwt = req.getHeader("token");
// 4.判断令牌是否存在,如果不存在,返回错误结果(未登录)if (!StringUtils.hasLength(jwt)) {log.info("请求头token,为空,返回未登录的信息");Result error = Result.error("NOT_LOGIN");String notLogin = JSONObject.toJSONString(error);res.getWriter().write(notLogin);return;}
// 5.解析token,如果解析失败,返回错误结果(未登录)try {JwtUtils.pareJWT(jwt);} catch (Exception e) {e.printStackTrace();log.info("解析失败");Result error = Result.error("NOT_LOGIN");String notLogin = JSONObject.toJSONString(error);res.getWriter().write(notLogin);return;}
// 6.放行log.info("合法放行");filterChain.doFilter(servletRequest, servletResponse);}
}
拦截器Interceptor
概述:是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
Interceptor快速入门
1.定义拦截器,实现HandlerInterceptor接口,并重写其所有方法
2.注册拦截器
package com.itheima.config;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 {@Autowiredprivate LoginCheckInterceptor loginCheckInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");}}
package com.itheima.interceptor;import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return HandlerInterceptor.super.preHandle(request, response, handler);}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}
拦截器-拦截路径
拦截器可以根据需求,配置不同的拦截路径
拦截器-执行流程
Filter和Interceptor
接口范围不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口
拦截范围不同:过滤器Filter会拦截所有资源,而Interceptor只会拦截Spring环境中的资源
运行代码:
package com.itheima.interceptor;import com.alibaba.fastjson.JSONObject;
import com.itheima.pojo.Result;
import com.itheima.utils.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 {@Overridepublic boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {// 1.获取请求urlString url = req.getRequestURI().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");String notLogin = JSONObject.toJSONString(error);res.getWriter().write(notLogin);return false;}
// 5.解析token,如果解析失败,返回错误结果(未登录)try {JwtUtils.pareJWT(jwt);} catch (Exception e) {e.printStackTrace();log.info("解析失败");Result error = Result.error("NOT_LOGIN");String notLogin = JSONObject.toJSONString(error);res.getWriter().write(notLogin);return false;}
// 6.放行log.info("合法放行");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
异常处理
全局异常处理器
package com.itheima.exception;import com.itheima.pojo.Result;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice
public class GlobalExceptionhandler {@ExceptionHandler(Exception.class)public Result ex(Exception ex) {ex.printStackTrace();return Result.error("操作失败,联系管理员");}
}