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. 创建操作

  2. 修改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 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序列化、配置类、工具类

序列化
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. 字符串工具类

点击查看代码
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);
} } } }

  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. 创建操作



  2. 父模块添加子模块

<module>security</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>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

点击查看代码
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);
}
}

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;
width: 350px;
/*height: 350px;*/
height: 430px;
background: #FFFFFF;
/*以下css用于让登录表单垂直居中在界面,可删除*/
position: absolute;
top: 50%;
left: 50%;
margin-top: -175px;
margin-left: -175px;
} .title {
width: 100%;
height: 40px;
line-height: 40px;
} .title span {
font-size: 18px;
color: #353f42;
} .title-msg {
width: 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 {
width: 100%;
/*height: 120px;*/
height: 200px;
} .input-content input {
width: 330px;
height: 40px;
border: 1px solid #dad9d6;
background: #ffffff;
padding-left: 10px;
padding-right: 10px;
} .enter-btn {
width: 350px;
height: 40px;
color: #fff;
background: #0bc5de;
line-height: 40px;
text-align: center;
border: 0px;
} .foor {
width: 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;
width: 350px;
height: 580px;
background: #FFFFFF;
/*以下css用于让登录表单垂直居中在界面,可删除*/
position: absolute;
top: 40%;
left: 50%;
margin-top: -175px;
margin-left: -175px;
} .title {
width: 100%;
height: 40px;
line-height: 40px;
} .title span {
font-size: 18px;
color: #353f42;
} .title-msg {
width: 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 {
width: 100%;
height: 360px;
} .input-content input {
width: 330px;
height: 40px;
border: 1px solid #dad9d6;
background: #ffffff;
padding-left: 10px;
padding-right: 10px;
} .enter-btn {
margin-top: 30px;
width: 350px;
height: 40px;
color: #fff;
background: #CCCCCC;
line-height: 40px;
text-align: center;
border: 0px;
cursor: not-allowed;
} .foor {
width: 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 {
width: 350px;
margin: 16px auto;
} #msg {
width: 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;
width: 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-width: 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;
width: 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;
width: 14px;
height: 11px;
background: #f57a7a;
background-size: 20px 14px;
} .refreshIcon {
position: absolute;
right: 0;
top: 0;
width: 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="width: 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;
width: 350px;
/*height: 350px;*/
height: 430px;
background: #FFFFFF;
/*以下css用于让登录表单垂直居中在界面,可删除*/
position: absolute;
top: 50%;
left: 50%;
margin-top: -175px;
margin-left: -175px;
} .title {
width: 100%;
height: 40px;
line-height: 40px;
} .title span {
font-size: 18px;
color: #353f42;
} .title-msg {
width: 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 {
width: 100%;
/*height: 120px;*/
height: 200px;
} .input-content input {
width: 330px;
height: 40px;
border: 1px solid #dad9d6;
background: #ffffff;
padding-left: 10px;
padding-right: 10px;
} .enter-btn {
width: 350px;
height: 40px;
color: #fff;
background: #0bc5de;
line-height: 40px;
text-align: center;
border: 0px;
} .foor {
width: 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. 邮箱登陆





简单创建一个SpringCloud2021.0.3项目(二)的更多相关文章

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

    目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 上三篇教程 3. 日志处理 1. 创建日志公共模块 2. Eureka引入日志模块 4. 到此的功能代码 5. 注册中心换成naco ...

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

    目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 上俩篇教程 3. Gateway集成sentinel,网关层做熔断降级 1. 超时熔断降级 2. 异常熔断 3. 集成sentine ...

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

    目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 新建父模块和注册中心 1. 新建父模块 2. 新建注册中心Eureka 3. 新建配置中心Config 4. 新建两个业务服务 1. ...

  4. 【Vue-Cli3.0】【1】创建一个Vue-Cli3.0的项目

    最近在做爬虫,然后要爬好多数据,代码写完了,就让它在爬了.不想闲着就复习一下Vue吧! 开始开始! ***正式讲解之前 先下载一个node.js吧! 一.首先检查一下 版本 PS D:\徐孟林\D D ...

  5. 通过beego快速创建一个Restful风格API项目及API文档自动化

    通过beego快速创建一个Restful风格API项目及API文档自动化 本文演示如何快速(一分钟内,不写一行代码)的根据数据库及表创建一个Restful风格的API项目,及提供便于在线测试API的界 ...

  6. 通过beego快速创建一个Restful风格API项目及API文档自动化(转)

    通过beego快速创建一个Restful风格API项目及API文档自动化 本文演示如何快速(一分钟内,不写一行代码)的根据数据库及表创建一个Restful风格的API项目,及提供便于在线测试API的界 ...

  7. 如何使用maven建一个web3.0的项目

    使用eclipse手动建一个maven的web project可能会有版本不合适的情况,例如使用spring的websocket需要web3.0什么的,不全面的修改可能会出现各种红叉,甚是苦恼.我从我 ...

  8. 以sb7code为基础创建一个基本的OpenGL项目

      以sb7code为基础创建一个基本的OpenGL项目   从github上面下载sb7code代码: https://github.com/openglsuperbible/sb7code 打开H ...

  9. 简单创建一个完整的struts2框架小程序

    要完成一个struts2框架的搭建, 1.首先应该从官网上下载最新的jar包,网络连接:http://struts.apache.org/download.cgi#struts2514.1,选择下载F ...

随机推荐

  1. 2 万字 + 20张图| 细说 Redis 九种数据类型和应用场景

    作者:小林coding 计算机八股文网(操作系统.计算机网络.计算机组成.MySQL.Redis):https://xiaolincoding.com 大家好,我是小林. 我们都知道 Redis 提供 ...

  2. VMware虚拟机基于contos 7 搭建lnmp环境全过程

    这个环境也整了几次了,由于本人比较懒,没有记住.找资料很麻烦,就自己动手咯 1.下载VMware虚拟机   (有注册码)     地址:http://www.zdfans.com/5928.html ...

  3. 使用FileSystemWatcher监听文件状态

    更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月2日. 一.FileSystemWatcher类型介绍 在.NET中使用 FileSystemWatcher 类型可以进行监视指定 ...

  4. JS数组at函数(获取最后一个元素的方法)介绍

    本文介绍js中数组的at函数,属于比较简单的知识普及性文章,难度不大. 0x00 首先,我们可以思考如下一个问题,如果要获取一个数组的最后一个元素(这是很常用的操作),我们应该怎么做? 相信大部分人能 ...

  5. 《原CSharp》第二回 巧习得元素分类 子不知怀璧其罪

    第二回 巧习得元素分类 子不知怀璧其罪 ===================================================================== 云溪父亲见状看了看云 ...

  6. 安装ImageMagick7.1库以及php的Imagick扩展

    由于ImageMagick7以下不支持heic等图片格式,所以重新安装了ImageMagick7.1版本支持heic格式,并写此文章记录一下. 如果安装过程中遇到一些未知的错误,https://ima ...

  7. 获取请求体数据 POST

    POST获取请求体 请求体中封装了 POST请求的请求参数 获取流对象 再从流对象中那数据 一种字节流 一种字符流 BufferedReader getReader()获取字符输入流 只能操作字符 S ...

  8. 循环结构-for循环和while循环

    循环语句1--for for循环语句格式: for(初始化表达式①; 布尔表达式②; 步进表达式④){ 循环体③ } 执行流程 执行顺序:①②③④>②③④>②③④-②不满足为止. ①负责完 ...

  9. Drone-比Jenkins更轻量化的持续集成部署工具

    Drone 简介 Drone 是一个基于Docker容器技术的可扩展的持续集成引擎,由GO语言编写,可用于自动化测试与构建,甚至发布.每个构建都在一个临时的Docker容器中执行,使开发人员能够完全控 ...

  10. golang面试-代码编写题1-14

    目录 1.代码编写题--统计文本行数-bufio 2.代码编写题--多协程收集错误信息-channel 3.代码编写题--超时控制,内存泄露 4.代码编写题--单例模式 5.代码编写题--九九乘法表 ...