• 简单创建一个SpringCloud2021.0.3项目(二)



    1. 项目说明

    当前这篇教程是:
    1. 抽取公共模块common,集成redis,虽然只有几个工具类和redis
    2. 新建Gateway网关,集成Security,做登陆和资源权限控制
    3. 前端登陆,做了2种方式。用户、密码、验证码;邮箱、验证码、图片滑块;并且前端加密传给后端解密;登陆异常次数限制
    4. 在分布式系统中基于Token的身份验证
    5. 每次请求刷新用户会话有效时间
    6. 通过AOP方式动态切换数据源

    简单创建一个SpringCloud2021.0.3项目(一)
    简单创建一个SpringCloud2021.0.3项目(二)
    简单创建一个SpringCloud2021.0.3项目(三)
    简单创建一个SpringCloud2021.0.3项目(四)

    1. 版本

    1. SpringCloud版本为2021.0.3
    2. SpringBoot版本为2.7.2

    2. 用到组件

    1. 注册中心:暂时用Eureka,后面再改成Nacos
    2. 网关:Gateway
    3. 权限:Security,Gateway集成
    4. 负载均衡:LoadBalancer,SpringCloud2020版之后就集成LoadBalancer
    5. 限流、熔断降级:Sentinel
    6. 配置中心:暂时用Config,后面改成Nacos
    7. 服务间访问:Feign

    3. 功能

    1. 项目最基本功能,权限控制,在分布式系统中基于Token的身份验证。
    2. 前端登陆,做了2种方式。用户、密码、验证码;邮箱、验证码、图片滑块;并且前端加密传给后端解密;登陆异常次数限制;
    3. 限流、负载均衡,应对高并发情况,降低系统负载;
    4. 服务熔断降级:避免系统雪崩,提高系统可用性;
    5. 两种方式的多数据源,一种是通过AOP方式动态切换数据源,另一种是不同数据源管理的数据各不相同;
    6. 日志系统Logback,是SpringBoot默认集成

    2. 上一篇教程

    简单创建一个SpringCloud2021.0.3项目(一)

    1. 新建Eureka注册中心
    2. 新建Config配置中心,producerService服务读取参数
    3. 2个业务服务(producerService和webService),webService通过Feign调用producerService的服务
    4. webService用到多数据源,不同的数据源管理不同的数据

    3. 创建公共模块Common

    1. 创建操作
      image

    2. 修改pom.xml文件
      image
      image

    点击查看代码
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>SpringCloud202208</artifactId>
            <groupId>com.xiaostudy</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>common</artifactId>
    
        <dependencies>
            <!-- SpringBoot Boot Redis -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
            <!-- JSON 解析器和生成器 -->
            <dependency>
                <groupId>com.alibaba.fastjson2</groupId>
                <artifactId>fastjson2</artifactId>
                <version>2.0.12</version>
            </dependency>
        </dependencies>
    
    </project>
    
    1. redis序列化、配置类、工具类
      image
    序列化
    package com.xiaostudy.common.redis;
    
    import com.alibaba.fastjson2.JSON;
    import com.alibaba.fastjson2.JSONReader;
    import com.alibaba.fastjson2.JSONWriter;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.SerializationException;
    
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    
    /**
     * Redis使用FastJson序列化
     */
    public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    
        private Class<T> clazz;
    
    
        public FastJson2JsonRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }
    
        @Override
        public byte[] serialize(T t) throws SerializationException {
            if (t == null) {
                return new byte[0];
            }
            return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(StandardCharsets.UTF_8);
        }
    
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
            String str = new String(bytes, StandardCharsets.UTF_8);
    
            return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
        }
    }
    
    配置类
    package com.xiaostudy.common.redis;
    
    import org.springframework.boot.autoconfigure.AutoConfigureBefore;
    import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * redis配置
     */
    @Configuration
    // 启动redis
    @EnableCaching
    // RedisConfig在RedisAutoConfiguration之前加载
    @AutoConfigureBefore(RedisAutoConfiguration.class)
    public class RedisConfig extends CachingConfigurerSupport {
        @Bean
        @SuppressWarnings(value = {"unchecked" , "rawtypes"})
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);
    
            FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
    
            // 使用StringRedisSerializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(serializer);
    
            // Hash的key也采用StringRedisSerializer的序列化方式
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(serializer);
    
            template.afterPropertiesSet();
            return template;
        }
    }
    
    工具类
    package com.xiaostudy.common.redis;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.BoundSetOperations;
    import org.springframework.data.redis.core.HashOperations;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Component;
    
    import java.util.*;
    import java.util.concurrent.TimeUnit;
    
    /**
     * spring redis 工具类
     **/
    @SuppressWarnings(value = {"unchecked" , "rawtypes"})
    @Component
    public class RedisService {
        @Autowired
        public RedisTemplate redisTemplate;
    
        /**
         * 缓存基本的对象,Integer、String、实体类等
         *
         * @param key   缓存的键值
         * @param value 缓存的值
         */
        public <T> void setCacheObject(final String key, final T value) {
            redisTemplate.opsForValue().set(key, value);
        }
    
        /**
         * 缓存基本的对象,Integer、String、实体类等
         *
         * @param key      缓存的键值
         * @param value    缓存的值
         * @param timeout  时间
         * @param timeUnit 时间颗粒度
         */
        public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit) {
            redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
        }
    
        /**
         * 设置有效时间
         *
         * @param key     Redis键
         * @param timeout 超时时间
         * @return true=设置成功;false=设置失败
         */
        public boolean expire(final String key, final long timeout) {
            return expire(key, timeout, TimeUnit.SECONDS);
        }
    
        /**
         * 设置有效时间
         *
         * @param key     Redis键
         * @param timeout 超时时间
         * @param unit    时间单位
         * @return true=设置成功;false=设置失败
         */
        public boolean expire(final String key, final long timeout, final TimeUnit unit) {
            return redisTemplate.expire(key, timeout, unit);
        }
    
        /**
         * 获取有效时间
         *
         * @param key Redis键
         * @return 有效时间
         */
        public long getExpire(final String key) {
            return redisTemplate.getExpire(key);
        }
    
        /**
         * 判断 key是否存在
         *
         * @param key 键
         * @return true 存在 false不存在
         */
        public Boolean hasKey(String key) {
            return redisTemplate.hasKey(key);
        }
    
        /**
         * 获得缓存的基本对象。
         *
         * @param key 缓存键值
         * @return 缓存键值对应的数据
         */
        public <T> T getCacheObject(final String key) {
            ValueOperations<String, T> operation = redisTemplate.opsForValue();
            return operation.get(key);
        }
    
        /**
         * 删除单个对象
         *
         * @param key
         */
        public boolean deleteObject(final String key) {
            return redisTemplate.delete(key);
        }
    
        /**
         * 删除集合对象
         *
         * @param collection 多个对象
         * @return
         */
        public boolean deleteObject(final Collection collection) {
            return redisTemplate.delete(collection) > 0;
        }
    
        /**
         * 缓存List数据
         *
         * @param key      缓存的键值
         * @param dataList 待缓存的List数据
         * @return 缓存的对象
         */
        public <T> long setCacheList(final String key, final List<T> dataList) {
            Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
            return count == null ? 0 : count;
        }
    
        /**
         * 获得缓存的list对象
         *
         * @param key 缓存的键值
         * @return 缓存键值对应的数据
         */
        public <T> List<T> getCacheList(final String key) {
            return redisTemplate.opsForList().range(key, 0, -1);
        }
    
        /**
         * 缓存Set
         *
         * @param key     缓存键值
         * @param dataSet 缓存的数据
         * @return 缓存数据的对象
         */
        public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
            BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
            Iterator<T> it = dataSet.iterator();
            while (it.hasNext()) {
                setOperation.add(it.next());
            }
            return setOperation;
        }
    
        /**
         * 获得缓存的set
         *
         * @param key
         * @return
         */
        public <T> Set<T> getCacheSet(final String key) {
            return redisTemplate.opsForSet().members(key);
        }
    
        /**
         * 缓存Map
         *
         * @param key
         * @param dataMap
         */
        public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
            if (dataMap != null) {
                redisTemplate.opsForHash().putAll(key, dataMap);
            }
        }
    
        /**
         * 获得缓存的Map
         *
         * @param key
         * @return
         */
        public <T> Map<String, T> getCacheMap(final String key) {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * 往Hash中存入数据
         *
         * @param key   Redis键
         * @param hKey  Hash键
         * @param value 值
         */
        public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
            redisTemplate.opsForHash().put(key, hKey, value);
        }
    
        /**
         * 获取Hash中的数据
         *
         * @param key  Redis键
         * @param hKey Hash键
         * @return Hash中的对象
         */
        public <T> T getCacheMapValue(final String key, final String hKey) {
            HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
            return opsForHash.get(key, hKey);
        }
    
        /**
         * 获取多个Hash中的数据
         *
         * @param key   Redis键
         * @param hKeys Hash键集合
         * @return Hash对象集合
         */
        public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
            return redisTemplate.opsForHash().multiGet(key, hKeys);
        }
    
        /**
         * 删除Hash中的某条数据
         *
         * @param key  Redis键
         * @param hKey Hash键
         * @return 是否成功
         */
        public boolean deleteCacheMapValue(final String key, final String hKey) {
            return redisTemplate.opsForHash().delete(key, hKey) > 0;
        }
    
        /**
         * 获得缓存的基本对象列表
         *
         * @param pattern 字符串前缀
         * @return 对象列表
         */
        public Collection<String> keys(final String pattern) {
            return redisTemplate.keys(pattern);
        }
    }
    
    1. 字符串工具类
      image
    点击查看代码
    package com.xiaostudy.common.utils;
    
    
    import java.util.Arrays;
    import java.util.List;
    
    public class StringUtils {
    
        public static final String BLANK = " ";
        public static final String EMPTY = "";
        public static final String DEFAULT_LOGOUT_SUCCESS_URL = "/web/webLogin/isLogout";
        public static final String DEFAULT_LOGIN_URL_1 = "/web/webLogin/form";
        public static final String DEFAULT_LOGIN_MAIL_URL_1 = "/web/webLogin/emailLogin";
        public static final String DEFAULT_LOGOUT_URL_1 = "/web/webLogin/logout";
        public static final String DEFAULT_REGISTER_URL_1 = "/web/webLogin/register";
        public static final String DEFAULT_REGISTER_HTML_1 = "/web/register.html";
        public static final String DEFAULT_LOGOUT_HTML_1 = "/web/login.html";
        public static final String DEFAULT_LOGIN_MAIL_HTML = "/web/loginMail.html";
        public static final String DEFAULT_INDEX_HTML_1 = "/web/index.html";
        public static final String COMMA = ",";
        public static final String EMAIL = "email";
        public static final String WILDCARD = "**";
        public static final String[] REQUEST_RUL_WHITE_S = {
                DEFAULT_LOGOUT_HTML_1
                , "/web/webLogin/login"
                , DEFAULT_LOGOUT_SUCCESS_URL
                , DEFAULT_LOGIN_MAIL_HTML
                , DEFAULT_REGISTER_URL_1
                , DEFAULT_REGISTER_HTML_1
                , DEFAULT_LOGIN_URL_1
                , "/security/verifyCode"
                , "/security/sendMailVerifyCode"
                , "/security/sendPhoneVerifyCode"
                , "/web/webLogin/test"
                , "/web/webLogin/test2"
                , "/web/img/**"
        };
        public static final List<String> REQUEST_RUL_WHITE_LIST = Arrays.asList(REQUEST_RUL_WHITE_S);
        public static final List<String> REQUEST_IP_WHITE_LIST = Arrays.asList(
                "192.168.1.6"
                , "192.168.1.2"
                , "127.0.0.1"
        );
    }
    
    1. 验证码工具类
    点击查看代码
    package com.xiaostudy.common.utils;
    
    import javax.imageio.ImageIO;
    import java.awt.*;
    import java.awt.geom.AffineTransform;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Arrays;
    import java.util.Map;
    import java.util.Random;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    public final class VerifyCodeUtils {
    
        //使用到Algerian字体,系统里没有的话需要安装字体,字体只显示大写,去掉了1,0,i,o几个容易混淆的字符
        public static final String VERIFY_CODES = "1234567890ABCDEFGHIJKLMNPQRSTUVWXYZ";
        private static Random random = new Random();
    
        private VerifyCodeUtils() {
        }
    
        public static final String EMAIL_REGEX = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
    
        public static boolean isEMail(String email) {
            Pattern regex = Pattern.compile(EMAIL_REGEX);
            Matcher matcher = regex.matcher(email);
            return matcher.matches();
        }
    
    
        /**
         * 使用系统默认字符源生成验证码
         */
        public static String generateVerifyCode(int verifySize) {
            return generateVerifyCode(verifySize, VERIFY_CODES);
        }
    
        /**
         * 使用指定源生成验证码
         */
        public static String generateVerifyCode(int verifySize, String sources) {
            if (sources == null || sources.length() == 0) {
                sources = VERIFY_CODES;
            }
            int codesLen = sources.length();
            Random rand = new Random(System.currentTimeMillis());
            StringBuilder verifyCode = new StringBuilder(verifySize);
            for (int i = 0; i < verifySize; i++) {
                verifyCode.append(sources.charAt(rand.nextInt(codesLen - 1)));
            }
            return verifyCode.toString();
        }
    
        /**
         * 生成随机验证码文件,并返回验证码值
         */
        public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException {
            String verifyCode = generateVerifyCode(verifySize);
            outputImage(w, h, outputFile, verifyCode);
            return verifyCode;
        }
    
        /**
         * 输出随机验证码图片流,并返回验证码值
         */
        public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException {
            String verifyCode = generateVerifyCode(verifySize);
            outputImage(w, h, os, verifyCode);
            return verifyCode;
        }
    
        /**
         * 生成指定验证码图像文件
         */
        public static void outputImage(int w, int h, File outputFile, String code) throws IOException {
            if (outputFile == null) {
                return;
            }
            File dir = outputFile.getParentFile();
            if (!dir.exists()) {
                dir.mkdirs();
            }
            outputFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(outputFile);
            outputImage(w, h, fos, code);
            fos.close();
        }
    
        /**
         * 输出指定验证码图片流
         */
        public static void outputImage(int w, int h, OutputStream os, String code) throws IOException {
            int verifySize = code.length();
            BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
            Random rand = new Random();
            Graphics2D g2 = image.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            Color[] colors = new Color[5];
            Color[] colorSpaces = new Color[]{Color.WHITE, Color.CYAN,
                    Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                    Color.PINK, Color.YELLOW};
            float[] fractions = new float[colors.length];
            for (int i = 0; i < colors.length; i++) {
                colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
                fractions[i] = rand.nextFloat();
            }
            Arrays.sort(fractions);
    
            g2.setColor(Color.GRAY);// 设置边框色
            g2.fillRect(0, 0, w, h);
    
            Color c = getRandColor(200, 250);
            g2.setColor(c);// 设置背景色
            g2.fillRect(0, 2, w, h - 4);
    
            //绘制干扰线
            Random random = new Random();
            g2.setColor(getRandColor(160, 200));// 设置线条的颜色
            for (int i = 0; i < 20; i++) {
                int x = random.nextInt(w - 1);
                int y = random.nextInt(h - 1);
                int xl = random.nextInt(6) + 1;
                int yl = random.nextInt(12) + 1;
                g2.drawLine(x, y, x + xl + 40, y + yl + 20);
            }
    
            // 添加噪点
            float yawpRate = 0.05f;// 噪声率
            int area = (int) (yawpRate * w * h);
            for (int i = 0; i < area; i++) {
                int x = random.nextInt(w);
                int y = random.nextInt(h);
                int rgb = getRandomIntColor();
                image.setRGB(x, y, rgb);
            }
    
            shear(g2, w, h, c);// 使图片扭曲
    
            g2.setColor(getRandColor(100, 160));
            int fontSize = h - 4;
            Font font = new Font("Algerian", Font.ITALIC, fontSize);
            g2.setFont(font);
            char[] chars = code.toCharArray();
            for (int i = 0; i < verifySize; i++) {
                AffineTransform affine = new AffineTransform();
                affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize / 2, h / 2);
                g2.setTransform(affine);
                g2.drawChars(chars, i, 1, ((w - 10) / verifySize) * i + 5, h / 2 + fontSize / 2 - 10);
            }
    
            g2.dispose();
            ImageIO.write(image, "jpg", os);
        }
    
        private static Color getRandColor(int fc, int bc) {
            if (fc > 255)
                fc = 255;
            if (bc > 255)
                bc = 255;
            int r = fc + random.nextInt(bc - fc);
            int g = fc + random.nextInt(bc - fc);
            int b = fc + random.nextInt(bc - fc);
            return new Color(r, g, b);
        }
    
        private static int getRandomIntColor() {
            int[] rgb = getRandomRgb();
            int color = 0;
            for (int c : rgb) {
                color = color << 8;
                color = color | c;
            }
            return color;
        }
    
        private static int[] getRandomRgb() {
            int[] rgb = new int[3];
            for (int i = 0; i < 3; i++) {
                rgb[i] = random.nextInt(255);
            }
            return rgb;
        }
    
        private static void shear(Graphics g, int w1, int h1, Color color) {
            shearX(g, w1, h1, color);
            shearY(g, w1, h1, color);
        }
    
        private static void shearX(Graphics g, int w1, int h1, Color color) {
    
            int period = random.nextInt(2);
    
            boolean borderGap = true;
            int frames = 1;
            int phase = random.nextInt(2);
    
            for (int i = 0; i < h1; i++) {
                double d = (double) (period >> 1)
                        * Math.sin((double) i / (double) period
                        + (6.2831853071795862D * (double) phase)
                        / (double) frames);
                g.copyArea(0, i, w1, 1, (int) d, 0);
                if (borderGap) {
                    g.setColor(color);
                    g.drawLine((int) d, i, 0, i);
                    g.drawLine((int) d + w1, i, w1, i);
                }
            }
    
        }
    
        private static void shearY(Graphics g, int w1, int h1, Color color) {
    
            int period = random.nextInt(40) + 10;
    
            boolean borderGap = true;
            int frames = 20;
            int phase = 7;
            for (int i = 0; i < w1; i++) {
                double d = (double) (period >> 1)
                        * Math.sin((double) i / (double) period
                        + (6.2831853071795862D * (double) phase)
                        / (double) frames);
                g.copyArea(i, 0, 1, h1, 0, (int) d);
                if (borderGap) {
                    g.setColor(color);
                    g.drawLine(i, (int) d, i, 0);
                    g.drawLine(i, (int) d + h1, i, h1);
                }
    
            }
    
        }
    
    }
    

    image

    1. 解密工具类
    点击查看代码
    package com.xiaostudy.common.utils;
    
    import javax.crypto.*;
    import javax.crypto.spec.DESedeKeySpec;
    import javax.crypto.spec.IvParameterSpec;
    import java.nio.charset.Charset;
    import java.nio.charset.StandardCharsets;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.Key;
    import java.security.NoSuchAlgorithmException;
    import java.security.spec.InvalidKeySpecException;
    
    public class DESUtils {
        /**
         * 用户名称密码加密,密钥
         */
        private static final String SECRET_KEY = "mwPZ7ISbC!ox6@7cP*^…5@%$)2*V";
        // 向量
        private static final String IV = "mwPZ7C!n";
        // 加解密统一使用的编码方式
        private static final Charset encoding = StandardCharsets.UTF_8;
    
        /**
         * 3DES解密
         *
         * @param encryptText 加密文本
         * @return
         * @throws Exception
         */
        public static String decode(String encryptText) throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
            DESedeKeySpec spec = new DESedeKeySpec(SECRET_KEY.getBytes());
            SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("desede");
            Key deskey = keyfactory.generateSecret(spec);
            Cipher cipher = Cipher.getInstance("desede/CBC/PKCS5Padding");
            IvParameterSpec ips = new IvParameterSpec(IV.getBytes());
            cipher.init(Cipher.DECRYPT_MODE, deskey, ips);
            byte[] decryptData = cipher.doFinal(hexToBytes(encryptText));
            return new String(decryptData, encoding);
        }
    
        public static byte[] hexToBytes(String hex) {
            hex = hex.length() % 2 != 0 ? "0" + hex : hex;
    
            byte[] b = new byte[hex.length() / 2];
            for (int i = 0; i < b.length; i++) {
                int index = i * 2;
                int v = Integer.parseInt(hex.substring(index, index + 2), 16);
                b[i] = (byte) v;
            }
            return b;
        }
    }
    

    4. 网关Gateway

    1. 创建Security

    1. 创建操作
      image
      image

    2. 父模块添加子模块
      image

    <module>security</module>
    
    1. 修改pom.xml
      image
      image
      image
      image
    点击查看代码
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.xiaostudy</groupId>
            <artifactId>SpringCloud202208</artifactId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
        <groupId>com.xiaostudy</groupId>
        <artifactId>security</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>security</name>
        <description>security</description>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
            <!--集成响应式web框架-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>
    
            <!-- druid数据源驱动 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.1</version>
            </dependency>
    
            <!--AOP-->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
            </dependency>
            <!--动态切换数据源用到-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.2</version>
            </dependency>
    
            <!-- JWT Token验证机制 -->
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.13.0</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.0</version>
            </dependency>
    
            <!--邮箱依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-mail</artifactId>
            </dependency>
    
            <!--SpringBoot中集成了jasypt在一定程度上保证密码的安全-->
            <dependency>
                <groupId>com.github.ulisesbocchio</groupId>
                <artifactId>jasypt-spring-boot-starter</artifactId>
                <version>2.1.1</version>
            </dependency>
    
            <dependency>
                <groupId>com.xiaostudy</groupId>
                <artifactId>common</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    1. 修改配置文件application.properties
      image
    点击查看代码
    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8
          username: root
          password: 密码
        druid2:
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=utf8
          username: root
          password: 密码
      mail:
        default-encoding: UTF-8
        # 阿里云发送服务器地址
        host: smtp.mxhichina.com
        #    port: 25                      #端口号
        # 发送人地址
        username: liwei@xiaostudy.com
        # 密码
        password: ENC(密码加密后的字符串)
        properties:
          mail:
            smtp:
              starttls:
                enable: true
                required: true
              auth: true
              socketFactory:
                class: javax.net.ssl.SSLSocketFactory
                port: 465
    jasypt:
      encryptor:
        password: 密钥
    mybatis:
      configuration:
        # 下划线转驼峰
        map-underscore-to-camel-case: true
      # 注册映射文件
      mapper-locations: mapper/*Mapper.xml
      # 注册实体类别名
      type-aliases-package: com.xiaostudy.security.entity
    
    session:
      # session过期时间,单位秒
      timeout: 1800
    #  timeout: 30
    
    1. 查询用户的实体类、service、mapper
    用户实体类
    package com.xiaostudy.security.entity;
    
    public class UserEentity {
    
        private String username;
        private String password;
        private String role;
        private Integer errorCount;
        private String url;
        private String email;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getRole() {
            return role;
        }
    
        public void setRole(String role) {
            this.role = role;
        }
    
        public Integer getErrorCount() {
            return errorCount;
        }
    
        public void setErrorCount(Integer errorCount) {
            this.errorCount = errorCount;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }
    
    Mapper接口
    package com.xiaostudy.security.mapper;
    
    import com.xiaostudy.security.entity.UserEentity;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    
    import java.util.List;
    
    @Mapper
    public interface UserMapper {
    
        public List<UserEentity> selectUserAll();
        public UserEentity selectUserByName(@Param("name") String username);
        public UserEentity selectUserByEmail(@Param("email") String email);
        public UserEentity selectUserByPhone(@Param("phone") String phone);
    
        public int loginPasswordErrorAdd(@Param("name")String username);
        public int loginPasswordErrorClean(@Param("name")String username);
    }
    
    Mapper.xml文件
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.xiaostudy.security.mapper.UserMapper">
    
        <select id="selectUserAll" resultType="com.xiaostudy.security.entity.UserEentity">
            SELECT username, password, role, error_count FROM `user`
        </select>
    
        <select id="selectUserByName" resultType="com.xiaostudy.security.entity.UserEentity">
            SELECT username, password, role, error_count, url, email FROM `user` where username = #{name}
        </select>
    
        <select id="selectUserByEmail" resultType="com.xiaostudy.security.entity.UserEentity">
            SELECT username, password, role, error_count, url, email FROM `user` where email = #{email}
        </select>
    
        <select id="selectUserByPhone" resultType="com.xiaostudy.security.entity.UserEentity">
            SELECT username, password, role, error_count, url, email, phone FROM `user` where phone = #{phone}
        </select>
    
        <update id="loginPasswordErrorAdd" parameterType="java.lang.String">
            update `user` set error_count = error_count + 1 where username = #{name}
        </update>
    
        <update id="loginPasswordErrorClean" parameterType="java.lang.String">
            update `user` set error_count = 0 where username = #{name}
        </update>
    
    </mapper>
    
    service
    package com.xiaostudy.security.service;
    
    
    import com.xiaostudy.security.entity.UserEentity;
    
    import java.util.List;
    
    public interface UserService {
    
        public List<UserEentity> selectUserAll();
        public UserEentity selectUserByNameDb1(String username);
        public UserEentity selectUserByEmailDb1(String email);
        public UserEentity selectUserByPhoneDb1(String phone);
        public UserEentity selectUserByNameDb2(String username);
    
        public boolean loginPasswordErrorAdd(String username);
        public boolean loginPasswordErrorClean(String username);
    }
    
    service实现类
    package com.xiaostudy.security.service.impl;
    
    import com.xiaostudy.security.datasources.annotation.DataSource;
    import com.xiaostudy.security.datasources.enums.DataSourceNameEnum;
    import com.xiaostudy.security.entity.UserEentity;
    import com.xiaostudy.security.mapper.UserMapper;
    import com.xiaostudy.security.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public List<UserEentity> selectUserAll() {
            return userMapper.selectUserAll();
        }
    
        @DataSource(name = DataSourceNameEnum.FIRST)
        @Override
        public UserEentity selectUserByNameDb1(String username) {
            return userMapper.selectUserByName(username);
        }
    
        @DataSource(name = DataSourceNameEnum.FIRST)
        @Override
        public UserEentity selectUserByEmailDb1(String email) {
            return userMapper.selectUserByEmail(email);
        }
        @DataSource(name = DataSourceNameEnum.FIRST)
        @Override
        public UserEentity selectUserByPhoneDb1(String phone) {
            return userMapper.selectUserByPhone(phone);
        }
    
        @DataSource(name = DataSourceNameEnum.SECOND)
        @Override
        public UserEentity selectUserByNameDb2(String username) {
            return userMapper.selectUserByName(username);
        }
    
        @DataSource(name = DataSourceNameEnum.FIRST)
        @Override
        public boolean loginPasswordErrorAdd(String username) {
            int i = userMapper.loginPasswordErrorAdd(username);
            return 0 != i;
        }
    
        @Transactional(rollbackFor = Exception.class)
        @Override
        public boolean loginPasswordErrorClean(String username) {
            int i = userMapper.loginPasswordErrorClean(username);
            return 0 != i;
        }
    }
    
    多数据源枚举
    package com.xiaostudy.security.datasources.enums;
    
    /**
     * 多数据源配置数据源枚举
     */
    public enum DataSourceNameEnum {
        FIRST("first")
        ,SECOND("second");
    
        private String name;
    
        DataSourceNameEnum(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }
    
    动态数据源路由
    package com.xiaostudy.security.datasources;
    
    import com.xiaostudy.security.datasources.enums.DataSourceNameEnum;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    import javax.sql.DataSource;
    import java.util.Map;
    
    /**
     * 动态数据源路由
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        private static final ThreadLocal<DataSourceNameEnum> contextHolder = new ThreadLocal<>();
    
        public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
            super.setDefaultTargetDataSource(defaultTargetDataSource);
            super.setTargetDataSources(targetDataSources);
            super.afterPropertiesSet();
        }
    
        @Override
        protected Object determineCurrentLookupKey() {
            return this.getDataSource();
        }
    
        public static void setDataSource(DataSourceNameEnum dataSource) {
            contextHolder.set(dataSource);
        }
    
        public static DataSourceNameEnum getDataSource() {
            return contextHolder.get();
        }
    
        public static void clearDataSource() {
            contextHolder.remove();
        }
    
    }
    
    多数据源注解
    package com.xiaostudy.security.datasources.annotation;
    
    import com.xiaostudy.security.datasources.enums.DataSourceNameEnum;
    
    import java.lang.annotation.*;
    
    /**
     * 多数据源注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        DataSourceNameEnum name();
    }
    
    多数据源AOP类
    package com.xiaostudy.security.datasources.aop;
    
    import com.xiaostudy.security.datasources.DynamicDataSource;
    import com.xiaostudy.security.datasources.annotation.DataSource;
    import com.xiaostudy.security.datasources.enums.DataSourceNameEnum;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.Ordered;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * 多数据源,切面处理类
     */
    @Aspect
    @Component
    public class DataSourceAspect implements Ordered {
        private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class);
    
        /**
         * 针对上面注解做切面拦截
         */
        @Pointcut("@annotation(com.xiaostudy.security.datasources.annotation.DataSource)")
    //    @Pointcut("execution(* com.xiaostudy.security.datasources..*.*(..))")
        public void dataSourcePointCut() {}
    
        @Around("dataSourcePointCut()")
        public Object around(ProceedingJoinPoint point) throws Throwable {
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            DataSource dataSource = method.getAnnotation(DataSource.class);
            if(dataSource == null){
                //如果没有注解,使用默认数据源
                DynamicDataSource.setDataSource(DataSourceNameEnum.FIRST);
            }else {
                //根据注解中设置的数据源名称,选择对应的数据源
                DynamicDataSource.setDataSource(dataSource.name());
                LOGGER.info("set datasource is " + dataSource.name().getName());
            }
    
            try {
                return point.proceed();
            } finally {
                //清除数据源配置
                DynamicDataSource.clearDataSource();
            }
        }
    
        @Override
        public int getOrder() {
            return 1;
        }
    }
    
    多数据配置类
    package com.xiaostudy.security.datasources.config;
    
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import com.xiaostudy.security.datasources.DynamicDataSource;
    import com.xiaostudy.security.datasources.enums.DataSourceNameEnum;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 多数据源配置类
     */
    @Configuration
    public class DynamicDataSourceConfig {
    
        //如果ioc容器中,同一个类型有多个bean,则bean的名称为方法的名称
        @Bean("firstDataSource")
        @ConfigurationProperties("spring.datasource.druid")
        public DataSource firstDataSource() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean("secondDataSource")
        @ConfigurationProperties("spring.datasource.druid2")
        public DataSource secondDataSource() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean
        @Primary
        public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceNameEnum.FIRST, firstDataSource);
            targetDataSources.put(DataSourceNameEnum.SECOND, secondDataSource);
            return new DynamicDataSource(firstDataSource, targetDataSources);
        }
    }
    

    image

    2. Security登陆配置

    1. 配置密码加密、解析器
    点击查看代码
    package com.xiaostudy.security.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    public class BeanConfig {
    
        //配置密码加密、解析器
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    
    1. IP工具类
    点击查看代码
    package com.xiaostudy.security.utils;
    
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    
    public final class IpUtils {
    
        private IpUtils() {
        }
    
        public static final String UNKNOWN = "unknown";
        public static final String LOCAL_IPV6 = "0:0:0:0:0:0:0:1";
        public static final String LOCAL_IPV4 = "127.0.0.1";
    
        public static String getIpAddr(ServerHttpRequest request) {
            HttpHeaders headers = request.getHeaders();
            String ip = headers.getFirst("x-forwarded-for");
            if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip) && ip.indexOf(",") != -1) {
                // 多次反向代理后会有多个ip值,第一个ip才是真实ip
                ip = ip.split(",")[0];
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("X-Real-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddress().getAddress().getHostAddress();
            }
            return LOCAL_IPV6.equals(ip) ? LOCAL_IPV4 : ip;
        }
    }
    
    1. Token工具类
    点击查看代码
    package com.xiaostudy.security.utils;
    
    import com.xiaostudy.common.utils.StringUtils;
    import io.jsonwebtoken.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpHeaders;
    import org.springframework.util.ObjectUtils;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ExecutorService;
    
    public class JwtTokenUtils {
        private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtils.class);
        // 有效时间,单位毫秒
    //    public static final long EXPIRATION = 30 * 1000L;
        public static final long EXPIRATION = 40 * 60 * 1000L;
        public static final long TOKEN_REFRESH_DATE = 15 * 1000L;
        //    public static final long TOKEN_REFRESH_DATE = 20 * 60 * 1000L;
        public static final String TOKEN_REFRESH_DATE_STR = "TOKEN_REFRESH_DATE";
        //JWT密钥
        public static final String SECRET = "123654";
    
        public static final String BASIC_EMPTY = "Basic ";
        public static final String BASIC_EMPTY_ = "Basic%20";
        public static final String AUTHENTICATION = "Authorization";
        public static final String COOKIE_AUTHENTICATION_BASIC_EMPTY_ = "Authorization=Basic%20";
        public static final String COOKIE_SPLIT = ";";
        public static final String COOKIE = "Cookie";
        public static final String TOKEN_CREATED = "created";
        public static final String TOKEN_REFRESH_FLAG = "RefreshTokenFlag";
        public static final String TOKEN_REFRESH_YES = "1";
        public static final String TOKEN_REFRESH_NO = "0";
    
        public static final String VERIFY_CODE = "verifyCode";
        public static final String COOKIE_VERIFY_CODE = "verifyCode=";
        public static final String USER_NAME = "userName";
        public static final String PASS_WORD = "passWord";
    
        public static final int LOGIN_ERROR_COUNT = 5;
    
        /**
         * 生成token令牌
         *
         * @param username 用户
         * @param payloads 令牌中携带的附加信息
         * @return 令token牌
         */
        public static String generateToken(String username, Map<String, Object> payloads) {
            int payloadSizes = payloads == null ? 0 : payloads.size();
    
            Map<String, Object> claims = new HashMap<>(payloadSizes + 2);
            claims.put(Claims.SUBJECT, username);
            claims.put(TOKEN_CREATED, new Date());
    
            if (payloadSizes > 0) {
                claims.putAll(payloads);
            }
    
            return generateToken(claims);
        }
    
        /**
         * 从claims生成令牌,如果看不懂就看谁调用它
         *
         * @param claims 数据声明
         * @return 令牌
         */
        private static String generateToken(Map<String, Object> claims) {
            Date expirationDate = new Date(System.currentTimeMillis() + EXPIRATION);
            // 刷新token时间
            claims.put(JwtTokenUtils.TOKEN_REFRESH_DATE_STR, new Date(System.currentTimeMillis() + TOKEN_REFRESH_DATE));
            return Jwts.builder().setClaims(claims)
                    .setExpiration(expirationDate)
                    .signWith(SignatureAlgorithm.HS512, SECRET)
                    .compact();
        }
    
        /**
         * 判断令牌是否过期
         *
         * @param token 令牌
         * @return 是否过期
         */
        public static boolean isTokenExpired(String token) {
            if (ObjectUtils.isEmpty(token)) {
                return false;
            }
            try {
                Claims claims = getClaimsFromToken(token);
                if (ObjectUtils.isEmpty(claims)) {
                    return false;
                }
                Date expiration = claims.getExpiration();
                return new Date().before(expiration);
            } catch (Exception e) {
                LOGGER.error("判断令牌是否过期异常", e);
                return false;
            }
        }
    
        /**
         * 从令牌中获取用户名
         *
         * @param token 令牌
         * @return 用户名
         */
        public static String getUsernameFromToken(String token) {
            if (ObjectUtils.isEmpty(token)) {
                return null;
            }
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = claims.getSubject();
            } catch (Exception e) {
                LOGGER.error("从令牌中获取用户名异常1", e);
                username = null;
            }
            return username;
        }
    
        /**
         * 刷新令牌
         *
         * @param token 原令牌
         * @return 新令牌
         */
        public static String refreshToken(String token) {
            if (ObjectUtils.isEmpty(token)) {
                return null;
            }
            String refreshedToken;
            try {
                Claims claims = getClaimsFromToken(token);
                claims.put(TOKEN_CREATED, new Date());
                refreshedToken = generateToken(claims);
            } catch (Exception e) {
                LOGGER.error("刷新令牌异常", e);
                refreshedToken = null;
            }
            return refreshedToken;
        }
    
        /**
         * 从令牌中获取数据声明,如果看不懂就看谁调用它
         *
         * @param token 令牌
         * @return 数据声明
         */
        private static Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                JwtParser jwtParser = Jwts.parser().setSigningKey(SECRET);
                Jws<Claims> claimsJws = jwtParser.parseClaimsJws(token);
                claims = claimsJws.getBody();
            } catch (Exception e) {
                LOGGER.error("从令牌中获取数据声明异常");
    //            LOGGER.error("从令牌中获取数据声明异常", e);
                claims = null;
            }
            return claims;
        }
    
        public static String getCookieUsername(HttpHeaders headers) {
            String authentication = getCookieAuthentication(headers);
            if (ObjectUtils.isEmpty(authentication)) {
                return null;
            }
            return getUsernameFromToken(authentication);
        }
    
        public static String getCookieAuthentication(HttpHeaders headers) {
            String authentication = headers.getFirst(JwtTokenUtils.AUTHENTICATION);
            if (ObjectUtils.isEmpty(authentication)) {
                String cookieStr = headers.getFirst(JwtTokenUtils.COOKIE);
                if (!ObjectUtils.isEmpty(cookieStr)) {
                    cookieStr = cookieStr.replaceAll(StringUtils.BLANK, StringUtils.EMPTY);
                    String[] cookies = cookieStr.split(JwtTokenUtils.COOKIE_SPLIT);
                    for (String c : cookies) {
                        if (!ObjectUtils.isEmpty(c) && c.startsWith(JwtTokenUtils.COOKIE_AUTHENTICATION_BASIC_EMPTY_)) {
                            authentication = c.replaceFirst(JwtTokenUtils.COOKIE_AUTHENTICATION_BASIC_EMPTY_, StringUtils.EMPTY);
                            break;
                        }
                    }
                }
            }
            if (!ObjectUtils.isEmpty(authentication)) {
                if (authentication.startsWith(JwtTokenUtils.BASIC_EMPTY_)) {
                    authentication = authentication.replaceFirst(JwtTokenUtils.BASIC_EMPTY_, StringUtils.EMPTY);
                } else if (authentication.startsWith(JwtTokenUtils.BASIC_EMPTY)) {
                    authentication = authentication.replaceFirst(JwtTokenUtils.BASIC_EMPTY, StringUtils.EMPTY);
                }
            }
            return authentication;
        }
    
        public static String getCookieVerifyCode(HttpHeaders headers) {
            String cookieStr = headers.getFirst(JwtTokenUtils.COOKIE);
            if (ObjectUtils.isEmpty(cookieStr)) {
                return null;
            }
            cookieStr = cookieStr.replaceAll(StringUtils.BLANK, StringUtils.EMPTY);
            String[] cookies = cookieStr.split(JwtTokenUtils.COOKIE_SPLIT);
            for (String c : cookies) {
                if (!ObjectUtils.isEmpty(c) && c.startsWith(JwtTokenUtils.COOKIE_VERIFY_CODE)) {
                    return c.replaceFirst(JwtTokenUtils.COOKIE_VERIFY_CODE, StringUtils.EMPTY);
                }
            }
            return null;
        }
    
        private static final Map<String, Date> LOG_TOKEN_DATE_MAP = new ConcurrentHashMap<>();
        private static final ExecutorService POOL = java.util.concurrent.Executors.newFixedThreadPool(2);
    
        public static boolean checkTokenAndRefreshToken(HttpHeaders headers, String authentication) {
            boolean tokenExpired = false;
            Date now = new Date();
            if (ObjectUtils.isEmpty(authentication)) {
                return tokenExpired;
            }
            try {
                Claims claims = getClaimsFromToken(authentication);
                if (ObjectUtils.isEmpty(claims)) {
                    return tokenExpired;
                }
                Date expiration = claims.getExpiration();
                if (now.after(expiration)) {
                    return tokenExpired;
                }
                Date expirationTokenRefresh = claims.get(TOKEN_REFRESH_DATE_STR, Date.class);
                tokenExpired = now.before(expirationTokenRefresh);
            } catch (Exception e) {
                LOGGER.error("判断令牌是否过期异常", e);
                return false;
            }
    
            headers.set(TOKEN_REFRESH_FLAG, TOKEN_REFRESH_NO);
            if (tokenExpired) {
                // token有效
                POOL.execute(new LogTokenRunnable(LOG_TOKEN_DATE_MAP, authentication, now));
                return tokenExpired;
            } else {
                Date date = LOG_TOKEN_DATE_MAP.get(authentication);
                if (ObjectUtils.isEmpty(date)) {
                    return tokenExpired;
                }
                Date expirationDate = new Date(date.getTime() + EXPIRATION);
                if (expirationDate.before(now)) {
                    return tokenExpired;
                }
                String refreshToken = refreshToken(authentication);
                if (ObjectUtils.isEmpty(refreshToken)) {
                    return tokenExpired;
                }
                headers.set(TOKEN_REFRESH_FLAG, TOKEN_REFRESH_YES);
                headers.set(AUTHENTICATION, BASIC_EMPTY + refreshToken);
                return true;
            }
        }
    
        // 记录token最后请求时间
        private static class LogTokenRunnable implements Runnable {
            private Map<String, Date> map;
            private String token;
            private Date now;
    
            LogTokenRunnable(Map<String, Date> map, String token, Date now) {
                this.map = map;
                this.token = token;
                this.now = now;
            }
    
            @Override
            public void run() {
                if (null == map || ObjectUtils.isEmpty(token) || ObjectUtils.isEmpty(now)) {
                    return;
                }
                Date date = map.get(token);
                if (ObjectUtils.isEmpty(date) || now.after(date)) {
                    map.put(token, now);
                }
            }
        }
    }
    
    1. 自定义UsernamePasswordAuthenticationToken
    点击查看代码
    package com.xiaostudy.security.entity;
    
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.utils.JwtTokenUtils;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.util.MultiValueMap;
    
    public class MyUserDetails extends UsernamePasswordAuthenticationToken {
        private static final long serialVersionUID = 1L;
        private String username;
        private String password;
        private String verifyCode;
        private String ipAddr;
        private String email;
    
        public MyUserDetails(String username, String password, String verifyCode, String ipAddr, String email) {
            super(username, password);
            this.username = username;
            this.password = password;
            this.verifyCode = verifyCode;
            this.ipAddr = ipAddr;
            this.email = email;
        }
    
        public static MyUserDetails unauthenticated(String username, String password, String verifyCode, String ipAddr, String email) {
            return new MyUserDetails(username, password, verifyCode, ipAddr, email);
        }
    
        public static MyUserDetails unauthenticated(String username, String password) {
            return new MyUserDetails(username, password, null, null, null);
        }
    
        public static MyUserDetails createAuthentication(MultiValueMap<String, String> data, String ipAddr) {
            String username = data.getFirst(JwtTokenUtils.USER_NAME);
            String password = data.getFirst(JwtTokenUtils.PASS_WORD);
            String verifyCode = data.getFirst(JwtTokenUtils.VERIFY_CODE);
            String email = data.getFirst(StringUtils.EMAIL);
            return MyUserDetails.unauthenticated(username, password, verifyCode, ipAddr, email);
        }
    
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public String getVerifyCode() {
            return verifyCode;
        }
    
        public void setVerifyCode(String verifyCode) {
            this.verifyCode = verifyCode;
        }
    
        public String getIpAddr() {
            return ipAddr;
        }
    
        public void setIpAddr(String ipAddr) {
            this.ipAddr = ipAddr;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
    }
    
    1. 自定义ServerFormLoginAuthenticationConverter,从表单获取参数转成自定义UsernamePasswordAuthenticationToken类
    点击查看代码
    package com.xiaostudy.security.config;
    
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.entity.MyUserDetails;
    import com.xiaostudy.security.utils.IpUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.server.authentication.ServerFormLoginAuthenticationConverter;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    // 请求认证过滤器,从表单获取参数,不用security的默认参数名username、password
    @Configuration
    public class MyServerFormLoginAuthenticationConverter extends ServerFormLoginAuthenticationConverter {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyServerFormLoginAuthenticationConverter.class);
    
        @Override
        public Mono<Authentication> convert(ServerWebExchange exchange) {
            LOGGER.info("请求认证过滤器----MyServerFormLoginAuthenticationConverter.........");
            String uri = exchange.getRequest().getURI().getPath();
            if (StringUtils.DEFAULT_LOGIN_URL_1.equals(uri)) {  //登录操作才对body做特殊操作,其他请求直接调用原有请求
                return this.apply(exchange);
            } else { //非登录操作,基本不用在网关里读取body,默认方法就行
                return super.convert(exchange);
            }
        }
    
        @Override
        public Mono<Authentication> apply(ServerWebExchange exchange) {
            final String ipAddr = IpUtils.getIpAddr(exchange.getRequest());
            return exchange.getFormData().map((data) -> MyUserDetails.createAuthentication(data, ipAddr));
        }
    
    }
    
    1. 自定义登陆处理
    点击查看代码
    package com.xiaostudy.security.config;
    
    import com.xiaostudy.common.redis.RedisService;
    import com.xiaostudy.common.utils.DESUtils;
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.entity.MyUserDetails;
    import com.xiaostudy.security.entity.UserEentity;
    import com.xiaostudy.security.service.UserService;
    import com.xiaostudy.security.utils.JwtTokenUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.*;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.util.ObjectUtils;
    import reactor.core.publisher.Mono;
    
    // 自定义处理登陆
    @Configuration
    public class MyReactiveAuthenticationManager implements ReactiveAuthenticationManager {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyReactiveAuthenticationManager.class);
    
        /**
         * @see BeanConfig#passwordEncoder()
         */
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Autowired
        private UserService userService;
    
        @Autowired
        private RedisService redisService;
    
        @Override
        public Mono<Authentication> authenticate(Authentication authentication) {
            LOGGER.info("自定义处理登陆----MyReactiveAuthenticationManager.........");
            //获取输入的用户名
            String username = authentication.getName();
            //获取输入的明文
            String rawPassword = (String) authentication.getCredentials();
            MyUserDetails myUserDetails = null;
            String verifyCode = null;
            String ipAddr = null;
            if (authentication instanceof MyUserDetails) {
                myUserDetails = (MyUserDetails) authentication;
                username = myUserDetails.getUsername();
                rawPassword = myUserDetails.getPassword();
                verifyCode = myUserDetails.getVerifyCode();
                ipAddr = myUserDetails.getIpAddr();
                String email = myUserDetails.getEmail();
                if (!ObjectUtils.isEmpty(email)) {
                    return this.authenticateEmail(email, verifyCode, ipAddr);
                }
            } else if (authentication instanceof UsernamePasswordAuthenticationToken) {
                UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
                // TODO 不是的话要处理
                username = (String) authenticationToken.getPrincipal();
                rawPassword = (String) authenticationToken.getCredentials();
                myUserDetails = MyUserDetails.unauthenticated(username, rawPassword);
            }
            if (null != ipAddr) {
                if (ObjectUtils.isEmpty(verifyCode)) {
                    return Mono.error(new DisabledException("请填写验证码!"));
                }
                String s = redisService.getCacheObject(ipAddr);
                if (ObjectUtils.isEmpty(s)) {
                    return Mono.error(new DisabledException("验证码过期,请重新获取验证码!"));
                }
                if (!s.equals(verifyCode) && !s.equals(verifyCode.toLowerCase())) {
                    return Mono.error(new DisabledException("验证码有误,请重新输入!"));
                }
            }
    
            try {
                if (!ObjectUtils.isEmpty(username)) {
                    username = DESUtils.decode(username);
                }
                if (!ObjectUtils.isEmpty(rawPassword)) {
                    rawPassword = DESUtils.decode(rawPassword);
                }
            } catch (Exception e) {
                LOGGER.error("解密用户密码出错!");
                return Mono.error(new DisabledException("解密用户密码出错!"));
            }
    
            if ((ObjectUtils.isEmpty(username) || ObjectUtils.isEmpty(rawPassword))) {
                return Mono.error(new DisabledException("请填写用户名或密码"));
            }
    
            UserDetails user = null;
            UserEentity userEentity = null;
            try {
                userEentity = userService.selectUserByNameDb1(username);
                if (ObjectUtils.isEmpty(userEentity)) {
                    return Mono.error(new UsernameNotFoundException("系统无此用户,请先注册!"));
                }
                Integer errorCount = userEentity.getErrorCount();
                if (!ObjectUtils.isEmpty(errorCount) && JwtTokenUtils.LOGIN_ERROR_COUNT == errorCount) {
                    return Mono.error(new DisabledException("登陆异常次数大于" + JwtTokenUtils.LOGIN_ERROR_COUNT));
                }
                User.UserBuilder userBuilder = User.builder().passwordEncoder(passwordEncoder::encode)
                        .username(userEentity.getUsername())
                        .password(userEentity.getPassword());
                String role = userEentity.getRole();
                if (!ObjectUtils.isEmpty(role)) {
                    userBuilder.roles(role);
                }
                String url = userEentity.getUrl();
                if (!ObjectUtils.isEmpty(url)) {
                    userBuilder.authorities(url);
                }
                if (ObjectUtils.isEmpty(role) && ObjectUtils.isEmpty(url)) {
                    userBuilder.authorities(StringUtils.DEFAULT_INDEX_HTML_1);
                }
                user = userBuilder.build();
            } catch (UsernameNotFoundException ufe) {
                return Mono.error(ufe);
            }
    
            if (!user.isEnabled()) {
                return Mono.error(new DisabledException("该账户已被禁用,请联系管理员"));
            } else if (!user.isAccountNonLocked()) {
                return Mono.error(new LockedException("该账号已被锁定"));
            } else if (!user.isAccountNonExpired()) {
                return Mono.error(new AccountExpiredException("该账号已过期,请联系管理员"));
            } else if (!user.isCredentialsNonExpired()) {
                return Mono.error(new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录"));
            }
    
            //验证密码
            if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
                userService.loginPasswordErrorAdd(username);
                return Mono.error(new BadCredentialsException("密码错误:" + username));
            }
    
            final Authentication authentication1 = new UsernamePasswordAuthenticationToken(user, rawPassword, user.getAuthorities());
    
            // TODO WebFlux方式默认没有放到context中,需要手动放入
            SecurityContextHolder.getContext().setAuthentication(authentication1);
    
            return Mono.just(authentication1);
        }
    
        /**
         * 自定义处理登陆----邮箱登陆
         *
         * @param email
         * @param verifyCode
         * @param ipAddr
         * @return reactor.core.publisher.Mono<org.springframework.security.core.Authentication>
         * @author liwei
         */
        public Mono<Authentication> authenticateEmail(String email, String verifyCode, String ipAddr) {
            LOGGER.info("自定义处理登陆----邮箱登陆.........");
            if (ObjectUtils.isEmpty(ipAddr)) {
                return Mono.error(new DisabledException("系统处理邮箱出错!"));
            }
            if (ObjectUtils.isEmpty(verifyCode)) {
                return Mono.error(new DisabledException("请填写验证码!"));
            }
            String s = redisService.getCacheObject(ipAddr);
            if (ObjectUtils.isEmpty(s)) {
                return Mono.error(new DisabledException("验证码过期,请重新获取验证码!"));
            }
            if (!s.equals(verifyCode) && !s.equals(verifyCode.toLowerCase())) {
                return Mono.error(new DisabledException("验证码有误,请重新输入!"));
            }
            redisService.deleteObject(ipAddr);
    
            try {
                if (!ObjectUtils.isEmpty(email)) {
                    email = DESUtils.decode(email);
                }
            } catch (Exception e) {
                LOGGER.error("解密邮箱出错!");
                return Mono.error(new DisabledException("解密邮箱出错!"));
            }
    
            if (ObjectUtils.isEmpty(email)) {
                return Mono.error(new DisabledException("请填写邮箱"));
            }
    
            UserDetails user;
            UserEentity userEentity;
            try {
                userEentity = userService.selectUserByEmailDb1(email);
                if (ObjectUtils.isEmpty(userEentity)) {
                    return Mono.error(new DisabledException("系统无此邮箱,不支持邮箱注册"));
                }
                User.UserBuilder userBuilder = User.builder().passwordEncoder(passwordEncoder::encode)
                        .username(userEentity.getUsername())
                        .password(userEentity.getPassword());
                String role = userEentity.getRole();
                if (!ObjectUtils.isEmpty(role)) {
                    userBuilder.roles(role);
                }
                String url = userEentity.getUrl();
                if (!ObjectUtils.isEmpty(url)) {
                    userBuilder.authorities(url);
                }
                if (ObjectUtils.isEmpty(role) && ObjectUtils.isEmpty(url)) {
                    userBuilder.authorities(StringUtils.DEFAULT_INDEX_HTML_1);
                }
                user = userBuilder.build();
            } catch (UsernameNotFoundException ufe) {
                return Mono.error(ufe);
            }
            if (!user.isEnabled()) {
                return Mono.error(new DisabledException("该账户已被禁用,请联系管理员"));
            } else if (!user.isAccountNonLocked()) {
                return Mono.error(new LockedException("该账号已被锁定"));
            } else if (!user.isAccountNonExpired()) {
                return Mono.error(new AccountExpiredException("该账号已过期,请联系管理员"));
            } else if (!user.isCredentialsNonExpired()) {
                return Mono.error(new CredentialsExpiredException("该账户的登录凭证已过期,请重新登录"));
            }
    
            userService.loginPasswordErrorAdd(userEentity.getUsername());
    
            final Authentication authentication1 = new UsernamePasswordAuthenticationToken(user, userEentity.getPassword(), user.getAuthorities());
    
            // TODO WebFlux方式默认没有放到context中,需要手动放入
            SecurityContextHolder.getContext().setAuthentication(authentication1);
    
            return Mono.just(authentication1);
        }
    
    }
    
    1. 自定义鉴权处理
    点击查看代码
    package com.xiaostudy.security.config;
    
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.entity.UserEentity;
    import com.xiaostudy.security.service.UserService;
    import com.xiaostudy.security.utils.IpUtils;
    import com.xiaostudy.security.utils.JwtTokenUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.security.authentication.CredentialsExpiredException;
    import org.springframework.security.authentication.DisabledException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.authorization.AuthorizationDecision;
    import org.springframework.security.authorization.ReactiveAuthorizationManager;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.server.authorization.AuthorizationContext;
    import org.springframework.util.ObjectUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    // 自定义的鉴权服务,通过鉴权的才能继续访问某个请求。反应式授权管理器接口
    @Configuration
    public class MyReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyReactiveAuthorizationManager.class);
        @Autowired
        private UserService userService;
    
        /**
         * 实现权限验证判断
         */
        @Override
        public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) {
            LOGGER.info("---自定义的鉴权服务---MyReactiveAuthorizationManager---");
            ServerWebExchange exchange = authorizationContext.getExchange();
            ServerHttpRequest request = exchange.getRequest();
            String ipAddr = IpUtils.getIpAddr(request);
            if (!StringUtils.REQUEST_IP_WHITE_LIST.contains(ipAddr)) {
                LOGGER.debug("---自定义的鉴权服务---MyReactiveAuthorizationManager---非白名单IP不可访问");
                return Mono.error(new DisabledException(String.format("IP:%s,非白名单,不可访问" , ipAddr)));
            }
            // option请求默认放行,解决跨域问题
            if (request.getMethod().equals(HttpMethod.OPTIONS)) {
                LOGGER.debug("---自定义的鉴权服务---MyReactiveAuthorizationManager---跨域放行");
                return Mono.just(new AuthorizationDecision(true));
            }
            //请求资源
            final String url = request.getURI().getPath();
            // 白名单放行,不用登陆就可以访问
            for (String requestRulWhite : StringUtils.REQUEST_RUL_WHITE_S) {
                if ((requestRulWhite.endsWith(StringUtils.WILDCARD) && url.startsWith(requestRulWhite.substring(0, requestRulWhite.length() - StringUtils.WILDCARD.length())))
                        || requestRulWhite.equals(url)) {
                    LOGGER.debug("---自定义的鉴权服务---MyReactiveAuthorizationManager---白名单url放行");
                    return Mono.just(new AuthorizationDecision(true));
                }
            }
            final HttpHeaders requestHeaders = request.getHeaders();
            final HttpHeaders responseHeaders = exchange.getResponse().getHeaders();
            String authentication = JwtTokenUtils.getCookieAuthentication(requestHeaders);
            boolean tokenExpired = JwtTokenUtils.checkTokenAndRefreshToken(responseHeaders, authentication);
            if (!tokenExpired) {
                LOGGER.warn("token过期");
                return Mono.error(new CredentialsExpiredException("token过期,请重新登陆"));
            } else {
                LOGGER.debug("token有效");
    
            }
            return authenticationMono.map(auth ->
                    new AuthorizationDecision(this.checkAuthorities(auth, url))
            ).defaultIfEmpty(
                    new AuthorizationDecision(defaultIsToken(authentication, url))
    //                new AuthorizationDecision(false)
            );
        }
    
        // 只有token情况下处理
        private boolean defaultIsToken(String token, String url) {
            if (ObjectUtils.isEmpty(token)) {
                return false;
            }
            String username = JwtTokenUtils.getUsernameFromToken(token);
            return this.checkAuthorities(username, url);
        }
    
        //权限校验,指定的url需要对应的角色,不指定的登陆成功就可以访问
        private boolean checkAuthorities(Authentication auth, String url) {
            if (ObjectUtils.isEmpty(auth)) {
                return false;
            }
            UserDetails principal = (UserDetails) auth.getPrincipal();
            if (ObjectUtils.isEmpty(principal)) {
                return false;
            }
            return this.checkAuthorities(principal.getUsername(), url);
        }
    
        //权限校验,指定的url需要对应的角色,不指定的登陆成功就可以访问
        private boolean checkAuthorities(String username, String url) {
            LOGGER.info("---自定义的鉴权服务---url:{}---" , url);
            if (ObjectUtils.isEmpty(username)) {
                return false;
            }
            UserEentity userEentity = userService.selectUserByNameDb1(username);
            if (ObjectUtils.isEmpty(userEentity)) {
                return false;
            }
            LOGGER.info("访问的URI是:{},用户信息:{}" , url, username);
            String role = userEentity.getRole();
            if ("/web/webLogin/user1".equals(url)) {
                return "3".equals(role);
            }
            if ("/web/webLogin/useri".equals(url)) {
                return "k".equals(role);
            }
            if ("/web/webLogin/usera".equals(url)) {
                return "c".equals(role) || "k".equals(role);
            }
    
            // 非指定接口,只要登陆都有权限
            return true;
        }
    }
    
    1. 自定义处理未登陆无访问权限的返回结果
    点击查看代码
    package com.xiaostudy.security.handler;
    
    import io.netty.util.CharsetUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    // 未登陆无访问权限的返回结果
    @Component
    public class AuthEntryPointExceptionHandler extends HttpBasicServerAuthenticationEntryPoint {
        private static final Logger LOGGER = LoggerFactory.getLogger(AuthEntryPointExceptionHandler.class);
    
        @Override
        public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
            LOGGER.info("未登陆无访问权限---{}--AuthEntryPointExceptionHandler.........", exchange.getRequest().getURI().getPath());
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            String jsonString = "{\"code\":200,\"status\":4,\"msg\":\"您未登陆或登陆已过期,请先登陆!\"}";
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(jsonString.getBytes(CharsetUtil.UTF_8));
            return exchange.getResponse().writeWith(Flux.just(wrap));
        }
    }
    
    1. 自定义登出成功后操作
    点击查看代码
    package com.xiaostudy.security.handler;
    
    import com.xiaostudy.common.utils.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.server.DefaultServerRedirectStrategy;
    import org.springframework.security.web.server.ServerRedirectStrategy;
    import org.springframework.security.web.server.WebFilterExchange;
    import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
    import reactor.core.publisher.Mono;
    
    import java.net.URI;
    
    // 成功登出实现类
    @Configuration
    public class MyRedirectServerLogoutSuccessHandler implements ServerLogoutSuccessHandler {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyRedirectServerLogoutSuccessHandler.class);
        private URI logoutSuccessUrl = URI.create(StringUtils.DEFAULT_LOGOUT_SUCCESS_URL);
        private ServerRedirectStrategy redirectStrategy = new DefaultServerRedirectStrategy();
    
        public MyRedirectServerLogoutSuccessHandler() {
        }
    
        @Override
        public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
            LOGGER.info("成功登出实现类----MyRedirectServerLogoutSuccessHandler.........");
            return this.redirectStrategy.sendRedirect(exchange.getExchange(), this.logoutSuccessUrl);
        }
    
    }
    
    1. 自定义处理登录失败或其他异常访问调用
    点击查看代码
    package com.xiaostudy.security.handler;
    
    import io.netty.util.CharsetUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.security.authentication.*;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.web.server.WebFilterExchange;
    import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;
    
    // 登录失败或其他异常访问调用的自定义处理类
    @Component
    public class MyServerAuthenticationFailureHandler implements ServerAuthenticationFailureHandler {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyServerAuthenticationFailureHandler.class);
    
        private static final String USER_NOT_EXISTS = "用户不存在,请先注册!";
    
        private static final String USERNAME_PASSWORD_ERROR = "用户或密码错误!";
    
        private static final String USER_LOCKED = "用户锁定!";
        private static final String USER_ACCOUNT_EXPIRED = "账号已过期!";
        private static final String USER_CREDENTIALS_EXPIRE = "票据已过期!";
    
        @Override
        public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
            LOGGER.info("登录失败时调用的自定义处理类----MyServerAuthenticationFailureHandler.........");
            ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            if (exception instanceof UsernameNotFoundException) {
                return writeErrorMessage(response, USER_NOT_EXISTS);
            } else if (exception instanceof BadCredentialsException) {
                return writeErrorMessage(response, USERNAME_PASSWORD_ERROR);
            } else if (exception instanceof LockedException) {
                return writeErrorMessage(response, USER_LOCKED);
            } else if (exception instanceof AccountExpiredException) {
                return writeErrorMessage(response, USER_ACCOUNT_EXPIRED);
            } else if (exception instanceof CredentialsExpiredException) {
                return writeErrorMessage(response, USER_CREDENTIALS_EXPIRE);
            } else if (exception instanceof DisabledException) {
                return writeErrorMessage(response, "不可访问," + exception.getMessage());
            }
            return writeErrorMessage(response, exception.getMessage());
        }
    
        private Mono<Void> writeErrorMessage(ServerHttpResponse response, String message) {
            String jsonString = String.format("{\"code\":200,\"status\":1,\"msg\":\"%s\"}", message);
            DataBuffer buffer = response.bufferFactory().wrap(jsonString.getBytes(CharsetUtil.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
    }
    
    1. 自定义处理登陆成功后返回结果
    点击查看代码
    package com.xiaostudy.security.handler;
    
    import com.xiaostudy.security.utils.JwtTokenUtils;
    import io.netty.util.CharsetUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.server.WebFilterExchange;
    import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;
    
    // 登录成功时调用的自定义处理类
    @Component
    public class MyServerAuthenticationSuccessHandler implements ServerAuthenticationSuccessHandler {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyServerAuthenticationSuccessHandler.class);
    
        @Override
        public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
            LOGGER.info("登录成功时调用的自定义处理类----MyServerAuthenticationSuccessHandler.........");
            // 登录成功后可以放入一些参数到session中
            ServerHttpResponse response = webFilterExchange.getExchange().getResponse();
            response.setStatusCode(HttpStatus.OK);
            HttpHeaders headers = response.getHeaders();
    
            UserDetails principal = (UserDetails) authentication.getPrincipal();
            String username = principal.getUsername();
    
            String token = JwtTokenUtils.generateToken(username, null);
            headers.set(JwtTokenUtils.AUTHENTICATION, String.format("%s%s", JwtTokenUtils.BASIC_EMPTY, token));
    
            String jsonString = String.format("{\"code\":200,\"status\":0,\"msg\":\"%s您登陆成功!\"}", username);
            headers.setContentType(MediaType.APPLICATION_JSON);
            DataBuffer buffer = response.bufferFactory().wrap(jsonString.getBytes(CharsetUtil.UTF_8));
            return response.writeWith(Mono.just(buffer));
        }
    }
    
    1. 自定义处理登陆后无权限访问返回结果
    点击查看代码
    package com.xiaostudy.security.handler;
    
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.utils.JwtTokenUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
    import org.springframework.stereotype.Component;
    import org.springframework.util.ObjectUtils;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.nio.charset.StandardCharsets;
    
    // 无权限访问被拒绝时的自定义处理器。如不自己处理,默认返回403错误<br>
    @Component
    public class MyWebFluxServerAccessDeniedHandler implements ServerAccessDeniedHandler {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyWebFluxServerAccessDeniedHandler.class);
    
        @Override
        public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
            LOGGER.info("无权限访问被拒绝时的自定义处理器----MyAccessDeniedHandlerWebFlux.........");
            String username = JwtTokenUtils.getCookieUsername(exchange.getRequest().getHeaders());
            if (ObjectUtils.isEmpty(username)) {
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if (!ObjectUtils.isEmpty(authentication)) {
                    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
                    if (!ObjectUtils.isEmpty(userDetails)) {
                        username = userDetails.getUsername();
                    }
                }
            }
            if (null == username) {
                username = StringUtils.EMPTY;
            }
    
            String jsonString = String.format("{\"code\":200,\"status\":3,\"msg\":\"%s您无此资源的访问权限!\"}" , username, exchange.getRequest().getURI().getPath());
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.OK);
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            return response.writeAndFlushWith(Flux.just(Flux.just(response.bufferFactory().wrap(jsonString.getBytes(StandardCharsets.UTF_8)))));
        }
    }
    
    1. 重写存储认证信息,实时修改用户session的过期时间
    点击查看代码
    package com.xiaostudy.security.filter;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    import java.time.Duration;
    
    // 重写存储认证信息,修改session默认时效和更新会话时间
    @Configuration
    public class MyWebSessionServerSecurityContextRepository extends WebSessionServerSecurityContextRepository {
        private static final Logger LOGGER = LoggerFactory.getLogger(MyWebSessionServerSecurityContextRepository.class);
        @Value("${session.timeout}")
        private Long timeout;
    
        @Override
        public Mono<Void> save(ServerWebExchange exchange, SecurityContext context) {
            // 只有登陆时执行,并且在load()执行之后
            LOGGER.info("存储认证信息---save---url:{}", exchange.getRequest().getURI().getPath());
            return exchange.getSession()
                    .doOnNext(session -> {
                        if (context == null) {
                            session.getAttributes().remove(super.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME);
                        } else {
                            session.getAttributes().put(super.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME, context);
                            // 在这里设置过期时间 单位使用Duration类中的定义  有秒、分、天等
                            session.setMaxIdleTime(Duration.ofSeconds(timeout));
                        }
                    })
                    .flatMap(session -> session.changeSessionId());
        }
    
        @Override
        public Mono<SecurityContext> load(ServerWebExchange exchange) {
            ServerHttpRequest request = exchange.getRequest();
            String url = request.getURI().getPath();
            LOGGER.info("存储认证信息---load---url:{}", url);
            return exchange.getSession().flatMap((session) -> {
                SecurityContext context = session.getAttribute(super.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME);
                if (context == null) {
                    session.getAttributes().remove(super.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME);
                } else {
                    session.getAttributes().put(super.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME, context);
                    // 在这里设置过期时间 单位使用Duration类中的定义  有秒、分、天等
                    session.setMaxIdleTime(Duration.ofSeconds(timeout));
                }
                return Mono.justOrEmpty(context);
            });
        }
    
    }
    
    1. 主要过滤配置类
    点击查看代码
    package com.xiaostudy.security.config;
    
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.filter.MyWebSessionServerSecurityContextRepository;
    import com.xiaostudy.security.handler.AuthEntryPointExceptionHandler;
    import com.xiaostudy.security.handler.MyServerAuthenticationFailureHandler;
    import com.xiaostudy.security.handler.MyServerAuthenticationSuccessHandler;
    import com.xiaostudy.security.handler.MyWebFluxServerAccessDeniedHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
    import org.springframework.security.config.web.server.ServerHttpSecurity;
    import org.springframework.security.web.server.SecurityWebFilterChain;
    import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
    import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
    import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
    import org.springframework.web.server.WebFilter;
    
    import java.util.Iterator;
    
    @Configuration
    @EnableWebFluxSecurity
    public class SecurityWebFluxConfig {
        private static final Logger LOG = LoggerFactory.getLogger(SecurityWebFluxConfig.class);
    
        @Autowired
        private MyReactiveAuthorizationManager reactiveAuthorizationManager;
    
        @Autowired
        private AuthEntryPointExceptionHandler serverAuthenticationEntryPoint;
    
        @Autowired
        private MyServerAuthenticationSuccessHandler myServerAuthenticationSuccessHandler;
    
        @Autowired
        private MyServerAuthenticationFailureHandler myServerAuthenticationFailureHandler;
    
        @Autowired
        private MyWebFluxServerAccessDeniedHandler myWebFluxServerAccessDeniedHandler;
    
        @Autowired
        private ServerLogoutSuccessHandler logoutSuccessHandler;
        @Autowired
        private MyWebSessionServerSecurityContextRepository myWebSessionServerSecurityContextRepository;
        @Autowired
        private MyServerFormLoginAuthenticationConverter myServerFormLoginAuthenticationConverter;
        @Autowired
        private MyReactiveAuthenticationManager myReactiveAuthenticationManager;
    
        // 主要过滤配置类
        @Bean
        public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
            LOG.info("加载security 权限配置....");
            http
    //                .headers()
    //                .cors()
                    // 关闭csrf
                    .csrf().disable()
                    // 存储认证信息,这里修改session时效
                    .securityContextRepository(myWebSessionServerSecurityContextRepository)
                    // 设置登陆地址,如果是前后端分离,就不用设置,前端处理。
                    .formLogin().loginPage(StringUtils.DEFAULT_LOGOUT_HTML_1)
                    // 登陆请求方式和接口
                    .requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, StringUtils.DEFAULT_LOGIN_URL_1, StringUtils.DEFAULT_LOGIN_MAIL_URL_1))
                    // 处理登陆
                    .authenticationManager(myReactiveAuthenticationManager)
                    // 登录成功handler
                    .authenticationSuccessHandler(myServerAuthenticationSuccessHandler)
                    // 登陆失败handler
                    .authenticationFailureHandler(myServerAuthenticationFailureHandler)
    
                    // 关闭默认登录验证
                    .and().httpBasic().disable()
    
    //                .requestCache()
    
                    // 登出,设置登出请求类型和URL
                    .logout().requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, StringUtils.DEFAULT_LOGOUT_URL_1))
                    // 登出成功后自定义处理
                    .logoutSuccessHandler(logoutSuccessHandler)
    
                    // 未登陆无访问权限handler
                    .and().exceptionHandling().authenticationEntryPoint(serverAuthenticationEntryPoint)
                    // 登陆无访问权限
                    .and().exceptionHandling().accessDeniedHandler(myWebFluxServerAccessDeniedHandler)
    
                    // 自定义鉴权
    //                .and().authorizeExchange().pathMatchers(StringUtils.REQUEST_RUL_WHITE_S).permitAll()
                    .and().authorizeExchange().anyExchange().access(reactiveAuthorizationManager)
    //                .anyExchange().authenticated()
            ;
            SecurityWebFilterChain chain = http.build();
            Iterator<WebFilter> weIterable = chain.getWebFilters().toIterable().iterator();
            while (weIterable.hasNext()) {
                WebFilter f = weIterable.next();
                if (f instanceof AuthenticationWebFilter) {
                    AuthenticationWebFilter webFilter = (AuthenticationWebFilter) f;
                    //将自定义的AuthenticationConverter添加到过滤器中
                    webFilter.setServerAuthenticationConverter(myServerFormLoginAuthenticationConverter);
                }
            }
            return chain;
        }
    }
    

    上面的图,验证码和解密工具类已经抽取到公共模块

    1. 邮箱实体类
    点击查看代码
    package com.xiaostudy.security.email;
    
    import java.io.File;
    
    public class MailEntity {
        /**
         * 主题
         */
        private String subject;
        /**
         * 内容
         */
        private String content;
        /**
         * 邮箱
         */
        private String toAccount;
        /**
         * 附件
         */
        private File attachmentFile;
        /**
         * 附件文件名
         */
        private String attachmentFileName;
    
        public String getSubject() {
            return subject;
        }
    
        public void setSubject(String subject) {
            this.subject = subject;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public String getToAccount() {
            return toAccount;
        }
    
        public void setToAccount(String toAccount) {
            this.toAccount = toAccount;
        }
    
        public File getAttachmentFile() {
            return attachmentFile;
        }
    
        public void setAttachmentFile(File attachmentFile) {
            this.attachmentFile = attachmentFile;
        }
    
        public String getAttachmentFileName() {
            return attachmentFileName;
        }
    
        public void setAttachmentFileName(String attachmentFileName) {
            this.attachmentFileName = attachmentFileName;
        }
    }
    
    1. 邮箱工具类
    点击查看代码
    package com.xiaostudy.security.email;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.mail.MailProperties;
    import org.springframework.mail.SimpleMailMessage;
    import org.springframework.mail.javamail.JavaMailSender;
    import org.springframework.mail.javamail.MimeMessageHelper;
    import org.springframework.stereotype.Component;
    
    import javax.mail.MessagingException;
    import javax.mail.internet.MimeMessage;
    
    @Component
    public class MailUtils {
    
        @Autowired
        private MailProperties mailProperties;
        @Autowired
        private JavaMailSender javaMailSender;
    
        /**
         * 发送邮件,里面有判断是否发文件
         */
        public void sendMail(MailEntity mailEntity) {
            if (null != mailEntity) {
                if (null != mailEntity.getAttachmentFile() && mailEntity.getAttachmentFile().exists()) {
                    if (null == mailEntity.getAttachmentFileName()) {
                        mailEntity.setAttachmentFileName(mailEntity.getAttachmentFile().getName());
                    }
                    sendMailAttachment(mailEntity);
                } else {
                    sendSimpleMail(mailEntity);
                }
            }
        }
    
        /**
         * 发送邮件,这里只发内容,不发文件
         */
        public void sendSimpleMail(MailEntity mailEntity) {
            SimpleMailMessage mimeMessage = new SimpleMailMessage();
            mimeMessage.setFrom(mailProperties.getUsername());
            mimeMessage.setTo(mailEntity.getToAccount());
            mimeMessage.setSubject(mailEntity.getSubject());
            mimeMessage.setText(mailEntity.getContent());
            javaMailSender.send(mimeMessage);
        }
    
        /**
         * 发送邮件-附件邮件
         *
         * @param mailEntity
         */
        public boolean sendMailAttachment(MailEntity mailEntity) {
            try {
                MimeMessage mimeMessage = javaMailSender.createMimeMessage();
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
                helper.setFrom(mailProperties.getUsername());
                helper.setTo(mailEntity.getToAccount());
                helper.setSubject(mailEntity.getSubject());
                helper.setText(mailEntity.getContent(), true);
                // 增加附件名称和附件
                helper.addAttachment(mailEntity.getAttachmentFileName(), mailEntity.getAttachmentFile());
                javaMailSender.send(mimeMessage);
                return true;
            } catch (MessagingException e) {
                e.printStackTrace();
                return false;
            }
        }
    }
    
    1. 验证码接口
    点击查看代码
    package com.xiaostudy.security.controller;
    
    import com.xiaostudy.common.redis.RedisService;
    import com.xiaostudy.common.utils.DESUtils;
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.common.utils.VerifyCodeUtils;
    import com.xiaostudy.security.email.MailEntity;
    import com.xiaostudy.security.email.MailUtils;
    import com.xiaostudy.security.utils.IpUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.io.buffer.DataBuffer;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.util.ObjectUtils;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.util.concurrent.TimeUnit;
    
    @RestController
    @RequestMapping("security")
    public class VerifyCodeController {
    
        @Autowired
        private MailUtils mailUtils;
        @Autowired
        private RedisService redisService;
    
        @RequestMapping("/verifyCode")
        public Mono<Void> verifyCode(ServerWebExchange exchange) throws IOException {
            String code = VerifyCodeUtils.generateVerifyCode(4).toLowerCase();
            redisService.setCacheObject(IpUtils.getIpAddr(exchange.getRequest()), code, 60L, TimeUnit.SECONDS);
            ByteArrayOutputStream data = new ByteArrayOutputStream();
            VerifyCodeUtils.outputImage(100, 40, data, code);
            ServerHttpResponse response = exchange.getResponse();
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            DataBuffer buffer = response.bufferFactory().wrap(data.toByteArray());
    
            return response.writeWith(Flux.just(buffer));
        }
    
        @RequestMapping("/sendMailVerifyCode")
        public Mono<String> sendMailVerifyCode(ServerWebExchange exchange) {
            exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
            return exchange.getFormData().map(data -> {
                String code = VerifyCodeUtils.generateVerifyCode(4).toLowerCase();
                redisService.setCacheObject(IpUtils.getIpAddr(exchange.getRequest()), code, 5L, TimeUnit.MINUTES);
                String email = data.getFirst(StringUtils.EMAIL);
                try {
                    if (!ObjectUtils.isEmpty(email)) {
                        email = DESUtils.decode(email);
                    }
                } catch (Exception e) {
                    return "{\"code\":200,\"status\":1,\"msg\":\"解密邮箱出错!\"}";
                }
                if (ObjectUtils.isEmpty(email)) {
                    return "{\"code\":200,\"status\":1,\"msg\":\"请输入邮箱!\"}";
                }
                if (!VerifyCodeUtils.isEMail(email)) {
                    return "{\"code\":200,\"status\":1,\"msg\":\"邮箱格式不对!\"}";
                }
                if ("xxxxx@163.com".equals(email)) {
                    MailEntity mailEntity = new MailEntity();
                    mailEntity.setToAccount(email);
                    mailEntity.setSubject("登陆系统验证码");
                    mailEntity.setContent(String.format("5分钟有效,您登陆的验证码是:%s" , code));
                    mailUtils.sendMail(mailEntity);
    
                    return "{\"code\":200,\"status\":0,\"msg\":\"验证码已发送至邮箱!\"}";
                }
                // TODO
                return "{\"code\":200,\"status\":1,\"msg\":\"测试,非自己邮箱不发!\"}";
            });
        }
    
    }
    
    1. 获取当前用户名、测试动态切换数据源接口
    点击查看代码
    package com.xiaostudy.security.controller;
    
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.security.datasources.annotation.DataSource;
    import com.xiaostudy.security.datasources.enums.DataSourceNameEnum;
    import com.xiaostudy.security.entity.UserEentity;
    import com.xiaostudy.security.service.UserService;
    import com.xiaostudy.security.utils.JwtTokenUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.util.ObjectUtils;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    import java.util.stream.Collectors;
    
    @RestController
    @RequestMapping("user")
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        @GetMapping("getCurrentUserName")
        public String getCurrentUserName(Authentication authentication, ServerHttpRequest request) {
            String username = JwtTokenUtils.getCookieUsername(request.getHeaders());
            if (!ObjectUtils.isEmpty(username)) {
                return username;
            }
            if (ObjectUtils.isEmpty(authentication)) {
                authentication = SecurityContextHolder.getContext().getAuthentication();
            }
            if (ObjectUtils.isEmpty(authentication)) {
                return null;
            }
            Object principal = authentication.getPrincipal();
            if (ObjectUtils.isEmpty(principal)) {
                return null;
            }
            if (principal instanceof UserDetails) {
                return ((UserDetails) principal).getUsername();
            } else if (principal instanceof String) {
                return (String) principal;
            }
            return null;
        }
    
        @DataSource(name = DataSourceNameEnum.FIRST)
        @GetMapping("testDataSource1")
        public String testDataSource1() {
            List<UserEentity> userEentities = userService.selectUserAll();
            if (ObjectUtils.isEmpty(userEentities)) {
                return null;
            }
            return userEentities.stream().map(UserEentity::getUsername).collect(Collectors.joining(StringUtils.COMMA));
        }
    
        @DataSource(name = DataSourceNameEnum.SECOND)
        @GetMapping("testDataSource2")
        public String testDataSource2() {
            List<UserEentity> userEentities = userService.selectUserAll();
            if (ObjectUtils.isEmpty(userEentities)) {
                return null;
            }
            return userEentities.stream().map(UserEentity::getUsername).collect(Collectors.joining(StringUtils.COMMA));
        }
    }
    

    1. 删除启动类

    3. 创建Gateway服务

    1. 创建操作

    2. 父模块添加子模块

    <module>gateway</module>
    
    1. 修改pom.xml文件

    点击查看代码
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>com.xiaostudy</groupId>
            <artifactId>SpringCloud202208</artifactId>
            <version>1.0-SNAPSHOT</version>
            <relativePath>../pom.xml</relativePath>
        </parent>
        <groupId>com.xiaostudy</groupId>
        <artifactId>gateway</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>gateway</name>
        <description>gateway</description>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.xiaostudy</groupId>
                <artifactId>security</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    1. 配置文件application.properties修改为application.yml,然后配置
    点击查看代码
    server:
      port: '@gateway.port@'
    
    eureka:
      port: '@eureka.port@'
      ip: '@eureka.ip@'
      url-name: '@eureka.url.name@'
      instance:
        # 把本机IP注册到eureka而不是本机机器名
        preferIpAddress: true
        # 把本机IP注册到eureka,由下面参数组成
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
      client:
        serviceUrl:
          defaultZone: http://@eureka.user.name@:@eureka.user.password@@${eureka.ip}:${eureka.port}/${eureka.url-name}/
    
    spring:
      application:
        name: '@gateway.application.name@'
      cloud:
        loadbalancer:
          retry:
            # 关闭重试
            enabled: false
        gateway:
          routes:
            # 路由的id,没有规定规则但要求唯一,建议配合服务名
            - id: '@producer.application.name@'
              # 匹配后提供服务的路由地址
              uri: lb://@producer.application.name@
              predicates:
                - Path=/producer/** # 断言,路径相匹配的进行路由
              filters:
                # 去掉url一级前缀,例如http://localhost:9904/producer/test/getByName,等同于http://localhost:9904/test/getByName
                - StripPrefix=1
            - id: '@web.application.name@'
              # lb:协议表示开启负载均衡
              uri: lb://@web.application.name@
              predicates:
                - Path=/web/** #断言,路径相匹配的进行路由
              filters:
                - StripPrefix=1
      redis:
        # 默认值:localhost
        host: localhost
        # 默认值:6379
        port: 6379
        # 默认值:0
        database: 1
        lettuce:
          pool:
            # 连接池最大连接数(使用负值表示没有限制),默认值:8
            max-active: 20
            # 连接池中的最大空闲连接,默认值:8
            max-idle: 10
            #连接池中的最小空闲连接,默认值:0
            min-idle: 1
            # 连接池最大阻塞等待时间(使用负值表示没有限制),默认值:-1,单位:毫秒
            max-wait: 2000
    
      profiles:
        # 使用的配置文件后缀application-security.yml。一个或多个,中间英文逗号分开
        active: security
    
    
    1. 启动类添加注解
    点击查看代码
    @ComponentScan(
            basePackages = {
                    // 把security服务下的包交给spring管理
                    "com.xiaostudy.security"
                    , "com.xiaostudy.gateway"
                    , "com.xiaostudy.common"
            }
    )
    @MapperScan("com.xiaostudy.security.mapper")
    
    1. 启动

    2. 注册中心看服务

    4. feign模块添加gateway接口

    1. application-feign.yml添加配置
    此时application-feign.yml
    producer:
      application:
        name: @producer.application.name@
    gateway:
      application:
        name: @gateway.application.name@
    feign:
      client:
        config:
          default:
            # 默认是1000
            connect-timeout: 5000
            read-timeout: 5000
    
    1. 添加gateway接口
    点击查看代码
    package com.xiaostudy.feign.apis;
    
    import com.xiaostudy.feign.config.FeignConfig;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    
    @FeignClient(name = "${gateway.application.name}" , contextId = "GatewayServiceApis" , configuration = FeignConfig.class)
    public interface GatewayServiceApis {
        @GetMapping(value = "/user/getCurrentUserName")
        public String getCurrentUserName();
    
        @GetMapping(value = "/user/testDataSource1")
        public String testDataSource1();
    
        @GetMapping(value = "/user/testDataSource2")
        public String testDataSource2();
    }
    

    5. webService简单登陆

    1. 注册请求类
    点击查看代码
    package com.xiaostudy.webservice.entity;
    
    import java.io.Serializable;
    
    public class RegisterRequest implements Serializable {
        private static final Long serialVersionUID = 1L;
    
        private String userName;
        private String passWord;
        private String verifyCode;
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getPassWord() {
            return passWord;
        }
    
        public void setPassWord(String passWord) {
            this.passWord = passWord;
        }
    
        public String getVerifyCode() {
            return verifyCode;
        }
    
        public void setVerifyCode(String verifyCode) {
            this.verifyCode = verifyCode;
        }
    }
    
    1. IP工具类
    点击查看代码
    package com.xiaostudy.webservice.utils;
    
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    
    import javax.servlet.http.HttpServletRequest;
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    
    public final class IpUtils {
    
        private IpUtils() {
        }
    
        public static final String UNKNOWN = "unknown";
        public static final String LOCAL_IPV6 = "0:0:0:0:0:0:0:1";
        public static final String LOCAL_IPV4 = "127.0.0.1";
    
        public static String getIpAddr(HttpServletRequest request) {
            String ipAddress;
            try {
                ipAddress = request.getHeader("x-forwarded-for");
                if (null == ipAddress || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("Proxy-Client-IP");
                }
                if (null == ipAddress || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("WL-Proxy-Client-IP");
                }
                if (null == ipAddress || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("HTTP_CLIENT_IP");
                }
                if (null == ipAddress || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
                }
                if (null == ipAddress || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getHeader("X-Real-IP");
                }
                if (null == ipAddress || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                    ipAddress = request.getRemoteAddr();
                    if (LOCAL_IPV4.equals(ipAddress) || LOCAL_IPV6.equals(ipAddress)) {
                        // 根据网卡取本机配置的IP
                        InetAddress inet = null;
                        try {
                            inet = InetAddress.getLocalHost();
                        } catch (UnknownHostException e) {
                            e.printStackTrace();
                        }
                        ipAddress = inet.getHostAddress();
                    }
                }
                // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
                if (ipAddress != null && ipAddress.length() > 15) {
                    if (ipAddress.indexOf(',') > 0) {
                        ipAddress = ipAddress.substring(0, ipAddress.indexOf(','));
                    }
                }
            } catch (Exception e) {
                ipAddress = "";
            }
    
            return LOCAL_IPV6.equals(ipAddress) ? LOCAL_IPV4 : ipAddress;
        }
    
        public static String getIpAddr(ServerHttpRequest request) {
            HttpHeaders headers = request.getHeaders();
            String ip = headers.getFirst("x-forwarded-for");
            if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip) && ip.indexOf(",") != -1) {
                // 多次反向代理后会有多个ip值,第一个ip才是真实ip
                ip = ip.split(",")[0];
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = headers.getFirst("X-Real-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddress().getAddress().getHostAddress();
            }
            return LOCAL_IPV6.equals(ip) ? LOCAL_IPV4 : ip;
        }
    }
    
    1. 添加公共模块
    <dependency>
        <groupId>com.xiaostudy</groupId>
        <artifactId>common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    1. 登陆跳转和一些测试
    点击查看代码
    package com.xiaostudy.webservice.controller;
    
    import com.xiaostudy.common.redis.RedisService;
    import com.xiaostudy.common.utils.DESUtils;
    import com.xiaostudy.common.utils.StringUtils;
    import com.xiaostudy.feign.apis.GatewayServiceApis;
    import com.xiaostudy.webservice.entity.RegisterRequest;
    import com.xiaostudy.webservice.entity.db1.UserEentity;
    import com.xiaostudy.webservice.utils.IpUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Controller;
    import org.springframework.util.ObjectUtils;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Controller
    @RequestMapping("/webLogin")
    public class LoginController {
    
        @Autowired
        private GatewayServiceApis gatewayServiceApis;
    
        @Value("${my.gateway.ip}")
        private String ip;
    
        @Value("${my.gateway.port}")
        private String port;
        @Value("${server.port}")
        private String applicationPort;
    
        @Autowired
        private com.xiaostudy.webservice.service.db1.UserService userService1;
    
        @Autowired
        private RedisService redisService;
    
        @RequestMapping("/login")
        public String login() {
            return String.format("redirect:http://%s:%s/web/login.html" , ip, port);
        }
    
        @RequestMapping("/isLogout")
        @ResponseBody
        public String isLogout() {
            return "{\"code\":200,\"status\":0,\"msg\":\"登出成功!\"}";
        }
    
        @RequestMapping("/register")
        @ResponseBody
        public String register(HttpServletRequest request, @RequestBody RegisterRequest registerRequest) {
            String verifyCode = registerRequest.getVerifyCode();
            String userName = registerRequest.getUserName();
            String passWord = registerRequest.getPassWord();
            if (ObjectUtils.isEmpty(verifyCode)) {
                return "{\"code\":200,\"status\":1,\"msg\":\"请输入验证码!\"}";
            }
            String ipAddr = IpUtils.getIpAddr(request);
            if (ObjectUtils.isEmpty(ipAddr)) {
                return "{\"code\":200,\"status\":1,\"msg\":\"系统出错,请稍后再试!\"}";
            }
            String s = redisService.getCacheObject(ipAddr);
            if (ObjectUtils.isEmpty(s)) {
                return "{\"code\":200,\"status\":1,\"msg\":\"验证码过期,请重新获取!\"}";
            }
            if (!s.equals(verifyCode) && !s.equals(verifyCode.toLowerCase())) {
                return "{\"code\":200,\"status\":1,\"msg\":\"验证码错误,请重新输入!\"}";
            }
            try {
                if (!ObjectUtils.isEmpty(userName)) {
                    userName = DESUtils.decode(userName);
                }
                if (!ObjectUtils.isEmpty(passWord)) {
                    passWord = DESUtils.decode(passWord);
                }
            } catch (Exception e) {
                return "{\"code\":200,\"status\":1,\"msg\":\"解密用户名密码出错!\"}";
            }
            if (ObjectUtils.isEmpty(userName)) {
                return "{\"code\":200,\"status\":1,\"msg\":\"请输入用户名!\"}";
            }
            if (ObjectUtils.isEmpty(passWord)) {
                return "{\"code\":200,\"status\":1,\"msg\":\"请输入密码!\"}";
            }
            UserEentity userEentity = userService1.selectUserByUsername(userName);
            if (!ObjectUtils.isEmpty(userEentity)) {
                return String.format("{\"code\":200,\"status\":1,\"msg\":\"%s用户名已存在!\"}" , userName);
            }
            userEentity = new UserEentity();
            userEentity.setUsername(userName);
            userEentity.setPassword(passWord);
            userEentity.setErrorCount(0);
            userEentity.setUrl(StringUtils.DEFAULT_INDEX_HTML_1);
            boolean insertUser = userService1.insertUser(userEentity);
            if (!insertUser) {
                return "{\"code\":200,\"status\":1,\"msg\":\"创建用户失败!\"}";
            }
            redisService.deleteObject(ipAddr);
            return "{\"code\":200,\"status\":0,\"msg\":\"创建用户成功!\"}";
        }
    
        @RequestMapping("/getCurrentUserName")
        @ResponseBody
        public String getCurrentUserName() {
            return gatewayServiceApis.getCurrentUserName();
        }
    
        @RequestMapping("/test")
        @ResponseBody
        public String test() {
            return "不用登陆";
        }
    
        @RequestMapping("/yes")
        @ResponseBody
        public String yes() {
            return gatewayServiceApis.getCurrentUserName() + "登陆成功就可以查看,应用端口:" + applicationPort;
        }
    
        @RequestMapping("/test2")
        @ResponseBody
        public String test2() {
            return "不用登陆2";
        }
    
        @RequestMapping("/useri")
        @ResponseBody
        public String useri() {
            String currentUserName = gatewayServiceApis.getCurrentUserName();
            return String.format("你好用户%s或用户x,有角色k权限" , currentUserName);
        }
    
        @RequestMapping("/usera")
        @ResponseBody
        public String usera() {
            String currentUserName = gatewayServiceApis.getCurrentUserName();
            return String.format("你好用户%s或用户x,有角色c权限" , currentUserName);
        }
    
        @RequestMapping("/user1")
        @ResponseBody
        public String user1() {
            String currentUserName = gatewayServiceApis.getCurrentUserName();
            return String.format("你好用户%s,有角色3权限" , currentUserName);
        }
    }
    
    1. 前端-首页html
    点击查看代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>首页</title>
        <style>
            * {
                padding: 0;
                margin: 0;
                font-family: "楷体";
            }
    
            header {
                background-color: #9b9c98;
                height: 100vh;
                background-size: cover;
                background-position: center;
            }
    
            ul {
                float: right;
                list-style-type: none;
                margin: 15px;
            }
    
            ul li {
                display: inline-block;
            }
    
            ul li a {
                text-decoration: none;
                color: #fff;
                padding: 5px 20px;
                border: 1px solid transparent;
                transition: .6s ease;
                border-radius: 20px;
            }
    
            ul li a:hover {
                background-color: #fff;
                color: #000;
            }
    
            ul li.active a {
                background-color: #fff;
                color: #000;
            }
    
            .title {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
            }
    
            .title h1 {
                color: #fff;
                font-size: 70px;
                font-family: Century Gothic;
            }
        </style>
    </head>
    <body>
    <header>
        <div class="main">
            <ul>
                <li id="user">你好</li>
                <li class="active"><a href="javascript:void(0);" onclick="logout();">退出</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webUser/multiDataSource')">多数据源</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webLogin/useri')">i有权</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webLogin/usera')">a有权</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webLogin/user1')">1有权</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webLogin/yes')">登陆看yes</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webLogin/test')">不用登看test</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webLogin/test2')">不用登陆看test2</a></li>
                <li><a href="javascript:void(0);" onclick="index('/producer/producerTest/getByName')">直接访问producer</a></li>
                <li><a href="javascript:void(0);" onclick="index('/web/webUser/getProducerTest')">登陆看getTest</a></li>
            </ul>
        </div>
        <div class="title">
            <h1><span style="color: crimson;">My</span> Homepage</h1>
        </div>
    </header>
    </body>
    
    <script type="application/javascript">
        window.onload = getCurrentUserName;
        var userName = "";
        var Authorization = "";
        var authorizationName = "Authorization";
        var refreshTokenFlag = "RefreshTokenFlag";
        var TOKEN_REFRESH_YES = "1";
    
        function getHeader() {
            var req = new XMLHttpRequest();
            req.open('GET', document.location.href, false);
            req.send(null);
            var refreshTokenFlagValue = req.getResponseHeader(refreshTokenFlag);
            if (TOKEN_REFRESH_YES === refreshTokenFlagValue) {
                Authorization = req.getResponseHeader(authorizationName);
                setCookie(authorizationName, Authorization, document.location.href);
            }
        }
    
        function getCurrentUserName() {
            userName = getCookie("username");
            Authorization = getCookie(authorizationName);
            getHeader();
            // if (undefined === userName || "" == userName || null == userName) {
                //步骤一:创建异步对象
                var xhr = new XMLHttpRequest();
                //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
                xhr.open('get', '/web/webLogin/getCurrentUserName');
                //步骤三:发送请求
                xhr.send();
                //步骤四:注册事件 onreadystatechange 状态改变就会调用
                xhr.onreadystatechange = function () {
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                        userName = xhr.responseText;
                        document.getElementById("user").innerText = "你好:" + userName + "!";
    
                        var refreshTokenFlagValue = xhr.getResponseHeader(refreshTokenFlag);
                        if (TOKEN_REFRESH_YES === refreshTokenFlagValue) {
                            Authorization = xhr.getResponseHeader(authorizationName);
                            setCookie(authorizationName, Authorization, "/web/index.html");
                        }
                    }
                }
            // } else {
            //     document.getElementById("user").innerText = "你好:" + userName + "!";
            // }
        }
    
        function logout() {
            //步骤一:创建异步对象
            var xhr = new XMLHttpRequest();
            //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
            xhr.open('get', '/web/webLogin/logout');
            //步骤三:发送请求
            xhr.send();
            //步骤四:注册事件 onreadystatechange 状态改变就会调用
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                    var jsonStr = xhr.responseText;
                    jsonStr = eval("(" + jsonStr + ")");
                    var code = jsonStr.code;
                    if (code != undefined && 200 === code) {
                        var status = jsonStr.status;
                        if (status != undefined && 0 === status) {
                            location.replace("/web/login.html");
                        } else {
                            alert(jsonStr.msg);
                        }
                    } else {
                        // alert("登陆异常");
                    }
                } else {
                    console.log("登出异常");
                }
            }
        }
    
        function index(url) {
            //步骤一:创建异步对象
            var xhr = new XMLHttpRequest();
            //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
            xhr.open('get', url);
            xhr.setRequestHeader(authorizationName, Authorization);
            xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
            //步骤三:发送请求
            xhr.send();
            //步骤四:注册事件 onreadystatechange 状态改变就会调用
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                    var jsonStr = xhr.responseText;
                    console.log(jsonStr);
                } else {
                    // console.log("异常,状态非200");
                }
            }
        }
    
        function setCookie(name, value, url) {
            /*
            *--------------- setCookie(name,value) -----------------
            * setCookie(name,value)
            * 功能:设置得变量name的值
            * 参数:name,字符串;value,字符串.
            * 实例:setCookie('username','baobao')
            *--------------- setCookie(name,value) -----------------
            */
            var Days = 30; //此 cookie 将被保存 30 天
            var exp = new Date();
            exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
            document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
            location.href = url; //接收页面.
        }
    
        function getCookie(name) {
            /*
            * getCookie(name)
            * 功能:取得变量name的值
            * 参数:name,字符串.
            * 实例:alert(getCookie("username"));
            */
            var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)"));
            if (arr != null) {
                console.log(arr);
                return unescape(arr[2]);
            }
            return null;
        }
    </script>
    </html>
    
    1. 前端-普通账号密码登陆
    点击查看代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>登陆</title>
        <style>
            body {
                background: #353f42;
            }
    
            * {
                padding: 0;
                margin: 0;
            }
    
            .main {
                margin: 0 auto;
                padding-left: 25px;
                padding-right: 25px;
                padding-top: 15px;
                 350px;
                /*height: 350px;*/
                height: 430px;
                background: #FFFFFF;
                /*以下css用于让登录表单垂直居中在界面,可删除*/
                position: absolute;
                top: 50%;
                left: 50%;
                margin-top: -175px;
                margin-left: -175px;
            }
    
            .title {
                 100%;
                height: 40px;
                line-height: 40px;
            }
    
            .title span {
                font-size: 18px;
                color: #353f42;
            }
    
            .title-msg {
                 100%;
                height: 64px;
                line-height: 64px;
            }
    
            .title:hover {
                cursor: default;
            }
    
            .title-msg:hover {
                cursor: default;
            }
    
            .title-msg span {
                font-size: 12px;
                color: #707472;
            }
    
            .input-content {
                 100%;
                /*height: 120px;*/
                height: 200px;
            }
    
            .input-content input {
                 330px;
                height: 40px;
                border: 1px solid #dad9d6;
                background: #ffffff;
                padding-left: 10px;
                padding-right: 10px;
            }
    
            .enter-btn {
                 350px;
                height: 40px;
                color: #fff;
                background: #0bc5de;
                line-height: 40px;
                text-align: center;
                border: 0px;
            }
    
            .foor {
                 100%;
                height: auto;
                color: #9b9c98;
                font-size: 12px;
                margin-top: 20px;
            }
    
            .enter-btn:hover {
                cursor: pointer;
                background: #1db5c9;
            }
    
            .foor div:hover {
                cursor: pointer;
                color: #484847;
                font-weight: 600;
            }
    
            .left {
                float: left;
            }
    
            .right {
                float: right;
            }
    
        </style>
    </head>
    
    <body>
    <div class="main">
        <div class="title">
            <span>密码登录</span>
        </div>
    
        <div class="title-msg">
            <span>请输入登录账户和密码</span>
        </div>
    
        <!--输入框-->
        <div class="input-content">
            <!--autoFocus-->
            <div>
                <input type="text" autocomplete="off"
                       placeholder="用户名" name="username" id="username" required/>
            </div>
    
            <div style="margin-top: 16px">
                <input type="password"
                       autocomplete="off" placeholder="登录密码" name="password" id="password" required maxlength="32"/>
            </div>
            <div style="margin-top: 16px">
                <img src="/security/verifyCode" title="看不清,请点我" onclick="refresh(this)" onmouseover="mouseover(this)"/>
                <input type="text" class="form-control" name="verifyCode" id="verifyCode" required="required" placeholder="验证码" autocomplete="off">
            </div>
        </div>
    
        <!--登入按钮-->
        <div style="text-align: center;margin-top: 30px;">
            <button type="submit" class="enter-btn" onclick="login()">登录</button>
        </div>
    
        <div class="foor">
            <div class="left" onclick="loginMail()"><span>邮箱登陆</span></div>
            <div class="right" onclick="register()"><span>注册账户</span></div>
        </div>
    </div>
    
    <!-- 引入 CDN Crypto.js 开始 AES加密 注意引入顺序 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-base64.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/md5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/evpkdf.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/cipher-core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/aes.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/pad-pkcs7.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/mode-ecb.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-utf8.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-hex.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/tripledes.js"></script>
    <!-- 引入 CDN Crypto.js 结束 -->
    
    <script type="application/javascript">
        function loginMail() {
            location.replace("/web/loginMail.html");
        }
        function register() {
            location.replace("/web/register.html");
        }
        function refresh(obj) {
            obj.src = "/security/verifyCode?" + Math.random();
        }
    
        function mouseover(obj) {
            obj.style.cursor = "pointer";
        }
    
        var authorizationName = "Authorization";
        function login() {
            var username1 = document.getElementById("username").value;
            var password = document.getElementById("password").value;
            var verifyCode = document.getElementById("verifyCode").value;
            var username = encryptByDES(username1);
            password = encryptByDES(password);
            var indexUrl = "/web/index.html";
            //步骤一:创建异步对象
            var xhr = new XMLHttpRequest();
            //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
    
            xhr.open('post', '/web/webLogin/form');
            xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
            xhr.send("userName=" + username + "&passWord=" + password + "&verifyCode=" + verifyCode + "&_t=" + new Date().getTime());
    
    
            //步骤四:注册事件 onreadystatechange 状态改变就会调用
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                    var jsonStr = xhr.responseText;
                    jsonStr = eval("(" + jsonStr + ")");
                    var code = jsonStr.code;
                    if (code != undefined && 200 === code) {
                        var status = jsonStr.status;
                        if (status != undefined && 0 === status) {
                            location.replace(indexUrl);
                            setCookie("username", username1, indexUrl);
                            setCookie(authorizationName, xhr.getResponseHeader(authorizationName), indexUrl);
                        } else {
                            alert(jsonStr.msg);
                        }
                    } else {
                        // alert("登陆异常");
                    }
                } else {
                    // alert("登陆异常");
                }
            }
        }
    
        function setCookie(name, value, url) {
            /*
            *--------------- setCookie(name,value) -----------------
            * setCookie(name,value)
            * 功能:设置得变量name的值
            * 参数:name,字符串;value,字符串.
            * 实例:setCookie('username','baobao')
            *--------------- setCookie(name,value) -----------------
            */
            var Days = 30; //此 cookie 将被保存 30 天
            var exp = new Date();
            exp.setTime(exp.getTime() + Days*24*60*60*1000);
            document.cookie = name + "="+ escape(value) + ";expires=" + exp.toGMTString();
            location.href = url; //接收页面.
        }
    
        var cryptoJSKey = CryptoJS.enc.Utf8.parse('mwPZ7ISbC!ox6@7cP*^…5@%$)2*V');
        var cryptoJSIv = CryptoJS.enc.Utf8.parse('mwPZ7C!n');
    
        function encryptBy(username, password) {
            let message = username + ':' + password;
    
            return encryptByDES(message);
        }
    
        //base64 账号加密码
        function encryptByDES(message) {
            let option = {
                iv: cryptoJSIv,
                mode: CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            }
            let encrypted = CryptoJS.TripleDES.encrypt(message, cryptoJSKey, option);
            return encrypted.ciphertext.toString().toUpperCase();
        }
    </script>
    </body>
    
    1. 前端-邮箱登陆
    点击查看代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>登陆</title>
        <style>
            body {
                background: #353f42;
            }
    
            * {
                padding: 0;
                margin: 0;
            }
    
            .main {
                margin: 0 auto;
                padding-left: 25px;
                padding-right: 25px;
                padding-top: 15px;
                 350px;
                height: 580px;
                background: #FFFFFF;
                /*以下css用于让登录表单垂直居中在界面,可删除*/
                position: absolute;
                top: 40%;
                left: 50%;
                margin-top: -175px;
                margin-left: -175px;
            }
    
            .title {
                 100%;
                height: 40px;
                line-height: 40px;
            }
    
            .title span {
                font-size: 18px;
                color: #353f42;
            }
    
            .title-msg {
                 100%;
                height: 64px;
                line-height: 64px;
            }
    
            .title:hover {
                cursor: default;
            }
    
            .title-msg:hover {
                cursor: default;
            }
    
            .title-msg span {
                font-size: 12px;
                color: #707472;
            }
    
            .input-content {
                 100%;
                height: 360px;
            }
    
            .input-content input {
                 330px;
                height: 40px;
                border: 1px solid #dad9d6;
                background: #ffffff;
                padding-left: 10px;
                padding-right: 10px;
            }
    
            .enter-btn {
                margin-top: 30px;
                 350px;
                height: 40px;
                color: #fff;
                background: #CCCCCC;
                line-height: 40px;
                text-align: center;
                border: 0px;
                cursor: not-allowed;
            }
    
            .foor {
                 100%;
                height: auto;
                color: #9b9c98;
                font-size: 12px;
                margin-top: 20px;
            }
    
            .foor div:hover {
                cursor: pointer;
                color: #484847;
                font-weight: 600;
            }
    
            .left {
                float: left;
            }
    
            .right {
                float: right;
            }
    
            /*滑块开始*/
            .container {
                 350px;
                margin: 16px auto;
            }
    
            #msg {
                 100%;
                line-height: 40px;
                font-size: 14px;
                text-align: center;
            }
    
            a:link,
            a:visited,
            a:hover,
            a:active {
                margin-left: 100px;
                color: #0366D6;
            }
    
            .block {
                position: absolute;
                left: 0;
                top: 0;
            }
    
            .sliderContainer {
                position: relative;
                text-align: center;
                 350px;
                height: 40px;
                line-height: 40px;
                margin-top: 15px;
                background: #f7f9fa;
                color: #45494c;
                border: 1px solid #e4e7eb;
            }
    
            .sliderContainer_active .slider {
                height: 38px;
                top: -1px;
                border: 1px solid #1991FA;
            }
    
            .sliderContainer_active .sliderMask {
                height: 38px;
                border- 1px;
            }
    
            .sliderContainer_success .slider {
                height: 38px;
                top: -1px;
                border: 1px solid #52CCBA;
                background-color: #52CCBA !important;
            }
    
            .sliderContainer_success .sliderMask {
                height: 38px;
                border: 1px solid #52CCBA;
                background-color: #D2F4EF;
            }
    
            .sliderContainer_success .sliderIcon {
                background-position: 0 0 !important;
            }
    
            .sliderContainer_fail .slider {
                height: 38px;
                top: -1px;
                border: 1px solid #f57a7a;
                background-color: #f57a7a !important;
            }
    
            .sliderContainer_fail .sliderMask {
                height: 38px;
                border: 1px solid #f57a7a;
                background-color: #fce1e1;
            }
    
            .sliderContainer_fail .sliderIcon {
                background-position: 0 -83px !important;
            }
    
            .sliderContainer_active .sliderText,
            .sliderContainer_success .sliderText,
            .sliderContainer_fail .sliderText {
                display: none;
            }
    
            .sliderMask {
                position: absolute;
                left: 0;
                top: 0;
                height: 40px;
                border: 0 solid #1991FA;
                background: #D1E9FE;
            }
    
            .slider {
                position: absolute;
                top: 0;
                left: 0;
                 40px;
                height: 40px;
                background: #fff;
                box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
                cursor: pointer;
                transition: background .2s linear;
            }
    
            .slider:hover {
                background: #1991FA;
            }
    
            .slider:hover .sliderIcon {
                background-position: 0 -13px;
            }
    
            .sliderIcon {
                position: absolute;
                top: 15px;
                left: 13px;
                 14px;
                height: 11px;
                background: #f57a7a;
                background-size: 20px 14px;
            }
    
            .refreshIcon {
                position: absolute;
                right: 0;
                top: 0;
                 34px;
                height: 34px;
                cursor: pointer;
                background: url(img/refresh.png) 50% 50%;
                background-size: 30px 30px;
            }
    
            /*滑块结束*/
    
        </style>
    </head>
    
    <body>
    <div class="main">
        <div class="title">
            <span>邮箱登录</span>
        </div>
    
        <div class="title-msg">
            <span>请输入邮箱获取验证码</span>
        </div>
    
        <!--输入框-->
        <div class="input-content">
            <div>
                <input type="text" autocomplete="off"
                       placeholder="邮箱" name="username" id="email" required/>
            </div>
            <div style="margin-top: 16px">
                <div class="clear"></div>
                <input name="code" type="text" class="form-control" id="code" placeholder="请输入验证码" autocomplete="off">
                <input style=" 200px;" type="button" value="发送验证码" id="send" onclick="onclickSend()">
                <span id="smscode_info" class="res-error"></span>
            </div>
    
            <div class="container" style="margin-top: 16px">
                <div id="captcha" style="position: relative"></div>
            </div>
        </div>
    
        <!--登入按钮-->
        <div>
            <button type="submit" class="enter-btn" onclick="login()" id="submit" disabled>登录</button>
        </div>
    
        <div class="foor">
            <div class="left" onclick="loginHtml()"><span>账号登陆</span></div>
    
            <div class="right" onclick="register()"><span>注册账户</span></div>
        </div>
    </div>
    
    <!-- 引入 CDN Crypto.js 开始 AES加密 注意引入顺序 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-base64.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/md5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/evpkdf.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/cipher-core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/aes.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/pad-pkcs7.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/mode-ecb.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-utf8.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-hex.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/tripledes.js"></script>
    <!-- 引入 CDN Crypto.js 结束 -->
    
    <script type="application/javascript">
        function loginHtml() {
            location.replace("/web/login.html");
        }
    
        function register() {
            location.replace("/web/register.html");
        }
    
        function onclickSend() {
            var email = document.getElementById("email").value;
            if ('' == email) {
                alert("请填写邮箱");
                return;
            }
            email = encryptByDES(email);
            var info = "秒后重新发送";
            var num = 6;
            var send = document.getElementById("send");
            send.setAttribute('value', num + info);
            send.setAttribute('disabled', 'true');
            send.removeAttribute('onclick');
            var t = setInterval(() => {
                num -= 1;
                var send = document.getElementById("send");
                send.setAttribute('value', num + info);
                if (num == 0) {
                    clearInterval(t);
                    send.setAttribute('value', '发送验证码');
                    send.setAttribute('onclick', 'onclickSend()');
                    send.removeAttribute('disabled');
                }
            }, 1000);
    
            //步骤一:创建异步对象
            var xhr = new XMLHttpRequest();
            //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
    
            xhr.open('post', '/security/sendMailVerifyCode');
            xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
            xhr.send("email=" + email + "&_t=" + new Date().getTime());
    
    
            //步骤四:注册事件 onreadystatechange 状态改变就会调用
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                    var jsonStr = xhr.responseText;
                    jsonStr = eval("(" + jsonStr + ")");
                    var code = jsonStr.code;
                    if (code != undefined && 200 === code) {
                        var status = jsonStr.status;
                        if (status != undefined && 0 === status) {
                            info = "秒后重新发送,已发送至邮箱";
                        } else {
                            alert(jsonStr.msg);
                        }
                    } else {
                        // alert("登陆异常");
                    }
                } else {
                    // alert("登陆异常");
                }
            }
        }
    
        var authorizationName = "Authorization";
    
        function login() {
            var email = document.getElementById("email").value;
            if ('' == email) {
                alert("请填写邮箱");
                return;
            }
            var verifyCode = document.getElementById("code").value;
            if ('' == verifyCode) {
                alert("请填写验证码");
                return;
            }
            email = encryptByDES(email);
            var indexUrl = "/web/index.html";
            //步骤一:创建异步对象
            var xhr = new XMLHttpRequest();
            //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
    
            xhr.open('post', '/web/webLogin/emailLogin');
            xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
            xhr.send("email=" + email + "&verifyCode=" + verifyCode + "&_t=" + new Date().getTime());
    
    
            //步骤四:注册事件 onreadystatechange 状态改变就会调用
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                    var jsonStr = xhr.responseText;
                    jsonStr = eval("(" + jsonStr + ")");
                    var code = jsonStr.code;
                    if (code != undefined && 200 === code) {
                        var status = jsonStr.status;
                        if (status != undefined && 0 === status) {
                            location.replace(indexUrl);
                            setCookie(authorizationName, xhr.getResponseHeader(authorizationName), indexUrl);
                        } else {
                            alert(jsonStr.msg);
                        }
                    } else {
                        // alert("登陆异常");
                    }
                } else {
                    // alert("登陆异常");
                }
            }
        }
    
        function setCookie(name, value, url) {
            /*
            *--------------- setCookie(name,value) -----------------
            * setCookie(name,value)
            * 功能:设置得变量name的值
            * 参数:name,字符串;value,字符串.
            * 实例:setCookie('username','baobao')
            *--------------- setCookie(name,value) -----------------
            */
            var Days = 30; //此 cookie 将被保存 30 天
            var exp = new Date();
            exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);
            document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
            location.href = url; //接收页面.
        }
    
        var cryptoJSKey = CryptoJS.enc.Utf8.parse('mwPZ7ISbC!ox6@7cP*^…5@%$)2*V');
        var cryptoJSIv = CryptoJS.enc.Utf8.parse('mwPZ7C!n');
    
        function encryptBy(username, password) {
            let message = username + ':' + password;
    
            return encryptByDES(message);
        }
    
        //base64 账号加密码
        function encryptByDES(message) {
            let option = {
                iv: cryptoJSIv,
                mode: CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            }
            let encrypted = CryptoJS.TripleDES.encrypt(message, cryptoJSKey, option);
            return encrypted.ciphertext.toString().toUpperCase();
        }
    
        // ==============================================================滑动开始===========================
        (function(window) {
            const l = 42, // 滑块边长
                r = 10, // 滑块半径
                w = 350, // canvas宽度
                h = 155, // canvas高度
                PI = Math.PI
            const L = l + r * 2 // 滑块实际边长
    
            function getRandomNumberByRange(start, end) {
                return Math.round(Math.random() * (end - start) + start)
            }
    
            function createCanvas(width, height) {
                const canvas = createElement('canvas')
                canvas.width = width
                canvas.height = height
                return canvas
            }
    
            function createImg(onload) {
                const img = createElement('img')
                img.crossOrigin = "Anonymous"
                img.onload = onload
                img.onerror = () => {
                    img.src = getRandomImg()
                }
                img.src = getRandomImg()
                return img
            }
    
            function createElement(tagName) {
                return document.createElement(tagName)
            }
    
            function addClass(tag, className) {
                tag.classList.add(className)
            }
    
            function removeClass(tag, className) {
                tag.classList.remove(className)
            }
    
            function getRandomImg() {
                return 'https://picsum.photos/300/150/?image=' + getRandomNumberByRange(0, 100)
            }
    
            function draw(ctx, operation, x, y) {
                ctx.beginPath()
                ctx.moveTo(x, y)
                ctx.lineTo(x + l / 2, y)
                ctx.arc(x + l / 2, y - r + 2, r, 0, 2 * PI)
                ctx.lineTo(x + l / 2, y)
                ctx.lineTo(x + l, y)
                ctx.lineTo(x + l, y + l / 2)
                ctx.arc(x + l + r - 2, y + l / 2, r, 0, 2 * PI)
                ctx.lineTo(x + l, y + l / 2)
                ctx.lineTo(x + l, y + l)
                ctx.lineTo(x, y + l)
                ctx.lineTo(x, y)
                ctx.fillStyle = '#fff'
                ctx[operation]()
                ctx.beginPath()
                ctx.arc(x, y + l / 2, r, 1.5 * PI, 0.5 * PI)
                ctx.globalCompositeOperation = "xor"
                ctx.fill()
            }
    
            function sum(x, y) {
                return x + y
            }
    
            function square(x) {
                return x * x
            }
    
            class jigsaw {
                constructor(el, success, fail) {
                    this.el = el
                    this.success = success
                    this.fail = fail
                }
    
                init() {
                    this.initDOM()
                    this.initImg()
                    this.draw()
                    this.bindEvents()
                }
    
                initDOM() {
                    const canvas = createCanvas(w, h) // 画布
                    const block = canvas.cloneNode(true) // 滑块
                    const sliderContainer = createElement('div')
                    const refreshIcon = createElement('div')
                    const sliderMask = createElement('div')
                    const slider = createElement('div')
                    const sliderIcon = createElement('span')
                    const text = createElement('span')
    
                    block.className = 'block'
                    sliderContainer.className = 'sliderContainer'
                    refreshIcon.className = 'refreshIcon'
                    sliderMask.className = 'sliderMask'
                    slider.className = 'slider'
                    sliderIcon.className = 'sliderIcon'
                    text.innerHTML = '向右滑动滑块填充拼图'
                    text.className = 'sliderText'
    
                    const el = this.el
                    el.appendChild(canvas)
                    el.appendChild(refreshIcon)
                    el.appendChild(block)
                    slider.appendChild(sliderIcon)
                    sliderMask.appendChild(slider)
                    sliderContainer.appendChild(sliderMask)
                    sliderContainer.appendChild(text)
                    el.appendChild(sliderContainer)
    
                    Object.assign(this, {
                        canvas,
                        block,
                        sliderContainer,
                        refreshIcon,
                        slider,
                        sliderMask,
                        sliderIcon,
                        text,
                        canvasCtx: canvas.getContext('2d'),
                        blockCtx: block.getContext('2d')
                    })
                }
    
                initImg() {
                    const img = createImg(() => {
                        this.canvasCtx.drawImage(img, 0, 0, w, h)
                        this.blockCtx.drawImage(img, 0, 0, w, h)
                        const y = this.y - r * 2 + 2
                        const ImageData = this.blockCtx.getImageData(this.x, y, L, L)
                        this.block.width = L
                        this.blockCtx.putImageData(ImageData, 0, y)
                    })
                    this.img = img
                }
    
                draw() {
                    // 随机创建滑块的位置
                    this.x = getRandomNumberByRange(L + 10, w - (L + 10))
                    this.y = getRandomNumberByRange(10 + r * 2, h - (L + 10))
                    draw(this.canvasCtx, 'fill', this.x, this.y)
                    draw(this.blockCtx, 'clip', this.x, this.y)
                }
    
                clean() {
                    this.canvasCtx.clearRect(0, 0, w, h)
                    this.blockCtx.clearRect(0, 0, w, h)
                    this.block.width = w
                }
    
                bindEvents() {
                    this.el.onselectstart = () => false
                    this.refreshIcon.onclick = () => {
                        this.reset()
                    }
    
                    let originX, originY, trail = [],
                        isMouseDown = false
                    this.slider.addEventListener('mousedown', function(e) {
                        originX = e.x, originY = e.y
                        isMouseDown = true
                    })
                    document.addEventListener('mousemove', (e) => {
                        if(!isMouseDown) return false
                        const moveX = e.x - originX
                        const moveY = e.y - originY
                        if(moveX < 0 || moveX + 38 >= w) return false
                        this.slider.style.left = moveX + 'px'
                        var blockLeft = (w - 40 - 20) / (w - 40) * moveX
                        this.block.style.left = blockLeft + 'px'
    
                        addClass(this.sliderContainer, 'sliderContainer_active')
                        this.sliderMask.style.width = moveX + 'px'
                        trail.push(moveY)
                    })
                    document.addEventListener('mouseup', (e) => {
                        if(!isMouseDown) return false
                        isMouseDown = false
                        if(e.x == originX) return false
                        removeClass(this.sliderContainer, 'sliderContainer_active')
                        this.trail = trail
                        const {
                            spliced,
                            TuringTest
                        } = this.verify()
                        if(spliced) {
                            if(TuringTest) {
                                addClass(this.sliderContainer, 'sliderContainer_success')
                                this.success && this.success()
                            } else {
                                addClass(this.sliderContainer, 'sliderContainer_fail')
                                this.text.innerHTML = '再试一次'
                                this.reset()
                            }
                        } else {
                            // alert("验证失败");
                            addClass(this.sliderContainer, 'sliderContainer_fail')
                            this.fail && this.fail();
                            //验证失败后,1秒后重新加载图片
                            setTimeout(() => {
                                this.reset()
                            }, 1000)
                        }
                    })
                }
    
                verify() {
                    const arr = this.trail // 拖动时y轴的移动距离
                    const average = arr.reduce(sum) / arr.length // 平均值
                    const deviations = arr.map(x => x - average) // 偏差数组
                    const stddev = Math.sqrt(deviations.map(square).reduce(sum) / arr.length) // 标准差
                    const left = parseInt(this.block.style.left)
                    return {
                        spliced: Math.abs(left - this.x) < 10,
                        TuringTest: average !== stddev, // 只是简单的验证拖动轨迹,相等时一般为0,表示可能非人为操作
                    }
                }
    
                reset() {
                    this.sliderContainer.className = 'sliderContainer'
                    this.slider.style.left = 0
                    this.block.style.left = 0
                    this.sliderMask.style.width = 0
                    this.clean()
                    this.img.src = getRandomImg()
                    this.draw()
                }
    
            }
    
            window.jigsaw = {
                init: function(element, success, fail) {
                    new jigsaw(element, success, fail).init()
                }
            }
        }(window))
    
        jigsaw.init(document.getElementById('captcha'), function() {
            var slider = document.querySelector('.slider');
            slider.setAttribute('disabled', 'true');
            var submit = document.querySelector('.enter-btn');
            submit.style.background = '#0bc5de';
            submit.style.setProperty('cursor', 'pointer');
            submit.removeAttribute('disabled');
        })
        // ==============================================================滑动结束===========================
    </script>
    </body>
    

    图片

    1. 前端-用户注册
    点击查看代码
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>注册</title>
        <style>
            body {
                background: #353f42;
            }
    
            * {
                padding: 0;
                margin: 0;
            }
    
            .main {
                margin: 0 auto;
                padding-left: 25px;
                padding-right: 25px;
                padding-top: 15px;
                 350px;
                /*height: 350px;*/
                height: 430px;
                background: #FFFFFF;
                /*以下css用于让登录表单垂直居中在界面,可删除*/
                position: absolute;
                top: 50%;
                left: 50%;
                margin-top: -175px;
                margin-left: -175px;
            }
    
            .title {
                 100%;
                height: 40px;
                line-height: 40px;
            }
    
            .title span {
                font-size: 18px;
                color: #353f42;
            }
    
            .title-msg {
                 100%;
                height: 64px;
                line-height: 64px;
            }
    
            .title:hover {
                cursor: default;
            }
    
            .title-msg:hover {
                cursor: default;
            }
    
            .title-msg span {
                font-size: 12px;
                color: #707472;
            }
    
            .input-content {
                 100%;
                /*height: 120px;*/
                height: 200px;
            }
    
            .input-content input {
                 330px;
                height: 40px;
                border: 1px solid #dad9d6;
                background: #ffffff;
                padding-left: 10px;
                padding-right: 10px;
            }
    
            .enter-btn {
                 350px;
                height: 40px;
                color: #fff;
                background: #0bc5de;
                line-height: 40px;
                text-align: center;
                border: 0px;
            }
    
            .foor {
                 100%;
                height: auto;
                color: #9b9c98;
                font-size: 12px;
                margin-top: 20px;
            }
    
            .enter-btn:hover {
                cursor: pointer;
                background: #1db5c9;
            }
    
            .foor div:hover {
                cursor: pointer;
                color: #484847;
                font-weight: 600;
            }
    
            .left {
                float: left;
            }
    
            .right {
                float: right;
            }
    
        </style>
    </head>
    
    <body>
    <div class="main">
        <div class="title">
            <span>用户注册</span>
        </div>
    
        <div class="title-msg">
            <span>请输入账户和密码</span>
        </div>
    
        <!--输入框-->
        <div class="input-content">
            <!--autoFocus-->
            <div>
                <input type="text" autocomplete="off"
                       placeholder="用户名" name="username" id="username" required/>
            </div>
    
            <div style="margin-top: 16px">
                <input type="password"
                       autocomplete="off" placeholder="登录密码" name="password" id="password" required maxlength="32"/>
            </div>
            <div style="margin-top: 16px">
                <img src="/security/verifyCode" title="看不清,请点我" onclick="refresh(this)" onmouseover="mouseover(this)"/>
                <input type="text" class="form-control" name="verifyCode" id="verifyCode" required="required" placeholder="验证码">
            </div>
        </div>
    
        <!--登入按钮-->
        <div style="text-align: center;margin-top: 30px;">
            <button type="submit" class="enter-btn" onclick="register()">注册</button>
        </div>
    
        <div class="foor">
            <div class="right" onclick="login()"><span>返回登陆</span></div>
        </div>
    </div>
    
    <!-- 引入 CDN Crypto.js 开始 AES加密 注意引入顺序 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-base64.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/md5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/evpkdf.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/cipher-core.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/aes.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/pad-pkcs7.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/mode-ecb.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-utf8.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/enc-hex.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.3.0/tripledes.js"></script>
    <!-- 引入 CDN Crypto.js 结束 -->
    
    <script type="application/javascript">
        function login() {
            location.replace("/web/login.html");
        }
        function refresh(obj) {
            obj.src = "/security/verifyCode?" + Math.random();
        }
    
        function mouseover(obj) {
            obj.style.cursor = "pointer";
        }
    
        function register() {
            var username1 = document.getElementById("username").value;
            var password = document.getElementById("password").value;
            var verifyCode = document.getElementById("verifyCode").value;
            var username = encryptByDES(username1);
            password = encryptByDES(password);
            var indexUrl = "/web/login.html";
            //步骤一:创建异步对象
            var xhr = new XMLHttpRequest();
            //步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
    
            xhr.open('post', '/web/webLogin/register');
            xhr.setRequestHeader('Content-Type', 'application/json;charset=utf8');
            var para=JSON.stringify({"userName":username,"passWord":password,"verifyCode":verifyCode});
            xhr.send(para);
    
            //步骤四:注册事件 onreadystatechange 状态改变就会调用
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    //步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的    console.log(xhr.responseText);//输入相应的内容    }
                    var jsonStr = xhr.responseText;
                    jsonStr = eval("(" + jsonStr + ")");
                    var code = jsonStr.code;
                    if (code != undefined && 200 === code) {
                        var status = jsonStr.status;
                        if (status != undefined && 0 === status) {
                            alert("注册成功,请前往登陆!");
                            // location.replace(indexUrl);
                        } else {
                            alert(jsonStr.msg);
                        }
                    } else {
                        // alert("登陆异常");
                    }
                } else {
                    // alert("登陆异常");
                }
            }
        }
    
        var cryptoJSKey = CryptoJS.enc.Utf8.parse('mwPZ7ISbC!ox6@7cP*^…5@%$)2*V');
        var cryptoJSIv = CryptoJS.enc.Utf8.parse('mwPZ7C!n');
    
        function encryptBy(username, password) {
            let message = username + ':' + password;
    
            return encryptByDES(message);
        }
    
        //base64 账号加密码
        function encryptByDES(message) {
            let option = {
                iv: cryptoJSIv,
                mode: CryptoJS.mode.CBC,
                padding: CryptoJS.pad.Pkcs7
            }
            let encrypted = CryptoJS.TripleDES.encrypt(message, cryptoJSKey, option);
            return encrypted.ciphertext.toString().toUpperCase();
        }
    </script>
    </body>
    

    6. 演示

    1. 未登陆-无需登陆就可以看到,访问url白名单
      http://localhost:9904/web/webLogin/test

    2. 未登录
      http://localhost:9904/web/webLogin/getTest

    3. 登陆-用户不存在

    4. 验证码错误

    5. 账号密码正确

    6. url权限控制

    7. 邮箱登陆
      image
      image
      image

  • 相关阅读:
    2017级算法第二次上机-B.第k顺序统计量
    2017级算法第二次上机-B.女娲加农炮
    2017级算法第二次上机-A.ModricWang's Real QuickSort Query
    2017级算法第三次上机-G.ModricWang的导弹拦截系统
    2017级算法第三次上机-F. SkyLee炒股票
    2017级算法第三次上机-C.SkyLee组装电脑
    Objective-c快速入门
    [翻译] Android是怎样绘制视图的
    AnimationsDemo中的ZoomActivity代码分析
    getGlobalVisibleRect和getLocalVisibleRect
  • 原文地址:https://www.cnblogs.com/xiaostudy/p/16631269.html
Copyright © 2020-2023  润新知