Json web token (JWT)(网络令牌),
是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
三个部分组成
- 页眉 header
- 有效载荷 playload
- 签名(标签)signature
页眉 标头通常由两部分组成:令牌的类型(即 JWT)和正在使用的签名算法(如 HMAC SHA256 或 RSA)。
jwt用于单点登录
单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
如何实现
@Autowired private StringRedisTemplate stringRedisTemplate; //写配置类 进行序列化 解决转义字符问题 @RequestMapping("login") public JsonResultBean login(User user, HttpServletResponse response) { if ("aa".equals(user.getUsername()) && "11".equals(user.getPassword())) { //用map集合更好的传出 HashMap<String, String> map = new HashMap<>(); map.put("username", user.getUsername()); map.put("password", user.getPassword()); String createjwt = JwtUtil.createjwt(map); //设置请求头 也可以放在参数中 response.setHeader("token", createjwt); //放到redis中 stringRedisTemplate.opsForValue().set(JWtPrex.JWT_PREX + user.getUsername(), createjwt); JsonResultBean ok = JsonResultBean.ok(); return ok; } else { JsonResultBean error = JsonResultBean.error(); return error; }
配置filter 拦截请求 实现 implements Filter
@Autowired private StringRedisTemplate stringRedisTemplate; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if (req.getRequestURL().toString().contains("login")){ chain.doFilter(request,response); return; } String token = req.getHeader("token"); if (token !=null){ DecodedJWT verifyJwt = JwtUtil.verifyJwt(token); String username = verifyJwt.getClaim("username").asString(); String redisToken = stringRedisTemplate.opsForValue().get(JWtPrex.JWT_PREX + username);//加前缀 if (redisToken.equals(token)){ chain.doFilter(request,response); return; } }else { JsonResultBean baseResultBean = new JsonResultBean(); baseResultBean.setCode("401");//未认证 baseResultBean.setMsg("未登录"); //返回数据 response.setContentType("application/json;charset=utf-8"); response.getWriter().write(new ObjectMapper().writeValueAsString(baseResultBean)); }
配置工具类
private static final String signature = "jwt-password";//标签 public static String createjwt(Map<String ,String> map) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.SECOND, 1800); JWTCreator.Builder builder = JWT.create(); map.forEach((k,v)->{builder.withClaim(k,v); }); String token= builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(signature))//设置标签的编码格式 ; return token; } //校验令牌是否正确 public static DecodedJWT verifyJwt(String token){ JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(signature)).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); return decodedJWT; }
设置 jwtfilter config
@Bean public FilterRegistrationBean JwtFilter(JWTFilter jwtFilter){ //配置类对象 FilterRegistrationBean filterRegistrationBean =new FilterRegistrationBean(); //配置对象 filterRegistrationBean.setFilter(jwtFilter); //设置名字 filterRegistrationBean.setName("JwtFilter"); //设置顺序,正整数值越小,优先级越高 filterRegistrationBean.setOrder(1); //设置拦截路径 filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; }
验证完成
过程 jwt 登录验证令牌 登录成功后 将会生产令牌 只有带着令牌才能往下走 登录一次可以多次登录
HttpServletRequest 继承servletrequest
HTTP协议有7中请求方式,常用的有2种
-
GET:
-
地址栏会跟上参数数据,以?开头,多个参数之间以&分割
-
GET提交参数数据有限制,不超过1KB
-
GET方式不适合提交敏感数据
-
浏览器直接访问的请求,默认提交方式是GET方式
-
POST:
-
参数不会跟着地址栏后面,参数而是跟在请求的实体内容中
-
POST提交的参数数据没有限制
-
POST方式提交敏感数据相对安全
请求体:即请求的内容,也就是页面参数
URL: 统一资源定位符 : http://localhost/servlet/demo1 URI:统一资源标识符 : /servlet/demo1
request.getMethod(); 获取请求方式
request.getRequestURI() /
request.getRequestURL() 请求资源
request.getProtocol() 请求http协议版本
request.getHeader("名称") 根据请求头获取请求值
request.getHeaderNames() 获取所有的请求头名称
request.getContextPath() --得到当前web应用的路径
@Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,@Component(@Controller、@Service、@Repository)通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。
2)@Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。通常方法体中包含了最终产生bean实例的逻辑,并且实例名就是方法名。
FilterRegistrationBean对象第二种
有2种方式可以实现过滤器
1:通过FilterRegistrationBean实例注册
2:通过@WebFilter注解生效
filterRegistrationBean.setOrder(**)进行设置过滤顺序
filterRegistrationBean.setFilter(jwtFilter);//设置过滤对象
//设置拦截路径 filterRegistrationBean.addUrlPatterns("/*");
第二 防止重放攻击
重放攻击是计算机世界黑客常用的攻击方式之一,所谓重放攻击就是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。
首先要明确一个事情,重放攻击是二次请求,黑客通过抓包获取到了请求的HTTP报文,然后黑客自己编写了一个类似的HTTP请求,发送给服务器。也就是说服务器处理了两个请求,先处理了正常的HTTP请求,然后又处理了黑客发送的篡改过的HTTP请求。
三种方法来防止
1. 加随机数
该方法优点是认证双方不需要时间同步,双方记住使用过的随机数,如发现报文中有以前使用过的随机数,就认为是重放攻击。缺点是需要额外保存使用过的随机数,若记录的时间段较长,则保存和查询的开销较大。
2. 加时间戳
该方法优点是不用额外保存其他信息。缺点是认证双方需要准确的时间同步,同步越好,受攻击的可能性就越小。但当系统很庞大,跨越的区域较广时,要做到精确的时间同步并不是很容易。
3. 加流水号
就是双方在报文中添加一个逐步递增的整数,只要接收到一个不连续的流水号报文(太大或太小),就认定有重放威胁。该方法优点是不需要时间同步,保存的信息量比随机数方式小。缺点是一旦攻击者对报文解密成功,就可以获得流水号,从而每次将流水号递增欺骗认证端。
我们用第二种来演示 timestamp filter
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; if (req.getRequestURL().toString().contains("login")){ chain.doFilter(request,response); return; } String timestamp = req.getParameter("timestamp"); System.out.println(timestamp); if (timestamp !=null){ SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date requestTime =null; try { requestTime= simpleDateFormat.parse(timestamp); } catch (ParseException e) { JsonResultBean baseResultBean = new JsonResultBean(); baseResultBean.setCode("5555");//未认证 baseResultBean.setMsg(TimeStampException.SYSTEM_TIME_ERROR); //返回数据 response.setContentType("application/json;charset=utf-8"); response.getWriter().write(new ObjectMapper().writeValueAsString(baseResultBean)); } long currentTime = System.currentTimeMillis(); long requestTimeTime = requestTime.getTime(); System.out.println(currentTime); System.out.println(requestTimeTime); if (currentTime -requestTimeTime > 6000 || currentTime-requestTimeTime<0){ JsonResultBean baseResultBean = new JsonResultBean(); baseResultBean.setCode("222");//未认证 baseResultBean.setMsg("时间戳不对"); //返回数据 response.setContentType("application/json;charset=utf-8"); response.getWriter().write(new ObjectMapper().writeValueAsString(baseResultBean)); }else { chain.doFilter(request,response); return; } }else { JsonResultBean baseResultBean = new JsonResultBean(); baseResultBean.setCode("666");//未认证 baseResultBean.setMsg("未登录"); //返回数据 response.setContentType("application/json;charset=utf-8"); response.getWriter().write(new ObjectMapper().writeValueAsString(baseResultBean)); } }
写config
@Configuration public class TimeStampConfig { @Bean public FilterRegistrationBean TimeStamp(TimeStamp timeStamp){ //配置类对象 FilterRegistrationBean filterRegistrationBean =new FilterRegistrationBean(); //配置对象 filterRegistrationBean.setFilter(timeStamp); //设置名字 filterRegistrationBean.setName("TimeStamp"); //设置顺序,正整数值越小,优先级越高 filterRegistrationBean.setOrder(2); //设置拦截路径 filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; }
在访问方法上加时间戳 超过1分钟这不能访问方法