会话的跟踪技术
1.客户端会话跟踪
优点:http协议中支持的技术
缺点:
-
移动端APP无法使用Cookie
-
Cookie存储在浏览器硬盘,用户可以禁用,不安全
-
Cookie无法跨域
流程:
-
用户登录后,服务器自动返回一个cookie响应给浏览器
-
浏览器接受到响应回来的数据之后,会自动的将cookie存储在浏览器本地
-
在后续的请求中,浏览器会自动的将cookie携带到服务器端
区分跨域的纬度:协议、IP/域名/端口号
实现代码
//设置cookie @RequestMapping("/setcookie") public Result setcookie(HttpServletResponse response){ response.addCookie(new Cookie("username","ffy")); return Result.success(); } //获取cookie @RequestMapping("/getcookie") public Result getcookie(HttpServletRequest request){ Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { log.info("getcookie: "+cookie.getName()); } return Result.success(); }
2.服务端会话跟踪
优点:存储在服务器端,安全
缺点:
-
在服务器集群环境下无法使用session
-
对Cookie的依赖程度过高。Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了。
流程:
-
浏览器在第一次请求的时候,服务器会自动生成一个session ,每一个绘画对象session都有一个sessionID,服务器将sessionID响应给浏览器(以JSSESSIONID=xxxx的形式)
-
浏览器会将SessionID存储到本地并在每一次访问时都携带
-
当浏览器后续发出请求时,服务器会根据浏览器携带的SessionID查询其对应的会话
实现代码:
@RequestMapping("/getSession") public Result getSession(HttpServletRequest request){ //获取当前请求的session对象 HttpSession session = request.getSession(); log.info("This is getSession:"+session.hashCode()); //往session中获取数据 Object loginUser = session.getAttribute("psw"); log.info("This is getSession:"+loginUser); return Result.success(loginUser); } @RequestMapping("/setSession") public Result setSession(HttpSession session){ log.info("从Session中读取到的数据:"+session.hashCode()); //往session中存储数据 session.setAttribute("psw","xiaobai"); return Result.success(); }
3.令牌技术
优缺点
优点
-
支持PC端、移动端
-
解决集群环境下的认证问题
-
减轻服务器的压力
缺点:需要手动配置
JWT
概念:
-
全称Json Web Token(官网:JSON Web Tokens - jwt.io),是一种简洁的、自包含的格式。
组成
-
第一部分:header(头),用来记录令牌类型、签名算法等。
-
第二部分:Payload(有效载荷),携带一些自定义的信息、默认信息等。
-
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
使用场景
-
登录认证
-
浏览器发情请求来执行登录操作,这时会访问登录的接口,如果登录成功后,我们就会生成一个jwt令牌,将生成的jwt令牌返回给前端
-
前端拿到jwt令牌之后,就会将jwt令牌存储起来。在后续的每一次请求中都会将jwt令牌携带到服务端
-
服务端统一拦截请求后,先来判断一下这次请求有没有把令牌带过来,如果没有带过来,直接拒绝访问,如果带过来了,还要校验一下令牌是否有效。如果有效,就直接放行进行请求的处理
-
JWT的登录认证操作
-
生成令牌
-
导入Jwt依赖包
<!-- JWT依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
-
使用工具类:Jwts生成JWT字符串
@Test public void testJwt(){ //设置有效载荷 Map<String,Object> map = new HashMap<>(); map.put("username","小明"); map.put("password",123456); //生成jwt令牌 String itcast = Jwts.builder().setClaims(map). signWith(SignatureAlgorithm.HS256, "itcast").//设置秘钥 setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)).//设置有效时间 compact(); System.out.println(itcast); }
-
-
校验令牌:在每一次请求当中,要接受令牌并对令牌进行校验
-
解析生成的令牌
@Test public void parseJwt(){ Claims claims = Jwts.parser().setSigningKey("itcast").//指定签名秘钥(要和生成米秘钥保持一致) parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJwYXNzd29yZCI6MTIzNDU2LCJleHAiOjE2ODU0MzIzNzgsInVzZXJuYW1lIjoi5bCP5piOIn0.2lqj3kKWGmRpSEbzijXXss7FPT49iLQ78OVUikNRNwc").getBody(); System.out.println(claims); }
-
测试令牌过期时间
-
JWT集成用户登录
JwtUtils
package com.itcast.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Date; import java.util.Map; @Component @Slf4j public class JwtUtils { //设置密钥 private static String signKey = "itcast"; //设置有效时间 private static Long expire = System.currentTimeMillis()+1000*60*30; public static String setJwt(Map<String,Object> claims){ //生成jwt令牌 String jwt = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(expire)).compact(); return jwt; } //校验jwt public static Claims parseJwt(String jwt){ Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody(); return claims; } }
EmpController登录生成JWT
@PostMapping("/login") public Result Login(@RequestBody Emp emp){ Boolean flag = empService.login(emp); if(flag==null){ return Result.error("用户名或密码有误"); } Map<String,Object> claim = new HashMap<>(); claim.put("username",emp.getUsername()); claim.put("id",emp.getId()); String claims = JwtUtils.setJwt(claim); return Result.success(claims); }
过滤器-Filter
概念
-
过滤器是JavaWeb的三大组件(Servlet/Filter/Listener)之一
-
过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能,使用了过滤器之后,想要访问web服务器上的资源,必须先经过过滤器,过滤器处理完毕之后,才能访问对应的资源
-
过滤器一般完成一些通用的操作,比如:登录校验、统一编码、敏感字符处理等
过滤器的基本使用操作
-
第一步,定义过滤器
-
定义一个类,实现Filter接口,并重写其所有方法
-
-
第二步,配置过滤器
-
Filter类加@WebFilter注解,配置拦截资源的路径。引导类加@ServletComponentScan开启Servlet组件支持
-
-
代码实现:
-
定义接口实现类ABCFilter
package com.itcast.filter; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; @WebFilter(urlPatterns = "/*") public class ABCFiler implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init()......"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("doFilter()......"); } @Override public void destroy() { System.out.println("destroy()......"); } }
-
配置Application
package com.itcast; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; @SpringBootApplication @ServletComponentScan public class Web01Application { public static void main(String[] args) { SpringApplication.run(Web01Application.class); } }
-
Filter拦截路径的方式
-
拦截具体路径
-
/login
-
只访问/login的路径才会被拦截
-
-
目录拦截
-
/emps/*
-
访问/emps下的所有资源都会被拦截
-
-
拦截所有
-
/*
-
访问所有资源都会被拦截
-
Filter过滤器链的执行顺序及流程
顺序
-
优先级是按照过滤器类名(字符串)的自然排序
流程
只存在一个Filter的情况
-
浏览器向服务器发出请求
-
放行前逻辑
-
放行
-
资源
-
放行后逻辑
-
服务器给浏览器返回响应
存在多个Filter的情况
-
浏览器向服务器发出请求
-
Filter1放行前逻辑
-
Filter1放行
-
Filter2放行前逻辑
-
Filter2放行
-
Filter3放行前逻辑
-
Filter3放行
-
Web资源
-
Filter3放行后逻辑
-
Filter2放行后逻辑
-
Filter1放行后逻辑
-
服务器给浏览器返回响应
登录校验-Filter
实现具体流程
-
获取请求url
-
判断请求url中是否包含login,如果包含,说明是登录操作,放行
-
获取请求头中的令牌(token)
-
判断令牌是否存在,如果不存在返回错误结果(未登录)
-
解析token,如果解析失败,返回错误结果(未登录)
-
放行
代码实现
1.获取请求url
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; String url = request.getRequestURL().toString(); }
2.判断请求url中是否包含login,如果包含,说明是登录操作,放行
//判断请求路径是否是登录路径 if(url.contains("/login")){ filterChain.doFilter(request, response); return; }
3.获取请求头中的令牌(token)
//获取请求头中的令牌信息 String token = request.getHeader("token"); log.info("从请求头中获取的令牌:{}",token);
4.判断令牌是否存在,如果不存在返回错误结果(未登录)
//判断令牌是否存在,如果不存在则返回错误结果 if(!StringUtils.hasLength(token)){ log.info("Token不存在"); Result responseResult = Result.error("NOT_LOGIN"); //把Result对象转换为JSON格式字符串 String json = JSONObject.toJSONString(responseResult); response.setContentType("application/json;charset=utf-8"); //将json字符串返回给页面 response.getWriter().write(json); return; }
5.解析token,如果解析失败,返回错误结果(未登录)
//解析token,如果解析失败,则返回错误结果 try { JwtUtils.parseJwt(token); }catch (Exception e){ log.info("令牌解析失败"); Result responseResult = Result.error("NOT_LOGIN"); String json = JSONObject.toJSONString(responseResult); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); return; }
6.放行
//放行 filterChain.doFilter(request,response);
完整代码:
LoginCheckFilter
package com.itcast.filter; import com.alibaba.fastjson.JSONObject; import com.itcast.pojo.Result; import com.itcast.utils.JwtUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; 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 { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("init()......"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)servletRequest; HttpServletResponse response = (HttpServletResponse)servletResponse; String url = request.getRequestURL().toString(); log.info("请求的路径"+url); //判断请求路径是否是登录路径 if(url.contains("/login")){ filterChain.doFilter(request, response); return; } //获取请求头中的令牌信息 String token = request.getHeader("token"); log.info("从请求头中获取的令牌:{}",token); //判断令牌是否存在,如果不存在则返回错误结果 if(!StringUtils.hasLength(token)){ log.info("Token不存在"); Result responseResult = Result.error("NOT_LOGIN"); //把Result对象转换为JSON格式字符串 String json = JSONObject.toJSONString(responseResult); response.setContentType("application/json;charset=utf-8"); //将json字符串返回给页面 response.getWriter().write(json); return; } //解析token,如果解析失败,则返回错误结果 try { JwtUtils.parseJwt(token); }catch (Exception e){ log.info("令牌解析失败"); Result responseResult = Result.error("NOT_LOGIN"); String json = JSONObject.toJSONString(responseResult); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); return; } //放行 filterChain.doFilter(request,response); } @Override public void destroy() { System.out.println("destroy()......"); } }
JwtUtils
package com.itcast.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.Date; import java.util.Map; @Component @Slf4j public class JwtUtils { //设置密钥 private static String signKey = "itcast"; //设置有效时间 private static Long expire = 1000*60*30L; public static String setJwt(Map<String,Object> claims){ //生成jwt令牌 String jwt = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, signKey).setExpiration(new Date(System.currentTimeMillis()+expire)).compact(); return jwt; } //校验jwt public static Claims parseJwt(String jwt){ Claims claims = Jwts.parser().setSigningKey(signKey).parseClaimsJws(jwt).getBody(); return claims; } }