精华内容
下载资源
问答
  • 四种策略、五种状态六个核心参数、七种阻塞队列 开篇闲扯 其实这篇文章仅仅是多线程的冰山一小角,不论是面试还是实际工作开发,这些都是不够的。还是要多看书本上的知识,个人认为看博客得到的知识点都是盲人摸象...

    四种策略、五种状态、六个核心参数、七种阻塞队列

    开篇闲扯
    其实这篇文章仅仅是多线程的冰山一小角,不论是面试还是实际工作开发,这些都是不够的。还是要多看书本上的知识,个人认为看博客得到的知识点都是盲人摸象,不成体系,这是最可怕的。如果把多线程比作一块拼图的话,那么你看的每一篇(包括我的文章)博客都仅仅是这块拼图中的零散几片,需要很长时间才能得到这个完整的多线程拼图。而当你集齐了多线程拼图的时候又会发现,原来多线程也不过是整个Java生态里的一小块,而Java生态也仅仅是软件开发行业中的产业链之一。

    读专业书籍的好处在于能够快速建立相关知识的体系,少走弯路,降低重复收集知识拼图的可能性。

    扯多了,那么本篇主要内容就是线程池,包括SingleThreadPool、FixedThreadPool、ScheduledThreadPool、CacheThreadPool和WorkStealingThreadPool等不同水池子,其中前4中比较常见,最后一种是在1.8及以后新增的线程池。

    线程池概述
    在开发过程中,我们都知道使用多线程来提升系统的吞吐量,但是无限制的创建线程反而会给系统带来负担,内存分配不足,CPU超负荷等等,而且过多的线程也会导致频繁的线程切换,前面有写过:频繁的上下文切换会导致严重的性能损耗。

    以我们常见的HotSport虚拟机为例,它的线程模型规定Java创建的线程是和计算机内核线程是一对一的,那么Java线程的创建与销毁就对应着内核线程的创建与销毁,而线程创建与销毁设计到内存空间申请、分配等动作,对资源的消耗可想而知。因此,线程池的出现就为让已知任务量的系统中保持着一定数量的工作线程,提升线程复用,控制线程数量,降低线程创建销毁的频率。下面用一张图展示线程池的工作原理:
    在这里插入图片描述
    四种拒绝策略
    策略名解释AbortPolicy丢弃任务并抛出RejectedExecutionException,默认该方式。DiscardPolicy丢弃任务,但是不抛出异常DiscardOldestPolicy丢弃workQueue中最前面的任务,然后重新尝试执行任务CallerRunsPolicy由调用线程自己处理该任务

    五种线程池状态
    状态名解释running该状态的线程池能够接受新任务,并对新添加的任务进行处理shutdown该状态的线程池不再接受新任务,但是会把阻塞队列中的任务全部执行完stop该状态的线程池不再接收新任务,不处理阻塞队列的任务,并且中断正在处理的任务tidying当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态terminated线程池彻底终止的状态

    通过源码看一下线程池状态在代码中是如何体现的:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //表示一个int型占用32位,减去3位表示29位
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
    //将-1左移29位后变成111
    private static final int RUNNING    = -1 << COUNT_BITS;
    //将0左移29位后是000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //将0左移29位后是001
    private static final int STOP       =  1 << COUNT_BITS;
    //将0左移29位后是010
    private static final int TIDYING    =  2 << COUNT_BITS;
    //将0左移29位后是011
    private static final int TERMINATED =  3 << COUNT_BITS;
    

    通过源码看到,通过ctl的高3位记录了线程池的运行状态,而低29位则记录了线程池中的任务数量。
    六个核心参数
    参数名参数说明corePoolSize(核心线程数)核心线程数量,线程池维护线程的最少数量workQueue(任务等待队列)保存等待执行任务的阻塞队列maximumPoolSize(线程池线程数最大值)线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务keepAliveTime(线程存活时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数threadFactory(线程工厂)用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。handler(线程饱和策略)当线程池和队列都满了,再加入线程会执行此策略。

    七种阻塞队列
    队列名数据结构解释ArrayBlockingQueueArrayList由数组结构组成的有界阻塞队列LinkedBlockingQueueLinkList由链表结构组成的无界阻塞队列PriorityBlockingQueueheap支持优先级排序的无界阻塞队列DealyQueueheap使用优先级队列实现的无界阻塞队列SynchronousQueue无不存储元素的阻塞队列LinkedTransferQueueheap由链表结构组成的无界阻塞队列LinkedBlockingDequeheap由链表结构组成的双向阻塞队列

    阻塞队列这块东西特别多,在并发包里的地位也很高,就这七个队列如果想写的很详细够写七篇文章的,这里就不仔细说了,因为我也还没研究他们的实现,暂时也只是略有了解…记在小本本上了,我会补上的。
    五种常见线程池
    SingleThreadPool
    先看一下它的构造方法:

    // 重点在阻塞队列上
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    

    把它上面的注释翻译过来就是“创建一个使用单个工作线程的执行器,同时它的队列是无界的。(但是请注意,如果这个单个线程在关闭之前的执行过程中意外终止,则在需要执行后续任务时,将替换一个新的线程。)任务被保证按顺序执行,并且在任何给定的时间内不会有多个任务处于活动状态”还特别强调了:Unlike the otherwise equivalent newFixedThreadPool(1),有兴趣可以研究一下源码,但其实没必要。

    演示一下基本用法:

    /**
     * FileName: SingleThreadPool
     * Author:   苏州程序大白
     * Date:     2021/4/17 9:00 
     * Description: 但线程的线程池
     */
    public class SingleThreadPool {
        public static ExecutorService singleThreadExecutor;
    
        static {
            singleThreadExecutor = Executors.newSingleThreadExecutor();
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                final int temp = i;
                singleThreadExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        Thread.currentThread().setName(String.valueOf(temp));
                        System.out.println("线程 "+Thread.currentThread().getName()+" 开始执行...");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            singleThreadExecutor.shutdown();
            System.out.println("===========Main Thread Over!=========");
        }
    }
    

    大家可以尝试运行一下,能够看到所有线程都是顺序执行的,SingleThreadExecutor是一个单线程的线程池,如果当前线程意外终止,线程池会创建一个新线程继续执行任务,以保证任务能够完成。

    不知道你们通过这个例子有没有发现问题,其实这么创建线程池,是有把服务搞死的风险,通过源码可以看到构造方法中用到的任务等待队列是LinkedBlockingQueue,理论上说它是一个无线队列,最大值为Integer.MAX_VALUE,就有OOM的风险。因此,在使用时,如果没有特殊需求,还是使用ThreadPoolExecutor进行创建线程池,可以自定义线程池的任意参数。

    FixedThreadPool
    先看一下它的构造方法:

    // 指定线程数
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    // 执行线程数和线程工厂类
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
    

    基础用法同上面的类似,只是初始化线程池用到的API不同

    public class FixedThreadPool {
        public static ExecutorService fixedThreadPool;
    
        static {
            //初始化固定核心线程数的线程池
            fixedThreadPool = Executors.newFixedThreadPool(5);
        }
    
        public static void main(String[] args) throws Exception {
            for (int i = 0; i < 20; i++) {
                final int temp = i;
                fixedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        Thread.currentThread().setName(String.valueOf(temp));
                        try {
                            System.out.println("线程 " + Thread.currentThread().getName() + " 开始...");
                            Thread.sleep(1000);
                            System.out.println("线程 " + Thread.currentThread().getName() + " 结束...");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            System.out.println("*********************");
            fixedThreadPool.shutdown();
            System.out.println("===========Main Thread Over!=========");
        }
    }
    

    ScheduledThreadPool
    创建一个周期线程池,支持定时周期性执行线程池中的任务。

    上构造:

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }
    

    可以看出不同于其他线程池的点是使用了延迟工作队列,在源码中也增加了两个定时执行的方法:

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) {}
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit) {}
    

    简单举个例子:

    public class ScheduledThreadPool {
        public static ScheduledExecutorService scheduledThreadPool;
    
        static {
            //初始化固定核心线程数的线程池
            scheduledThreadPool = Executors.newScheduledThreadPool(5);
        }
    
        public static void main(String[] args) throws Exception {
            for (int i = 0; i < 5; i++) {
                final int temp = i;
                scheduledThreadPool.scheduleWithFixedDelay(new Runnable() {
                    @Override
                    public void run() {
                        Thread.currentThread().setName(String.valueOf(temp));
                        try {
                            System.out.println("线程 " + Thread.currentThread().getName() + " 每隔10秒执行一次...");
                            Thread.sleep(500);// 1
                            System.out.println("线程 " + Thread.currentThread().getName() + " 结束...");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, 3, 10, TimeUnit.SECONDS);
            }
    
            System.out.println("===========Main Thread Over!=========");
        }
    }
    

    这里演示了一种用法,即:线程池启动3S以后开始执行任务,每隔10S将这5个任务都执行一遍。如果将代码 1 位置处的500ms改成11s呢?这时候设置的每隔10s周期性执行就会失效,可以复制到自己本地运行看看现象,因此使用这类线程池还是要注意每个任务的处理耗时,合理评估间隔时间。感兴趣的还可以自己尝试使用scheduleAtFixedRate()的方法以及其他的使用方式。

    再分享一个小Tip:可以考虑多线程池嵌套使用,合理利用Executors下默认线程池的特性。

    CacheThreadPool
    带有缓存的线程池,如果线程池长度超过处理需要,线程池可以灵活回收空闲线程,若无可回收线程,就创建新线程。

    构造方法:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    //可自定义线程工厂类构造
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
    

    通过构造器可以看出,可以自定义线程的工厂类,而且跟其他几个默认的线程池区别在于阻塞队列的不同,也有个同样致命的缺点:Integer.MAX_VALUE,存在OOM的风险。

    简单示例:

    public class CachedThreadPool {
        public static ExecutorService cachedThreadPool;
    
        static {
            //初始化带有缓存的线程池
            cachedThreadPool = Executors.newCachedThreadPool();
        }
    
        public static void main(String[] args) throws Exception {
            for (int i = 0; i < 5; i++) {
                final int temp = i;
                cachedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        Thread.currentThread().setName(String.valueOf(temp));
                        try {
                            System.out.println("线程 " + Thread.currentThread().getName() + " 开始...");
                            Thread.sleep(2000);
                            System.out.println("线程 " + Thread.currentThread().getName() + " 结束...");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
            System.out.println("===========Main Thread Over!=========");
        }
    }
    

    到这里一共介绍了4种线程池的构造和基础用法,但是这些默认构造在极端情况下都存在风险,这种使用Executors返回的线程池对象风险如下:

    1.FixedThreadPool和SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,导致OOM
    2.CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,导致 OOM。
    

    还是那句话,没有特殊需求,就老老实实的按规格里的来写:

    new ThreadPoolExecutor(“核心线程数”, “最大线程数”, “空闲线程存活时间”, “时间单位”, “阻塞队列类型”);
    

    所有的参数都可以自定义,不过也不能设计的太洒脱,还是要根据业务场景合理设置,那怎么叫合理呢?简单!分两个场景介绍:

    1.CPU(计算)密集型

    假设CPU核心数为N,那么一般可以将核心线程数设置为N+1个,可以充分利用CPU的资源,且降低了CPU单个核心的上下文切换的频率。至于为什么要比CPU多设置一个线程,是因为防止某个时间点线程意外终止,此时多设置的这个线程就可以顶上了。

    上demo证明一下这个理论对不对,不对的话,我就去找一下Doug Lea先生,跟他battle一下

    public class ThreadPoolTest {
        // 初始一个线程池
        private static ThreadPoolExecutor threadPool;
    
        //计算线程池运行总时长
        private static Vector<Long> threadPoolRunTime;
    
        //单个线程运行市场
        private static Vector<Long> singleThreadRunTime;
    
        static {
            // 用于获取当前硬件设备的CPU核心数
            int coreNum = Runtime.getRuntime().availableProcessors();
            System.out.println("当前设备CPU核心数:" + coreNum);
    
            // 这里是重点,通过修改核心线程数和最大线程数,来观察任务执行耗时变化,即可证明前面阐述的理论
            threadPool = new ThreadPoolExecutor(coreNum + 1, coreNum + 1, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000), new ThreadPoolExecutor.DiscardPolicy());
            threadPoolRunTime = new Vector<Long>();
            singleThreadRunTime = new Vector<Long>();
        }
    
        public static void main(String[] args) throws Exception {
            List<Future<?>> futureTaskList = new ArrayList<Future<?>>();
            //运行总次数可以不修改
            int testNum = 100;
            for (int i = 0; i < testNum; i++) {
                //提交一个计算密集型任务,并计算单个任务总耗时
                Future<?> future = threadPool.submit(new TestCPU(singleThreadRunTime, threadPoolRunTime));
    
                //提交一个IO密集型任务,并计算单个任务总耗时
                //Future<?> future = threadPool.submit(new TestIO(singleThreadRunTime, threadPoolRunTime));
                futureTaskList.add(future);
            }
    
            for (Future<?> future : futureTaskList) {
                //获取线程执行结果
                future.get(testNum, TimeUnit.SECONDS);
            }
    
            System.out.println("运行总耗时: " + getTime(threadPoolRunTime) / threadPoolRunTime.size() + " ms");
            System.out.println("单个线程平均耗时: " + getTime(singleThreadRunTime) / singleThreadRunTime.size() + " ms");
            threadPool.shutdown();
        }
    
        public static Long getTime(Vector<Long> list) {
            long time = 0;
            for (int i = 0; i < list.size(); i++) {
                time = list.get(i) + time;
            }
            return time;
        }
    }
    
    /**
     * FileName: TestCPU
     * Author:   苏州程序大白
     * Date:     2021/4/17 9:00 
     * Description: 计算密集型任务
     */
    public class TestCPU implements Runnable {
        private static List<Long> threadPoolRunTime;
        private static List<Long> singleThreadRunTime;
        private long startTime = 0;
    
        public TestCPU(List<Long> singleThreadRunTime, List<Long> threadPoolRunTime) {
            startTime = System.currentTimeMillis();
            this.singleThreadRunTime = singleThreadRunTime;
            this.threadPoolRunTime = threadPoolRunTime;
        }
    
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //统计1~100000之间素数的总数
            countPrimes(1, 100000);
            long endTime = System.currentTimeMillis();
    
            long threadPoolTime = endTime - startTime;
            long threadTime = endTime - start;
    
            threadPoolRunTime.add(threadPoolTime);
            singleThreadRunTime.add(threadTime);
    
            System.out.println("当前线" + Thread.currentThread().getName() + "程耗时:" + (endTime - start) + " ms");
        }
    
        /**
         * 判断是否为素数
         */
        public boolean isPrime(final int num) {
            if (num <= 1) {
                return false;
            }
            for (int i = 2; i <= Math.sqrt(num); i++) {
                if (num % i == 0) {
                    return false;
                }
            }
            return true;
        }
    
        /**
         * 计算素数
         */
        public int countPrimes(final int startNum, final int endNum) {
            int count = 0;
            for (int i = startNum; i <= endNum; i++) {
                if (isPrime(i)) {
                    count++;
                }
            }
            return count;
        }
    }
    

    可以复制到自己本地跑一下这个测试类,根据自己本地CPU核心数设置不同的线程池核心数,能够发现执行结果有很大的不同,而效率最高的时候,就是当线程池中线程数与CPU核心数差不多相等的时候,会有差异,但不会太大。

    2.I/O密集型

    这类程序,由于系统大多数时间都是在处理IO交互,在处理IO的时间段内,是不会占用CPU时间片的,因此CPU有能力处理更多的线程。假设W:线程等待IO资源的时间;C:线程运行时间;P:目标CPU使用率。N:CPU数量,那么相对最优线程池大小Core = N * P * (1 + W/C)

    还是创建一个IO密集型的任务,然后批量提交到我们的线程池中做测试:

    /**
     * FileName: TestIO
     * Author:   苏州程序大白
     * Date:     2021/4/17 9:00 
     * Description: IO密集型任务
     */
    public class TestIO implements Runnable {
        private static List<Long> threadPoolRunTime;
        private static List<Long> singleThreadRunTime;
        private long startTime = 0;
    
        public TestIO(Vector<Long> singleThreadRunTime, Vector<Long> threadPoolRunTime) {
            startTime = System.currentTimeMillis();
            this.singleThreadRunTime = singleThreadRunTime;
            this.threadPoolRunTime = threadPoolRunTime;
        }
    
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            try {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 执行IO操作
                readFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            threadPoolRunTime.add(end - startTime);
            singleThreadRunTime.add(end - start);
            System.out.println("当前线程" + Thread.currentThread().getName() + "耗时:" + (end - start) + " ms");
        }
    
        /**
         * IO操作,读取一个本地文件
         */
        public void readFile() throws IOException {
            //自己随便创建一个txt文件用来测试
            File sourceFile = new File("/Users/RollerRunning/Documents/test/IO.txt");
            BufferedReader input = new BufferedReader(new FileReader(sourceFile));
            //按行读取
            String line = null;
            while ((line = input.readLine()) != null) {
                System.out.println(line);
            }
            input.close();
        }
    }
    

    还是百看不如一次自己运行,拷贝一下,自己运行感受一下。这些Demo书里都能找到,也可以百度一下其他的博主写的一些测试案例,都大差不差。
    WorkStealingThreadPool
    还是先来看看这个线程池的构造方法:

    public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    

    这个线程池是JDK1.8以后才新增的基于ForkJoinPool扩展来的,通过构造方法就能看出来,在它的内部是通过new ForkJoinPool()来实现的,而前面四种是通过new ThreadPoolExecutor()再搭配不同的阻塞队列实现的,更细节的东西就不多说了,不是一两篇能写完的。

    那么这个新增的线程池有啥优点呢?优点就是它内部线程会steal work啊,小时候有个梗就是吃零食比谁吃得快,谁先吃完可以抢别人的吃,这个线程池也是这个思想,谁的任务执行完了,就可以去帮助其他线程执行任务了,不过这么说不够准确,先看一张图,然后再详细解释
    在这里插入图片描述
    在这里插入图片描述
    图一是前四种线程池的一个大概的任务分配模型,而图二则是WorkStealingThreadPool线程池的任务分配模式,每一个线程都有一个自己的任务等待队列,也就是自己的零食,当自己的任务执行完了,允许从其他线程的任务队列中获取任务并协助执行。这样就可以提高线程的可用性,提升线程池整体效率。 最后来个简单的示例:

    public class WorkStealThreadPool {
        public static ExecutorService workStealThreadPool;
    
        static {
            //初始化无锁线程池
            workStealThreadPool = Executors.newWorkStealingPool();
        }
    
        public static void main(String[] args) throws Exception {
            int core = Runtime.getRuntime().availableProcessors();
            System.out.println("当前设备CPU核心数为:" + core);
            for (int i = 0; i < 50; i++) {
                FutureTask<?> futureTask = new FutureTask<>(new Callable<String>() {
                    @Override
                    public String call() {
                        try {
                            System.out.println("线程 " + Thread.currentThread().getName() + " 开始...");
                            Thread.sleep(1000);
                            System.out.println("线程 " + Thread.currentThread().getName() + " 结束...");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return "当前线程名称为:"+Thread.currentThread().getName();
                    }
                });
                workStealThreadPool.submit(new Thread(futureTask));
                //System.out.println(futureTask.get());
            }
            System.out.println("===========Main Thread Over!=========");
        }
    }
    
    展开全文
  • 我把日常渗透过程中遇到的案例作了漏洞成因分析,这次,关注因重置...密码找回流程一般包括获取短信验证码、校验短信验证码是否有效、设置新密码等三步骤。在第二步,校验短信验证码是否有效的结果应保存在服务端...

    前言

    在逻辑漏洞中,任意用户密码重置最为常见,可能出现在新用户注册页面,也可能是用户登录后重置密码的页面,或者用户忘记密码时的密码找回页面,其中,密码找回功能是重灾区。我把日常渗透过程中遇到的案例作了漏洞成因分析,这次,关注因重置凭证可暴破导致的任意用户密码重置问题。

    密码找回流程一般包括获取短信验证码、校验短信验证码是否有效、设置新密码等三个步骤。在第二步,校验短信验证码是否有效的结果应保存在服务端,某些网站未在服务端保存而是错误地将结果状态值下发客户端,后续又依靠前端 js 判断是否可以进入第三步,那么,更改应答包中的状态值,可重置其他用户的密码。

    案例一

    在密码找回页面 http://www.xx.cn/yy/action/forgot 用攻击者手机号 13908081024 进入密码找回全流程,获取短信验证码 033128、输入图片验证码、输入短信验证码并提交:

    服务端校验通过后,系统应答如下:


    简单分析发现,校验通过时服务端并未向客户端 set-cookie,猜测服务端并未记录校验状态,是否进入设置新密码页面完全是由前端 js 基于应答状态决定的,那么,即便我没有短信验证码,通过将服务端下发给客户端的校验状态从“失败”改为“成功”,也能成功重置找回账号密码。

    具体而言,以信息搜集时找到的客服手机号 13980808888 为例。输入手机号、获取短信验证码、输入图片验证码、输入错误的短信验证码 123123 后提交:


    由于短信验证码错误,系统校验肯定失败,系统应答如下:


    拦截该应答,用前面抓取校验成功的应答包替换之:


    放行至客户端,顺利进入新密码设置页面:



    输入新密码 PenTest1024 后提交,页面提示密码修改成功:



    尝试用 13980808888/PenTest1024 登录,验证成功


    案例二

    在密码找回页面 http://www.xx.cn/yy/forgot 用攻击者手机号 13908081024 进入密码找回全流程,获取短信验证码 2118、输入短信验证码并提交:


    服务端校验通过后,系统应答如下:


    简单分析发现,校验通过时服务端并未向客户端 set-cookie,将服务端下发给客户端的校验状态 code 改为“0000”,可以重置其他用户密码。

    具体而言,以土豪手机号 13888888888 为例。输入手机号、获取短信验证码、输入错误的短信验证码 1234 后提交。由于短信验证码错误,服务端校验失败,应答如下:


    拦截该应答,用前面抓取校验成功的应答包替换之后,放行至客户端,顺利进入新密码设置页面:


    输入新密码 PenTest1024 后提交,页面提示密码修改成功。尝试用 13888888888/PenTest1024 登录,验证成功:


    加固措施

    服务端校验短信验证码后应通过 cookie 记录状态,不应在前端通过状态参数判断。另外,服务端应限制枚举等恶意请求。


    展开全文
  • 线程的一生-6个状态

    2021-01-12 12:07:28
    六个状态分别是 New Runnable 调用了start,就是runnable,对应操作系统的 ready,running Blocked synchronized所保护,且没有锁。 Waiting Object.wait() 无参数 Timed waiting 有参数 Terminated ...

    线程的一生-6个状态

    六个状态分别是

    • New
    • Runnable
      • 调用了start,就是runnable,对应操作系统的 ready,running
    • Blocked
      • synchronized所保护,且没有锁。
    • Waiting
      • Object.wait() 无参数
    • Timed waiting
      • 有参数
    • Terminated
      • 正常、不正常

    线程的6个状态

    阻塞状态

    • 一般习惯而言,把blocked、waiting、timed_waiting 统称为阻塞状态
    展开全文
  • 在我们的opms项目管理中,有一项目状态的修正,四种状态分别为延期,挂起,进行,结束 在接口自动化案例中,我们需要对这四种状态进行遍历,然后进行断言,判断状态是否正确修改。 我们通...

    概述

    今天用jmeter做一次参数化实战。通过计数器遍历参数表,然后查询jdbc进行beanshell断言。

    涉及元件

    用户参数,计数器,正则表达式,jdbc,beanshell脚本

    详细过程

    在我们的opms项目管理中,有一个项目状态的修正,四种状态分别为延期,挂起,进行,结束

     

    在接口自动化案例中,我们需要对这四种状态进行遍历,然后进行断言,判断状态是否正确修改。

    我们通过抓接口,发现了传参的一个规律:除了项目id之外,四种状态的status分别是1,2,3,4。

     

     

     我们把添加项目的返回id用正则表达式提取出来,作为项目状态接口的入参,参数名称为projectid,这个是主键id

     

    现在思考一下,接下来的四种状态,你要如何去操作才能全部执行一次并断言结果???

    通常的做法是写四个接口,传四种参数,再添加四个断言,但是这种做法会造成大量的脚本冗余,在接口自动化中是会被鄙视的!如下:

     

    现在教大家用精简的脚本完成复杂的逻辑校验

     

    添加一个循环控制器,让它循环四次,可以遍历四种状态

     

     

     

    添加一个用户参数,在参数表中配置四种项目状态

     

     

    添加一个计数器,上限为4,递增1,用来遍历参数表中的四种状态

     

     

    添加一个http请求,在名称中遍历状态名;在入参中遍历状态值

     

    添加一个jdbc请求,通过变更的状态值和项目id组合查询的方式,验证每一次状态的变更是否正确的写入了数据库

     

    我们在接口执行了之后,还需要去前端页面判断一下状态名称是否真的被修改了,在这里用到了xpath表达式去提取前端页面数据

    添加一个beanshell断言,判断我们数据库返回值是否为空,如果不为空且和前端页面返回的状态名称,表示断言通过,否则断言失败

     

    运行脚本,在结果树中和log日志中观察执行效果

    遍历了四种状态并对实际运行结果进行了断言

     

     

    关注公众号,获取更多精彩文章!

    点击获取《jmeter接口自动化与性能实战-飞天小子.pdf》

     

    转载于:https://www.cnblogs.com/Zfc-Cjk/p/10851112.html

    展开全文
  • 线程是 CPU 调度的基本单位。... /* param1: runnable 任务,param2: 线程访问控制上下文,调用六个参数的 init(),此构造没有对外开放 */ Thread(Runnable target, AccessControlContext acc) { .
  • 允许一对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。 我们的程序中很多情况下是判断一个参数的值,进行一些其他操作。这些情况必须依次过滤直到符合条件的时候,执行具体逻辑。 二、实例...
  • 章 退出和退出状态 ...当一脚本不带参数exit来结束时,脚本的退出状态由脚本中最后执行的命令来决定 $? 读取最后执行命令的退出码。函数返回后,$?给出函数最后执行的那条命令的退出码。这种给
  • 1 o.act 输出方向设定 o.=========output direction 输出方向 act=actuator 执行 rev 逆操作 revert的缩写 v. 恢复,回复(到以前的状态、制度或行为);回到,重提,重新考虑(某个...举例子 运行状态下,实际温...
  • 六个基本对象

    2011-05-04 23:09:00
    servletContext:每应用对应一servletContext,数据可以通过这对象实现在所有servlet之间的共享,即数据的共享是通过它来实现的。 servletConfig:servlet初始化时所有的状态参数封装成的一对象
  • 16:至少包含一标点符号(英文输入法状态下,除―和空格外的所有符号;若为其他数字,则表示配置值的和,如 3=1+2,表示同时启用第 1 项和第 2 项策略。当COMPATIBLE_MODE=1 时,PWD_POLICY 的实际值均为 0 2.12.2
  • systemctl status sshd.service 我们可以知道sshd service的pid,比如是2366,可以用ps查看一下。 ps -up 2366 在我的机器上,目前它是active的,...如果我们要开始这service,用start参数 systemctl star...
  • 我们在assets里创建一Animator Charactercontrol,命名为Character,这就是我们Hero的组件Animator需要添加的controller。  首先我们在Base Layer创建五State(Idle,Run... Clip赋予到Motion参数,然后再左下
  • 需求背景 在实际服务端API接口项目开发过程中,会有一些项目约定规范用法Tips,这次整理分享一下我过去使用过的,希望对你有用 ... 一接口返回时,无论内部是返回成功、失败、异常等,都统一返回了http状态码...
  • java的线程状态概述

    2020-11-10 17:13:01
    在API中java.lang.Thread.State这枚举中给出了种线程状态:这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解 线程状态 导致状态发生条件 NEW(新建) 线程刚被创建,但是并未启动。还没...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 749
精华内容 299
关键字:

六个状态参数