简单创建一个SpringCloud2021.0.3项目(二)
目录
1. 项目说明
当前这篇教程是:
1. 抽取公共模块common,集成redis,虽然只有几个工具类和redis
2. 新建Gateway网关,集成Security,做登陆和资源权限控制
3. 前端登陆,做了2种方式。用户、密码、验证码;邮箱、验证码、图片滑块;并且前端加密传给后端解密;登陆异常次数限制
4. 在分布式系统中基于Token的身份验证
5. 每次请求刷新用户会话有效时间
6. 通过AOP方式动态切换数据源
简单创建一个SpringCloud2021.0.3项目(一)
简单创建一个SpringCloud2021.0.3项目(二)
简单创建一个SpringCloud2021.0.3项目(三)
简单创建一个SpringCloud2021.0.3项目(四)
1. 版本
- SpringCloud版本为2021.0.3
- SpringBoot版本为2.7.2
2. 用到组件
- 注册中心:暂时用Eureka,后面再改成Nacos
- 网关:Gateway
- 权限:Security,Gateway集成
- 负载均衡:LoadBalancer,SpringCloud2020版之后就集成LoadBalancer
- 限流、熔断降级:Sentinel
- 配置中心:暂时用Config,后面改成Nacos
- 服务间访问:Feign
3. 功能
- 项目最基本功能,权限控制,在分布式系统中基于Token的身份验证。
- 前端登陆,做了2种方式。用户、密码、验证码;邮箱、验证码、图片滑块;并且前端加密传给后端解密;登陆异常次数限制;
- 限流、负载均衡,应对高并发情况,降低系统负载;
- 服务熔断降级:避免系统雪崩,提高系统可用性;
- 两种方式的多数据源,一种是通过AOP方式动态切换数据源,另一种是不同数据源管理的数据各不相同;
- 日志系统Logback,是SpringBoot默认集成
2. 上一篇教程
简单创建一个SpringCloud2021.0.3项目(一)
- 新建Eureka注册中心
- 新建Config配置中心,producerService服务读取参数
- 2个业务服务(producerService和webService),webService通过Feign调用producerService的服务
- webService用到多数据源,不同的数据源管理不同的数据
3. 创建公共模块Common
-
创建操作
-
修改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>
- 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);
}
}
- 字符串工具类
点击查看代码
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"
);
}
- 验证码工具类
点击查看代码
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);
}
}
}
}
- 解密工具类
点击查看代码
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
-
创建操作
-
父模块添加子模块
<module>security</module>
- 修改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>
- 修改配置文件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
- 查询用户的实体类、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登陆配置
- 配置密码加密、解析器
点击查看代码
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();
}
}
- 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;
}
}
- 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);
}
}
}
}
- 自定义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;
}
}
- 自定义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));
}
}
- 自定义登陆处理
点击查看代码
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);
}
}
- 自定义鉴权处理
点击查看代码
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;
}
}
- 自定义处理未登陆无访问权限的返回结果
点击查看代码
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));
}
}
- 自定义登出成功后操作
点击查看代码
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);
}
}
- 自定义处理登录失败或其他异常访问调用
点击查看代码
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));
}
}
- 自定义处理登陆成功后返回结果
点击查看代码
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));
}
}
- 自定义处理登陆后无权限访问返回结果
点击查看代码
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)))));
}
}
- 重写存储认证信息,实时修改用户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);
});
}
}
- 主要过滤配置类
点击查看代码
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;
}
}
上面的图,验证码和解密工具类已经抽取到公共模块
- 邮箱实体类
点击查看代码
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;
}
}
- 邮箱工具类
点击查看代码
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;
}
}
}
- 验证码接口
点击查看代码
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\":\"测试,非自己邮箱不发!\"}";
});
}
}
- 获取当前用户名、测试动态切换数据源接口
点击查看代码
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));
}
}
- 删除启动类
3. 创建Gateway服务
-
创建操作
-
父模块添加子模块
<module>gateway</module>
- 修改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>
- 配置文件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
- 启动类添加注解
点击查看代码
@ComponentScan(
basePackages = {
// 把security服务下的包交给spring管理
"com.xiaostudy.security"
, "com.xiaostudy.gateway"
, "com.xiaostudy.common"
}
)
@MapperScan("com.xiaostudy.security.mapper")
-
启动
-
注册中心看服务
4. feign模块添加gateway接口
- 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
- 添加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简单登陆
- 注册请求类
点击查看代码
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;
}
}
- 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;
}
}
- 添加公共模块
<dependency>
<groupId>com.xiaostudy</groupId>
<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 登陆跳转和一些测试
点击查看代码
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);
}
}
- 前端-首页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>
- 前端-普通账号密码登陆
点击查看代码
<!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>
- 前端-邮箱登陆
点击查看代码
<!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>
图片
- 前端-用户注册
点击查看代码
<!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. 演示
-
未登陆-无需登陆就可以看到,访问url白名单
http://localhost:9904/web/webLogin/test
-
登陆-用户不存在
-
验证码错误
-
账号密码正确
-
url权限控制
-
邮箱登陆