登录校验
服务端或客户端无记录任何用户登录成功的标识,HTTP协议是无状态协议,第一次请求响应登录成功后,下一次请求我们无法判断员工是否已经登录。为了解决这个问题,在登录成功后,将用户登录成功的信息,存起来,记录用户已经登录成功的标记,判断这个标记来判断是否登录成功,我们使用两种技术
-
统一拦截:可以使用两种技术实现,Filter过滤器 以及 Interceptor 拦截器。
-
登录标记:就需要用户登录成功之后,每一次请求中,都可以获取到该标记。
HTTP是无状态的,不能在多次请求间共享数据,需要使用会话跟踪技术来解决
会话技术
会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次
请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据
。
会话跟踪具体的实现方式有:
1). 客户端会话跟踪技术:Cookie
2). 服务端会话跟踪技术:Session
会话技术的缺点:
-
服务端集群环境下Session的共享问题。
-
移动端APP端无法使用Cookie。
模拟客户端和服务端会话跟踪技术
1.Session
@RestController public class HttpSessionTest01 { @Autowired private HttpSession session; //依赖中的类 注入 @GetMapping("/loginTest01") public Result loginTest01(String username) { session.setAttribute("isLongin",true); return Result.success(); }
2.Cookie
@RestController public class HttpSessionTest02 { @Autowired private HttpSession session; //依赖中的类 注入 @GetMapping("/loginTest02") public Result loginTest02() { Boolean isLogin = (Boolean) session.getAttribute("isLogin"); if (isLogin == null || isLogin == false) { return Result.error("未登录成功,不能访问资源"); } else { return Result.success("登录成功,可以访问资源"); } }
JWT 令牌技术
令牌技术优势:
-
解决了集群环境下的认证问题,减轻服务器端的存储压力
-
支持PC端、移动端
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
JWT的精髓在于:“去中心化”,数据是保存在客户端的。优点很多:
-
可以通过参数或者请求头进行发送,数据量小,速度快
-
token负载中包含了很多其他信息,避免查询数据库
-
jwt跨语言,任何web形式都支持
-
不在服务端保存session信息, 对多服务支持友好
JWT的工作原理
1.是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,示例如下: {"UserName": "Chongchong","Role": "Admin","Expire": "2018-08-08 20:15:56"}
-
之后,当用户与服务器通信时,客户在请求中发回JSON对象JWT
-
为了防止用户篡改数据,服务器将在生成对象时添加签名,并对发回的数据进行验证
验证原理(发送过去的数据和密匙解析出来的数据是否一致)
它验证的方法其实很简单,只要把header做base64url解码,就能知道JWT用的什么算法做的签名,然后用这个算法,再次用同样的逻辑对header和payload做一次签名, 并比较这个签名是否与JWT本身包含的第三个部分的串是否完全相同,只要不同,就可以认为这个JWT是一个被篡改过的串,自然就属于验证失败了。接收方生成签名的时候必须使用跟JWT发送方相同的密钥
idea里怎么操作
1.导入依赖
<!--令牌依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
2.
@Component //生成 public class JwtUtils { private static String singKey = "itheima"; private static Long expire =43200000L; public static String generateJwt(Map<String,Object> clamis) { String jwt = Jwts.builder() .addClaims(clamis) .signWith(SignatureAlgorithm.ES256, singKey) .setExpiration(new Date(System.currentTimeMillis() + expire)) .compact(); return jwt; } //解析 public static Claims parseJwt(String jwt) { Claims claims = Jwts.parser() .setSigningKey(singKey) .parseClaimsJws(jwt) .getBody(); return claims ; }
Claims是Map的实现类可以通过键获取值
3.令牌技术工具类集成
导入依赖 ,创建utils包,创建jwtUitls工具类 在controller层创建LoginController 完成登录给令牌功能
1.工具类引入
public class JwtUtils { private static String signKey = "itheima"; private static Long expire = 43200000L; /** * 生成JWT令牌 * @param claims JWT第二部分负载 payload 中存储的内容 * @return */ 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)//解析令牌 .getBody();//生成请求体 return claims; } }
2.使用
@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()); claims.put("name",e.getName()); //生成JWT令牌 String jwt = JwtUtils.generateJwt(claims); return Result.success(jwt); } return Result.error("用户名或密码错误"); } }
统一拦截:
统一拦截请求,在服务端,我们可以通过两种手段实现:过滤器Filter、拦截器Interceptor。
过滤器与拦截器的区别
触发顺序不一样, 过滤器的触发顺序在拦截器前,也就是先执行过滤器,在执行拦截器, 过滤前 - 拦截前 - Action处理 - 拦截后 - 过滤后。 过滤器之间的执行顺序嵌套的, 但是拦截器执行顺序也是嵌套执行的但是,因为有三个方法,会先执行pre方法,再一起执行post方法,最后一起执行after方法 实现方式不同,过滤器基于函数回调实现,拦截器基于动态代理(反射)实现 拦截器是基于java的反射机制的,而过滤器是基于函数回调。 拦截器不依赖与servlet容器,过滤器依赖与servlet容器。 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用。 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问。 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
aop与过滤器,拦截器的区别
过滤器,拦截器拦截的是URL。AOP拦截的是类的元数据(包、类、方法名、参数等)。 过滤器并没有定义业务用于执行逻辑前、后等,仅仅是请求到达就执行。 拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。 AOP针对具体的代码,能够实现更加复杂的业务逻辑。 三者功能类似,但各有优势,从过滤器 -> 拦截器 -> 切面,拦截规则越来越细致。 执行顺序依次是过滤器、拦截器、切面。
1.过滤器
-
概念:Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。
-
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。
-
过滤器一般完成一些通用的操作,比如:登陆鉴权、统一编码处理、敏感字符处理等等…
1.快速入门
1.Servlet 三大组件 Servlet、Filter、Listener 在传统项目中需要在 web.xml 中进行相应的配置。Servlet 3.0 开始在 javax.servlet.annotation 包下提供 3 个对应的 @WebServlet、@WebFilter、@WebListener 注解来简化操作。
2、@WebServlet、@WebFilter、@WebListener 写在对应的 Servlet、Filter、Listener 类上作为标识,从而不需要在 web.xml 中进行配置了。
3.Spring Boot 应用中这三个注解默认是不被扫描的,需要在项目启动类上添加 @ServletComponentScan 注解, 表示对 Servlet 组件扫描。
总结
Filter是依赖于Servlet的,需要导入Servlet的依赖 过滤器会在容器启动时被初始化,并且只会初始化一次 doFilter()方法在目标请求被拦截前执行,放行调用filterChain.doFilter(servletRequest,servletResponse);具体变量名根据方法体而变 destroy()方法在容器销毁时执行,只执行一次 Filter可以拦截所有请求,包括静态资源, 过滤器基于函数回调实现,
扩展
过滤器,监听器和Servlet是JavaWeb三大组件之一,它们的在SpringBoot中的使用方式都差不多区别如下 使用注解方式使用时都需要在启动类加上@ServletComponentScan注解 三个注解为@WebSrvlet,@WebFilter和@Weblistener Servlet类继承的是HttpServlet,Filter实现的是Filter类,Listener实现ServletContextListener类 使用Bean注入方式使用时,Filter组件方法返回类型是FilterRegistrationBean,Listener组件方法返回类型为ServletListenerRefistrationBean,Servlet组件返回类型为ServletRefistrationBean
Filter 拦截路径
拦截路径 | urlPattern值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问/emps下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
实现登录拦截
流程图
1.过滤器登录拦截代码实现
@WebFilter(urlPatterns = "/*") //拦截所有资源路径 public class loginCheckFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { Filter.super.init(filterConfig); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("你被拦截了"); HttpServletRequest request = (HttpServletRequest) servletRequest; //1.获取请求路径 String url = request.getRequestURI().toString(); //2.判断是否是登录请求 if (url.contains("login")) { //放行 filterChain.doFilter(servletRequest,servletResponse); return; } //3.获取请求头 String jwt = request.getHeader("token"); //4.判断是否存在token if (jwt==null) { notLoginResponse(servletResponse);//抽取成方法 //拦截 return; } //解析令牌 try { Claims claims = JwtUtils.parseJWT(jwt); //放行 filterChain.doFilter(servletRequest,servletResponse); } catch (Exception e) { e.printStackTrace(); //令牌失效 notLoginResponse(servletResponse); } } private void notLoginResponse(ServletResponse servletResponse) throws IOException { //返回统一Not_LOGIN结果给前端 Result errorResult=Result.error("Not_LOGIN"); //怎么把errorResult对象转换未JOSN对象 //java 对象 JOSN之间的映射 ObjectMapper objectMapper = new ObjectMapper(); //把java对象变成josn字符串 String json = objectMapper.writeValueAsString(errorResult); //返回到前端 servletResponse.getWriter().write(json); } @Override public void destroy() { Filter.super.destroy(); } }
拦截器
使用方式
1.编写拦截器类并实现HandlerInterceptor 接口 2.实现HandlerInterceptor 接口的preHandle,postHandle和afterHandle方法 3.编写配置类,注意加上@configuration注解,并继承WebMvcConfigurer接口
总结
拦截器依赖于SpringMvc的,需要导入Mvc的依赖 preHandle() 在目标请求完成之前执行。有返回值Boolean类型,true:表示放行 postHandle() 在目标请求之完成后执行。 afterCompletion() 在整个请求完成之后【modelAndView已被渲染执行】。 拦截器只能拦截action请求,不包括静态资源(有待验证) 基于java反射机制实现
拦截范围 :
拦截一级目录/*
拦截所有/**
拦截路径 :
拦截路径 | urlPattern值 | 含义 |
---|---|---|
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问/emps下的下一级资源,如: /emps/1 ,但是 不会拦截 /emps/list/1,/emps/list/1/2 |
目录拦截 | /emps/** | 访问/emps下的所有资源,都会被拦截 |
拦截所有 | /** | 访问所有资源,都会被拦截 |
1.快速入门
1.定义拦截器,实现HandlerInterceptor接口,重写所有方法
@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 false; } //目标资源方法执行后执行 @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 ...."); } }
2.注册拦截器
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginCheckInterceptor loginCheckInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**"); } }
登录拦截器代码实现
@Component public class LoginCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("我是拦截器,你被拦截了"); //1.获取请求路径 String url = request.getRequestURI().toString(); //2.判断是否是登录请求 if (url.contains("login")) { //放行 return true; } //3.获取请求头 String jwt = request.getHeader("token"); //4.判断是否存在token 如果存在解析令牌 if (jwt==null) { notLoginResponse(response); //拦截 return false; } //解析令牌 try { Claims claims = JwtUtils.parseJWT(jwt); //放行 return true; } catch (Exception e) { e.printStackTrace(); //令牌失效 notLoginResponse(response); return false; } } private void notLoginResponse(ServletResponse servletResponse) throws IOException { //返回统一Not_LOGIN结果给前端 Result errorResult=Result.error("Not_LOGIN"); //怎么把errorResult对象转换未JOSN对象 //java 对象 JOSN之间的映射 ObjectMapper objectMapper = new ObjectMapper(); //把java对象变成josn字符串 String json = objectMapper.writeValueAsString(errorResult); //返回到前端 servletResponse.getWriter().write(json); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
异常处理
两种方案:
1.Controller 层进行异常捕捉;
2.全局异常处理
创建异常包 创建类接入@ExceptionHandler(Exception.class) @Slf4j 实现方法
@Slf4j public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result ex(Exception ex) { log.error("全局处理异常"+ex); return Result.error("系统繁忙,请稍后重试..."); }