init
This commit is contained in:
+470
@@ -0,0 +1,470 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.CubicCurve2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 行为验证码生成器基类
|
||||
*
|
||||
* <p>所有图片按逻辑尺寸的整数倍超采样渲染,客户端以逻辑尺寸展示,保证高像素密度屏幕下依然清晰锐利;
|
||||
* 坐标契约始终使用逻辑尺寸,渲染倍率对调用方透明。底图完全由代码生成,不依赖内置图片资源,
|
||||
* 由「同色系主题 × 构图风格」两个维度随机组合:主题决定配色,风格决定纹理形态,
|
||||
* 依赖纹理连续性做对齐线索的模式可限定风格池。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public abstract class AbstractBehaviorGenerator implements BehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 超采样渲染倍率,兼顾高像素密度屏幕清晰度与图片体积
|
||||
*/
|
||||
protected static final int RENDER_SCALE = 2;
|
||||
|
||||
/**
|
||||
* JPEG 压缩质量,低于该值时渐变底图会出现明显色带
|
||||
*/
|
||||
private static final float JPEG_QUALITY = 0.9F;
|
||||
|
||||
/**
|
||||
* 同色系主题池:{渐变起色, 渐变止色, 光斑色A, 光斑色B, 流线色A, 流线色B, 流线色C}
|
||||
*/
|
||||
private static final Color[][] THEMES = {
|
||||
// 深海蓝
|
||||
{new Color(0x1B, 0x33, 0x58), new Color(0x0C, 0x17, 0x2B), new Color(0x3E, 0x92, 0xCC), new Color(0x2C, 0x5F, 0x8A),
|
||||
new Color(0x7F, 0xD8, 0xF5), new Color(0x4F, 0xA3, 0xD9), new Color(0x9A, 0xD1, 0xD4)},
|
||||
// 暮紫
|
||||
{new Color(0x33, 0x1F, 0x54), new Color(0x17, 0x0F, 0x2E), new Color(0x7C, 0x5C, 0xBF), new Color(0x4B, 0x3F, 0x72),
|
||||
new Color(0xB8, 0xA1, 0xE3), new Color(0x8F, 0x6E, 0xD5), new Color(0x6E, 0x8F, 0xF0)},
|
||||
// 松石绿
|
||||
{new Color(0x14, 0x40, 0x3C), new Color(0x0A, 0x1F, 0x1E), new Color(0x2E, 0xC4, 0xB6), new Color(0x1B, 0x6F, 0x66),
|
||||
new Color(0x7B, 0xE0, 0xD3), new Color(0x3D, 0xB8, 0xA5), new Color(0xA8, 0xE6, 0xCF)},
|
||||
// 绛暮红
|
||||
{new Color(0x46, 0x1D, 0x3D), new Color(0x20, 0x0E, 0x1D), new Color(0xC8, 0x6B, 0x98), new Color(0x8A, 0x3F, 0x66),
|
||||
new Color(0xF0, 0xA6, 0xC2), new Color(0xD4, 0x7F, 0xA6), new Color(0xE8, 0xC1, 0x9A)},
|
||||
// 石墨蓝灰
|
||||
{new Color(0x25, 0x2F, 0x45), new Color(0x12, 0x17, 0x24), new Color(0x5C, 0x7A, 0xEA), new Color(0x3A, 0x4A, 0x7A),
|
||||
new Color(0x9B, 0xB5, 0xF0), new Color(0x6C, 0x8A, 0xE8), new Color(0x8F, 0xD6, 0xE8)},
|
||||
// 琥珀棕
|
||||
{new Color(0x4A, 0x34, 0x21), new Color(0x1F, 0x15, 0x0C), new Color(0xC9, 0x8A, 0x3B), new Color(0x8A, 0x5A, 0x28),
|
||||
new Color(0xE8, 0xB2, 0x5E), new Color(0xD1, 0x9A, 0x4A), new Color(0xF0, 0xD0, 0xA0)},
|
||||
// 橄榄绿
|
||||
{new Color(0x3A, 0x42, 0x22), new Color(0x17, 0x1C, 0x0E), new Color(0x8A, 0x9A, 0x4A), new Color(0x5A, 0x6B, 0x2E),
|
||||
new Color(0xC0, 0xD0, 0x7A), new Color(0x9A, 0xB0, 0x55), new Color(0xD8, 0xE0, 0xA8)},
|
||||
// 酒红金
|
||||
{new Color(0x4A, 0x1F, 0x28), new Color(0x20, 0x0D, 0x12), new Color(0xC0, 0x5A, 0x6A), new Color(0xB0, 0x8A, 0x3A),
|
||||
new Color(0xE8, 0x9A, 0xA8), new Color(0xD4, 0xB0, 0x60), new Color(0xF0, 0xC8, 0xD0)},
|
||||
// 青橙对比
|
||||
{new Color(0x14, 0x38, 0x3F), new Color(0x0A, 0x1C, 0x20), new Color(0x2E, 0x9C, 0xB8), new Color(0xE0, 0x7A, 0x3A),
|
||||
new Color(0x6E, 0xD0, 0xE8), new Color(0xF0, 0xA4, 0x68), new Color(0xA8, 0xE0, 0xD8)},
|
||||
// 紫金
|
||||
{new Color(0x38, 0x25, 0x4E), new Color(0x17, 0x0F, 0x22), new Color(0x8A, 0x6A, 0xCF), new Color(0xC0, 0xA0, 0x50),
|
||||
new Color(0xB8, 0x9A, 0xE8), new Color(0xE0, 0xC8, 0x78), new Color(0xD0, 0xB8, 0xF0)},
|
||||
};
|
||||
|
||||
/**
|
||||
* 底图构图风格
|
||||
*
|
||||
* <p>依赖纹理连续性做对齐线索的模式(旋转还原、拼接还原)应限定使用具备贯穿性线条的风格。</p>
|
||||
*/
|
||||
protected enum ArtStyle {
|
||||
/**
|
||||
* 流线:贯穿画布的贝塞尔曲线
|
||||
*/
|
||||
FLOW,
|
||||
/**
|
||||
* 波纹:多组正弦波浪线
|
||||
*/
|
||||
WAVE,
|
||||
/**
|
||||
* 几何:低多边形三角拼接
|
||||
*/
|
||||
GEOMETRIC,
|
||||
/**
|
||||
* 光点:光点群与星轨弧线
|
||||
*/
|
||||
GLOW_DOTS,
|
||||
/**
|
||||
* 网格:斜向细网格
|
||||
*/
|
||||
GRID
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制超采样底图,物理像素为逻辑尺寸的渲染倍率倍
|
||||
*
|
||||
* @param width 逻辑宽度
|
||||
* @param height 逻辑高度
|
||||
* @return 底图
|
||||
*/
|
||||
protected static BufferedImage createArtBackground(int width, int height) {
|
||||
return createArtBackground(width, height, randomTheme(), ArtStyle.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 以指定主题绘制超采样底图,供需要与底图配色联动的前景元素复用同一主题
|
||||
*
|
||||
* @param width 逻辑宽度
|
||||
* @param height 逻辑高度
|
||||
* @param theme 主题色组
|
||||
* @return 底图
|
||||
*/
|
||||
protected static BufferedImage createArtBackground(int width, int height, Color[] theme) {
|
||||
return createArtBackground(width, height, theme, ArtStyle.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 以指定主题与风格池绘制超采样底图,风格从池中随机选取
|
||||
*
|
||||
* @param width 逻辑宽度
|
||||
* @param height 逻辑高度
|
||||
* @param theme 主题色组
|
||||
* @param styles 可选构图风格池
|
||||
* @return 底图
|
||||
*/
|
||||
protected static BufferedImage createArtBackground(int width, int height, Color[] theme, ArtStyle... styles) {
|
||||
BufferedImage image = new BufferedImage(width * RENDER_SCALE, height * RENDER_SCALE, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = scaledGraphics(image);
|
||||
|
||||
// 同色系斜向渐变与柔光斑构成共通基调,营造空间纵深
|
||||
graphics.setPaint(new GradientPaint(0, 0, theme[0], width, height, theme[1]));
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
int glowCount = randomInt(3, 5);
|
||||
for (int i = 0; i < glowCount; i++) {
|
||||
Color glow = theme[2 + randomInt(0, 2)];
|
||||
int radius = randomInt(Math.min(width, height) / 3, Math.min(width, height));
|
||||
Point2D center = new Point2D.Double(randomInt(0, width), randomInt(0, height));
|
||||
graphics.setPaint(new RadialGradientPaint(center, radius, new float[]{0F, 1F},
|
||||
new Color[]{withAlpha(glow, randomInt(36, 66)), withAlpha(glow, 0)}));
|
||||
graphics.fillOval((int) center.getX() - radius, (int) center.getY() - radius, radius * 2, radius * 2);
|
||||
}
|
||||
|
||||
// 风格纹理层决定底图形态
|
||||
ArtStyle style = styles[ThreadLocalRandom.current().nextInt(styles.length)];
|
||||
switch (style) {
|
||||
case FLOW -> drawFlowStyle(graphics, width, height, theme);
|
||||
case WAVE -> drawWaveStyle(graphics, width, height, theme);
|
||||
case GEOMETRIC -> drawGeometricStyle(graphics, width, height, theme);
|
||||
case GLOW_DOTS -> drawGlowDotsStyle(graphics, width, height, theme);
|
||||
case GRID -> drawGridStyle(graphics, width, height, theme);
|
||||
}
|
||||
|
||||
// 极细微噪点提升机器识别成本,密度与透明度均克制以免显脏
|
||||
int noiseCount = width * height / 600;
|
||||
for (int i = 0; i < noiseCount; i++) {
|
||||
graphics.setColor(new Color(255, 255, 255, randomInt(10, 32)));
|
||||
graphics.fillRect(randomInt(0, width), randomInt(0, height), 1, 1);
|
||||
}
|
||||
drawWatermark(graphics, width, height);
|
||||
graphics.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 流线风格:贯穿画布的贝塞尔曲线
|
||||
*/
|
||||
private static void drawFlowStyle(Graphics2D graphics, int width, int height, Color[] theme) {
|
||||
int curveCount = randomInt(3, 6);
|
||||
for (int i = 0; i < curveCount; i++) {
|
||||
graphics.setColor(withAlpha(themeLine(theme), randomInt(70, 130)));
|
||||
graphics.setStroke(new BasicStroke(randomInt(2, 4) + 0.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
graphics.draw(new CubicCurve2D.Double(
|
||||
randomInt(-40, 20), randomInt(0, height),
|
||||
randomInt(0, width), randomInt(-50, height + 50),
|
||||
randomInt(0, width), randomInt(-50, height + 50),
|
||||
randomInt(width - 20, width + 40), randomInt(0, height)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 波纹风格:多组横贯画布的正弦波浪线,同组内相位递进形成波纹束
|
||||
*/
|
||||
private static void drawWaveStyle(Graphics2D graphics, int width, int height, Color[] theme) {
|
||||
int groupCount = randomInt(3, 5);
|
||||
for (int i = 0; i < groupCount; i++) {
|
||||
Color line = themeLine(theme);
|
||||
int baseY = randomInt(0, height);
|
||||
double amplitude = randomInt(8, 26);
|
||||
double wavelength = randomInt(70, 160);
|
||||
double phase = randomInt(0, 360) * Math.PI / 180;
|
||||
int lineCount = randomInt(2, 4);
|
||||
for (int j = 0; j < lineCount; j++) {
|
||||
graphics.setColor(withAlpha(line, randomInt(50, 110)));
|
||||
graphics.setStroke(new BasicStroke(randomInt(1, 3) + 0.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
Path2D wave = new Path2D.Double();
|
||||
int offsetY = baseY + j * randomInt(8, 16);
|
||||
wave.moveTo(-10, offsetY + amplitude * Math.sin(phase));
|
||||
for (int x = 0; x <= width + 10; x += 6) {
|
||||
wave.lineTo(x, offsetY + amplitude * Math.sin(x / wavelength * Math.PI * 2 + phase));
|
||||
}
|
||||
graphics.draw(wave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 几何风格:抖动网格三角化的低多边形拼接,颜色沿渐变基调作亮度扰动
|
||||
*/
|
||||
private static void drawGeometricStyle(Graphics2D graphics, int width, int height, Color[] theme) {
|
||||
int cell = randomInt(46, 70);
|
||||
int cols = width / cell + 2;
|
||||
int rows = height / cell + 2;
|
||||
// 网格点带随机抖动,三角形因此各不相同
|
||||
Point[][] points = new Point[rows + 1][cols + 1];
|
||||
for (int row = 0; row <= rows; row++) {
|
||||
for (int col = 0; col <= cols; col++) {
|
||||
int jitter = cell / 3;
|
||||
points[row][col] = new Point(
|
||||
col * cell - cell / 2 + randomInt(-jitter, jitter + 1),
|
||||
row * cell - cell / 2 + randomInt(-jitter, jitter + 1));
|
||||
}
|
||||
}
|
||||
for (int row = 0; row < rows; row++) {
|
||||
for (int col = 0; col < cols; col++) {
|
||||
Point p1 = points[row][col];
|
||||
Point p2 = points[row][col + 1];
|
||||
Point p3 = points[row + 1][col];
|
||||
Point p4 = points[row + 1][col + 1];
|
||||
if (randomInt(0, 2) == 0) {
|
||||
fillShadedTriangle(graphics, width, height, theme, p1, p2, p3);
|
||||
fillShadedTriangle(graphics, width, height, theme, p2, p4, p3);
|
||||
} else {
|
||||
fillShadedTriangle(graphics, width, height, theme, p1, p2, p4);
|
||||
fillShadedTriangle(graphics, width, height, theme, p1, p4, p3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按三角形重心在渐变轴上的位置插值主题基色,叠加亮度扰动后半透明填充,保留底层光斑的透光感
|
||||
*/
|
||||
private static void fillShadedTriangle(Graphics2D graphics, int width, int height, Color[] theme, Point p1, Point p2, Point p3) {
|
||||
double ratio = Math.min(1.0, Math.max(0.0,
|
||||
((p1.x + p2.x + p3.x) / 3.0 / width + (p1.y + p2.y + p3.y) / 3.0 / height) / 2));
|
||||
int shift = randomInt(-14, 15);
|
||||
Color base = blend(theme[0], theme[1], ratio);
|
||||
graphics.setColor(new Color(
|
||||
clampChannel(base.getRed() + shift), clampChannel(base.getGreen() + shift), clampChannel(base.getBlue() + shift), 150));
|
||||
Path2D triangle = new Path2D.Double();
|
||||
triangle.moveTo(p1.x, p1.y);
|
||||
triangle.lineTo(p2.x, p2.y);
|
||||
triangle.lineTo(p3.x, p3.y);
|
||||
triangle.closePath();
|
||||
graphics.fill(triangle);
|
||||
}
|
||||
|
||||
/**
|
||||
* 光点风格:大小错落的光点群与少量星轨弧线
|
||||
*/
|
||||
private static void drawGlowDotsStyle(Graphics2D graphics, int width, int height, Color[] theme) {
|
||||
int dotCount = randomInt(22, 36);
|
||||
for (int i = 0; i < dotCount; i++) {
|
||||
Color dot = randomInt(0, 3) == 0 ? Color.WHITE : themeLine(theme);
|
||||
int size = randomInt(2, 7);
|
||||
graphics.setColor(withAlpha(dot, randomInt(50, 150)));
|
||||
graphics.fillOval(randomInt(0, width), randomInt(0, height), size, size);
|
||||
}
|
||||
int arcCount = randomInt(1, 3);
|
||||
for (int i = 0; i < arcCount; i++) {
|
||||
graphics.setColor(withAlpha(themeLine(theme), randomInt(50, 90)));
|
||||
graphics.setStroke(new BasicStroke(1.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
int radius = randomInt(width / 2, width * 2);
|
||||
graphics.drawArc(randomInt(-radius, width - radius), randomInt(-radius, height - radius),
|
||||
radius * 2, radius * 2, randomInt(0, 360), randomInt(40, 120));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网格风格:整体旋转的斜向细网格
|
||||
*/
|
||||
private static void drawGridStyle(Graphics2D graphics, int width, int height, Color[] theme) {
|
||||
Graphics2D scoped = (Graphics2D) graphics.create();
|
||||
scoped.rotate(Math.toRadians(randomInt(12, 34)), width / 2.0, height / 2.0);
|
||||
int spacing = randomInt(16, 26);
|
||||
Color line = themeLine(theme);
|
||||
scoped.setStroke(new BasicStroke(1F));
|
||||
// 旋转后网格须超出画布覆盖四角
|
||||
int reach = width + height;
|
||||
for (int x = -reach; x <= reach; x += spacing) {
|
||||
scoped.setColor(withAlpha(line, randomInt(22, 46)));
|
||||
scoped.drawLine(x, -reach, x, reach);
|
||||
}
|
||||
for (int y = -reach; y <= reach; y += spacing) {
|
||||
scoped.setColor(withAlpha(line, randomInt(22, 46)));
|
||||
scoped.drawLine(-reach, y, reach, y);
|
||||
}
|
||||
scoped.dispose();
|
||||
// 少量贯穿斜线增强方向线索
|
||||
for (int i = 0; i < randomInt(2, 4); i++) {
|
||||
graphics.setColor(withAlpha(themeLine(theme), randomInt(60, 100)));
|
||||
graphics.setStroke(new BasicStroke(randomInt(2, 4), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
graphics.drawLine(randomInt(-30, width / 2), randomInt(-20, height + 20),
|
||||
randomInt(width / 2, width + 30), randomInt(-20, height + 20));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取主题流线色组中的随机一色
|
||||
*
|
||||
* @param theme 主题色组
|
||||
* @return 流线色
|
||||
*/
|
||||
protected static Color themeLine(Color[] theme) {
|
||||
return theme[4 + ThreadLocalRandom.current().nextInt(3)];
|
||||
}
|
||||
|
||||
private static Color blend(Color from, Color to, double ratio) {
|
||||
return new Color(
|
||||
(int) (from.getRed() + (to.getRed() - from.getRed()) * ratio),
|
||||
(int) (from.getGreen() + (to.getGreen() - from.getGreen()) * ratio),
|
||||
(int) (from.getBlue() + (to.getBlue() - from.getBlue()) * ratio));
|
||||
}
|
||||
|
||||
private static int clampChannel(int value) {
|
||||
return Math.min(255, Math.max(0, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取随机主题色组
|
||||
*
|
||||
* @return 主题色组
|
||||
*/
|
||||
protected static Color[] randomTheme() {
|
||||
return THEMES[ThreadLocalRandom.current().nextInt(THEMES.length)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 在底图右下角绘制品牌水印,低透明度不干扰题面;文案取服务端常量,不接受外部入参以杜绝向图片注入任意文本
|
||||
*/
|
||||
private static void drawWatermark(Graphics2D graphics, int width, int height) {
|
||||
Graphics2D scoped = (Graphics2D) graphics.create();
|
||||
scoped.setFont(new Font(Font.SANS_SERIF, Font.BOLD | Font.ITALIC, 13));
|
||||
FontMetrics metrics = scoped.getFontMetrics();
|
||||
int textX = width - metrics.stringWidth(OAuth2TokenConstant.BEHAVIOR_WATERMARK) - 10;
|
||||
int textY = height - 8;
|
||||
scoped.setColor(new Color(0, 0, 0, 40));
|
||||
scoped.drawString(OAuth2TokenConstant.BEHAVIOR_WATERMARK, textX + 1, textY + 1);
|
||||
scoped.setColor(new Color(255, 255, 255, 52));
|
||||
scoped.drawString(OAuth2TokenConstant.BEHAVIOR_WATERMARK, textX, textY);
|
||||
scoped.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建已应用渲染倍率与质量参数的画笔,调用方按逻辑坐标绘制
|
||||
*
|
||||
* @param image 超采样图片
|
||||
* @return 画笔
|
||||
*/
|
||||
protected static Graphics2D scaledGraphics(BufferedImage image) {
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
||||
graphics.scale(RENDER_SCALE, RENDER_SCALE);
|
||||
return graphics;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码为 JPEG 格式 DataURI,用于无透明通道的底图以控制体积
|
||||
*
|
||||
* @param image 图片
|
||||
* @return DataURI 字符串
|
||||
*/
|
||||
protected static String toJpegBase64(BufferedImage image) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
param.setCompressionQuality(JPEG_QUALITY);
|
||||
try (MemoryCacheImageOutputStream imageOutput = new MemoryCacheImageOutputStream(outputStream)) {
|
||||
writer.setOutput(imageOutput);
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
} finally {
|
||||
writer.dispose();
|
||||
}
|
||||
return "data:image/jpeg;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException("行为验证码图片编码失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码为 PNG 格式 DataURI,用于携带透明通道的抠块与圆图
|
||||
*
|
||||
* @param image 图片
|
||||
* @return DataURI 字符串
|
||||
*/
|
||||
protected static String toPngBase64(BufferedImage image) {
|
||||
try {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", outputStream);
|
||||
return "data:image/png;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException("行为验证码图片编码失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整颜色透明度
|
||||
*
|
||||
* @param color 原色
|
||||
* @param alpha 透明度
|
||||
* @return 调整后的颜色
|
||||
*/
|
||||
protected static Color withAlpha(Color color, int alpha) {
|
||||
return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取区间随机数
|
||||
*
|
||||
* @param origin 起始值(含)
|
||||
* @param bound 结束值(不含)
|
||||
* @return 随机数
|
||||
*/
|
||||
protected static int randomInt(int origin, int bound) {
|
||||
return ThreadLocalRandom.current().nextInt(origin, bound);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 行为验证码出题结果
|
||||
*
|
||||
* <p>data 为下发客户端的渲染数据,answer 为服务端留存的校验答案,两者严格分离,答案不得出现在响应体中。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
public class Behavior implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 验证码模式编码
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* 客户端渲染数据
|
||||
*/
|
||||
private Kv data;
|
||||
|
||||
/**
|
||||
* 服务端校验答案
|
||||
*/
|
||||
private String answer;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 行为验证码工厂
|
||||
*
|
||||
* <p>生成器均为无状态实现,随类加载注册一次即可全局复用;出题时随机选取模式,校验时按出题留存的模式精确分发。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class BehaviorFactory {
|
||||
|
||||
/**
|
||||
* 生成器注册表
|
||||
*/
|
||||
private static final List<BehaviorGenerator> GENERATORS = List.of(
|
||||
new WordBehaviorGenerator(), new IdiomBehaviorGenerator(), new PuzzleBehaviorGenerator(),
|
||||
new ConcatBehaviorGenerator(), new RotateBehaviorGenerator()
|
||||
);
|
||||
|
||||
/**
|
||||
* 模式编码索引
|
||||
*/
|
||||
private static final Map<String, BehaviorGenerator> GENERATOR_POOL = GENERATORS.stream()
|
||||
.collect(Collectors.toUnmodifiableMap(BehaviorGenerator::type, Function.identity()));
|
||||
|
||||
private BehaviorFactory() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机选取一种模式的生成器
|
||||
*
|
||||
* @return 生成器
|
||||
*/
|
||||
public static BehaviorGenerator random() {
|
||||
return GENERATORS.get(ThreadLocalRandom.current().nextInt(GENERATORS.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按模式编码获取生成器
|
||||
*
|
||||
* @param type 模式编码
|
||||
* @return 生成器,未注册的模式返回 null
|
||||
*/
|
||||
public static BehaviorGenerator of(String type) {
|
||||
return GENERATOR_POOL.get(type);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
/**
|
||||
* 行为验证码生成器
|
||||
*
|
||||
* <p>实现类须为无状态设计,可被多线程并发调用;答案采用紧凑字符串编码,由各模式自行定义格式并负责解析校验。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface BehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 验证码模式编码
|
||||
*
|
||||
* @return 模式编码
|
||||
*/
|
||||
String type();
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @return 出题结果
|
||||
*/
|
||||
Behavior generate();
|
||||
|
||||
/**
|
||||
* 校验用户作答
|
||||
*
|
||||
* @param answer 服务端留存答案
|
||||
* @param userAnswer 用户作答内容
|
||||
* @return 是否通过
|
||||
*/
|
||||
boolean verify(String answer, String userAnswer);
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 拼接还原验证码生成器
|
||||
*
|
||||
* <p>底图沿随机高度水平切分,下条内容整体循环左移随机偏移量后下发,
|
||||
* 用户拖动滑块使下条右移复位,上下纹理衔接连贯即通过。
|
||||
* 答案为还原所需的右移量,作答为用户拖动的位移;位移在画布宽度上循环,校验按环绕就近取差。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class ConcatBehaviorGenerator extends AbstractBehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 模式编码
|
||||
*/
|
||||
public static final String TYPE = "concat";
|
||||
|
||||
private static final int WIDTH = 340;
|
||||
private static final int HEIGHT = 220;
|
||||
/**
|
||||
* 切缝与画布上下缘的最小距离,保证上下条都有足够的纹理参与对齐
|
||||
*/
|
||||
private static final int SLICE_MARGIN = 70;
|
||||
/**
|
||||
* 错位量与零点的最小距离,避免初始状态即接近还原
|
||||
*/
|
||||
private static final int OFFSET_MARGIN = 60;
|
||||
/**
|
||||
* 命中容差(像素)
|
||||
*/
|
||||
private static final int TOLERANCE = 8;
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior generate() {
|
||||
// 拼接对齐依赖跨切缝的纹理连续性,排除无方向感的光点风格
|
||||
BufferedImage background = createArtBackground(WIDTH, HEIGHT, randomTheme(),
|
||||
ArtStyle.FLOW, ArtStyle.WAVE, ArtStyle.GEOMETRIC, ArtStyle.GRID);
|
||||
int sliceY = randomInt(SLICE_MARGIN, HEIGHT - SLICE_MARGIN + 1);
|
||||
int offset = randomInt(OFFSET_MARGIN, WIDTH - OFFSET_MARGIN + 1);
|
||||
BufferedImage dislocated = dislocate(background, sliceY, offset);
|
||||
|
||||
Behavior captcha = new Behavior();
|
||||
captcha.setType(TYPE);
|
||||
captcha.setAnswer(String.valueOf(offset));
|
||||
captcha.setData(Kv.create()
|
||||
.set("image", toJpegBase64(dislocated))
|
||||
.set("sliceY", sliceY)
|
||||
.set("width", WIDTH)
|
||||
.set("height", HEIGHT));
|
||||
return captcha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String answer, String userAnswer) {
|
||||
try {
|
||||
int target = Integer.parseInt(answer);
|
||||
int moved = (int) Double.parseDouble(userAnswer.trim());
|
||||
int delta = Math.abs(moved - target) % WIDTH;
|
||||
return Math.min(delta, WIDTH - delta) <= TOLERANCE;
|
||||
} catch (NumberFormatException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出下条循环左移后的错位图。像素块搬移直接在物理像素域进行,
|
||||
* 避免经过缩放变换的画笔中逻辑目标区与物理源区两套坐标混用出错
|
||||
*/
|
||||
private BufferedImage dislocate(BufferedImage background, int sliceY, int offset) {
|
||||
int physicalWidth = WIDTH * RENDER_SCALE;
|
||||
int physicalHeight = HEIGHT * RENDER_SCALE;
|
||||
int physicalSlice = sliceY * RENDER_SCALE;
|
||||
int physicalOffset = offset * RENDER_SCALE;
|
||||
BufferedImage result = new BufferedImage(physicalWidth, physicalHeight, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D graphics = result.createGraphics();
|
||||
// 上条原样保留
|
||||
graphics.drawImage(background,
|
||||
0, 0, physicalWidth, physicalSlice,
|
||||
0, 0, physicalWidth, physicalSlice, null);
|
||||
// 下条循环左移:源图右段移至目标左段,源图左段补至目标右段
|
||||
graphics.drawImage(background,
|
||||
0, physicalSlice, physicalWidth - physicalOffset, physicalHeight,
|
||||
physicalOffset, physicalSlice, physicalWidth, physicalHeight, null);
|
||||
graphics.drawImage(background,
|
||||
physicalWidth - physicalOffset, physicalSlice, physicalWidth, physicalHeight,
|
||||
0, physicalSlice, physicalOffset, physicalHeight, null);
|
||||
graphics.dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 成语语序点选验证码生成器
|
||||
*
|
||||
* <p>底图乱序布置一条成语的四个字与若干干扰字,不下发任何目标提示,
|
||||
* 用户须凭语言知识辨识出成语并按语序点击。机器即使识别出全部字符,
|
||||
* 仍需具备成语知识才能确定目标与顺序,对抗强度高于明文提示的文字点选。
|
||||
* 布局、绘制与校验逻辑复用文字点选实现。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class IdiomBehaviorGenerator extends WordBehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 模式编码
|
||||
*/
|
||||
public static final String TYPE = "idiom";
|
||||
|
||||
/**
|
||||
* 干扰字符数
|
||||
*/
|
||||
private static final int DISTURB_COUNT = 2;
|
||||
|
||||
/**
|
||||
* 常用四字成语词表,收录时保证条目内四字互异,避免同图出现相同字导致作答歧义
|
||||
*/
|
||||
private static final String[] IDIOMS = {
|
||||
"风和日丽", "山清水秀", "鸟语花香", "春暖花开", "秋高气爽", "冰天雪地", "万紫千红", "五彩缤纷", "花好月圆", "湖光山色",
|
||||
"青山绿水", "世外桃源", "名山大川", "奇花异草", "古木参天", "电闪雷鸣", "风起云涌", "海阔天空", "波澜壮阔",
|
||||
"一帆风顺", "万事如意", "心想事成", "吉祥如意", "五谷丰登", "六畜兴旺", "国泰民安", "安居乐业", "丰衣足食",
|
||||
"张灯结彩", "欢天喜地", "普天同庆", "皆大欢喜", "兴高采烈", "眉开眼笑", "手舞足蹈", "心花怒放", "神采飞扬",
|
||||
"聚精会神", "全神贯注", "专心致志", "一丝不苟", "持之以恒", "坚持不懈", "自强不息", "发愤图强", "勤学苦练",
|
||||
"博览群书", "学富五车", "才高八斗", "出口成章", "妙笔生花", "画龙点睛", "入木三分", "行云流水", "龙飞凤舞", "笔走龙蛇",
|
||||
"高瞻远瞩", "深谋远虑", "运筹帷幄", "足智多谋", "神机妙算", "料事如神", "明察秋毫", "洞若观火", "心明眼亮", "耳聪目明",
|
||||
"雷厉风行", "当机立断", "斩钉截铁", "大刀阔斧", "干净利落", "快马加鞭", "争分夺秒", "只争朝夕", "闻鸡起舞", "夜以继日",
|
||||
"同舟共济", "齐心协力", "众志成城", "万众一心", "同甘共苦", "患难与共", "肝胆相照", "荣辱与共", "风雨同舟",
|
||||
"光明磊落", "表里如一", "言行一致", "脚踏实地", "实事求是", "名副其实", "货真价实", "童叟无欺",
|
||||
"见义勇为", "助人为乐", "雪中送炭", "拔刀相助", "舍己为人", "大公无私", "克己奉公", "两袖清风", "一尘不染",
|
||||
"虚怀若谷", "不耻下问", "集思广益", "从善如流", "闻过则喜", "开诚布公", "推心置腹", "设身处地",
|
||||
"温故知新", "举一反三", "融会贯通", "触类旁通", "深入浅出", "通俗易懂", "言简意赅", "短小精悍", "画蛇添足", "杯弓蛇影",
|
||||
"守株待兔", "刻舟求剑", "亡羊补牢", "掩耳盗铃", "自相矛盾", "滥竽充数", "买椟还珠", "邯郸学步", "东施效颦", "叶公好龙",
|
||||
"狐假虎威", "鹬蚌相争", "螳臂当车", "井底之蛙", "对牛弹琴", "指鹿为马", "塞翁失马", "愚公移山", "精卫填海",
|
||||
"八仙过海", "三头六臂", "七嘴八舌", "五湖四海", "四面八方", "千军万马", "万水千山", "千辛万苦", "千变万化",
|
||||
"日新月异", "突飞猛进", "一日千里", "与时俱进", "继往开来", "承前启后", "推陈出新", "标新立异", "独辟蹊径", "别具匠心",
|
||||
"栉风沐雨", "披星戴月", "风餐露宿", "跋山涉水", "翻山越岭", "长途跋涉", "不远万里", "走南闯北", "五体投地", "心悦诚服",
|
||||
"金碧辉煌", "富丽堂皇", "雕梁画栋", "巧夺天工", "鬼斧神工", "匠心独运", "精雕细琢", "玲珑剔透",
|
||||
"车水马龙", "川流不息", "熙来攘往", "门庭若市", "高朋满座", "宾至如归", "络绎不绝", "人声鼎沸", "座无虚席", "万人空巷",
|
||||
};
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior generate() {
|
||||
// 语序辨识依赖汉字语义,服务器缺少中文字体时降级为明文提示的文字点选
|
||||
if (!CJK_AVAILABLE) {
|
||||
return super.generate();
|
||||
}
|
||||
String idiom = IDIOMS[ThreadLocalRandom.current().nextInt(IDIOMS.length)];
|
||||
List<String> drawChars = new ArrayList<>(idiom.length() + DISTURB_COUNT);
|
||||
for (int i = 0; i < idiom.length(); i++) {
|
||||
drawChars.add(String.valueOf(idiom.charAt(i)));
|
||||
}
|
||||
// 干扰字不得与成语字重复,否则用户无法唯一定位目标
|
||||
Set<String> excludes = new HashSet<>(drawChars);
|
||||
drawChars.addAll(pickDistinctChars(DISTURB_COUNT, excludes));
|
||||
return renderWordPuzzle(drawChars, idiom.length(), false);
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 滑块拼图验证码生成器
|
||||
*
|
||||
* <p>底图随机位置抠出拼图块,拼图块纵向位置固定下发,用户仅横向拖动滑轨使其对齐缺口。
|
||||
* 答案为缺口左上角横坐标,作答为拼图块最终停靠横坐标,容差内视为命中。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class PuzzleBehaviorGenerator extends AbstractBehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 模式编码
|
||||
*/
|
||||
public static final String TYPE = "puzzle";
|
||||
|
||||
private static final int WIDTH = 340;
|
||||
private static final int HEIGHT = 220;
|
||||
/**
|
||||
* 拼图块包围盒边长
|
||||
*/
|
||||
private static final int PIECE_SIZE = 60;
|
||||
/**
|
||||
* 命中容差(像素)
|
||||
*/
|
||||
private static final int TOLERANCE = 8;
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior generate() {
|
||||
BufferedImage background = createArtBackground(WIDTH, HEIGHT);
|
||||
// 缺口位于画布右半区,避免拼图块起始位置即命中
|
||||
int gapX = randomInt(WIDTH / 2, WIDTH - PIECE_SIZE - 10);
|
||||
int gapY = randomInt(10, HEIGHT - PIECE_SIZE - 10);
|
||||
Shape pieceShape = createPieceShape();
|
||||
|
||||
// 先按缺口位置截取拼图块,再在底图上覆盖缺口暗色遮罩
|
||||
BufferedImage piece = cutPiece(background, pieceShape, gapX, gapY);
|
||||
maskGap(background, pieceShape, gapX, gapY);
|
||||
|
||||
Behavior captcha = new Behavior();
|
||||
captcha.setType(TYPE);
|
||||
captcha.setAnswer(String.valueOf(gapX));
|
||||
captcha.setData(Kv.create()
|
||||
.set("image", toJpegBase64(background))
|
||||
.set("piece", toPngBase64(piece))
|
||||
.set("pieceY", gapY)
|
||||
.set("pieceSize", PIECE_SIZE)
|
||||
.set("width", WIDTH)
|
||||
.set("height", HEIGHT));
|
||||
return captcha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String answer, String userAnswer) {
|
||||
try {
|
||||
int deltaX = Math.abs((int) Double.parseDouble(userAnswer.trim()) - Integer.parseInt(answer));
|
||||
return deltaX <= TOLERANCE;
|
||||
} catch (NumberFormatException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造拼图块形状:圆角方形主体 + 顶部凸起 + 右侧凹口,坐标基于包围盒原点
|
||||
*/
|
||||
private Shape createPieceShape() {
|
||||
Area area = new Area(new RoundRectangle2D.Double(4, 16, 44, 40, 8, 8));
|
||||
area.add(new Area(new Ellipse2D.Double(17, 7, 18, 18)));
|
||||
area.subtract(new Area(new Ellipse2D.Double(40, 28, 16, 16)));
|
||||
return area;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按拼图形状截取底图内容并描边,输出带透明通道的拼图块
|
||||
*/
|
||||
private BufferedImage cutPiece(BufferedImage background, Shape pieceShape, int gapX, int gapY) {
|
||||
BufferedImage piece = new BufferedImage(PIECE_SIZE * RENDER_SCALE, PIECE_SIZE * RENDER_SCALE, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = scaledGraphics(piece);
|
||||
graphics.setClip(pieceShape);
|
||||
graphics.drawImage(background, -gapX, -gapY, WIDTH, HEIGHT, null);
|
||||
graphics.setClip(null);
|
||||
graphics.setColor(new Color(255, 255, 255, 210));
|
||||
graphics.setStroke(new BasicStroke(1.2F));
|
||||
graphics.draw(pieceShape);
|
||||
graphics.dispose();
|
||||
return piece;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在底图缺口位置叠加暗色遮罩与双层描边(外亮内暗),形成凹陷的拖动目标提示
|
||||
*/
|
||||
private void maskGap(BufferedImage background, Shape pieceShape, int gapX, int gapY) {
|
||||
Graphics2D graphics = scaledGraphics(background);
|
||||
graphics.translate(gapX, gapY);
|
||||
graphics.setColor(new Color(10, 14, 22, 150));
|
||||
graphics.fill(pieceShape);
|
||||
graphics.setClip(pieceShape);
|
||||
graphics.setColor(new Color(0, 0, 0, 110));
|
||||
graphics.setStroke(new BasicStroke(4F));
|
||||
graphics.draw(pieceShape);
|
||||
graphics.setClip(null);
|
||||
graphics.setColor(new Color(255, 255, 255, 130));
|
||||
graphics.setStroke(new BasicStroke(1.2F));
|
||||
graphics.draw(pieceShape);
|
||||
graphics.dispose();
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 旋转还原验证码生成器
|
||||
*
|
||||
* <p>底图与其他模式保持一致的横幅画布,从画布中心截取圆形区域并预旋转随机角度,外围图案保持原状,
|
||||
* 用户转动内圆使其与外围纹理衔接连贯即通过。答案为还原所需角度,与预旋转角度互补为整圆;
|
||||
* 校验时按圆周就近取差,容差内视为命中。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class RotateBehaviorGenerator extends AbstractBehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 模式编码
|
||||
*/
|
||||
public static final String TYPE = "rotate";
|
||||
|
||||
private static final int WIDTH = 340;
|
||||
private static final int HEIGHT = 220;
|
||||
/**
|
||||
* 中心圆直径
|
||||
*/
|
||||
private static final int CIRCLE_SIZE = 150;
|
||||
/**
|
||||
* 命中容差(度)
|
||||
*/
|
||||
private static final int TOLERANCE = 8;
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior generate() {
|
||||
// 旋转还原依赖内外纹理的方向连续性,限定具备贯穿性线条的风格
|
||||
BufferedImage background = createArtBackground(WIDTH, HEIGHT, randomTheme(), ArtStyle.FLOW, ArtStyle.WAVE, ArtStyle.GRID);
|
||||
// 预旋转角度避开整圆两端,防止初始状态即接近还原
|
||||
int rotateAngle = randomInt(45, 316);
|
||||
BufferedImage ring = cutRotatedCircle(background, rotateAngle);
|
||||
maskCircle(background);
|
||||
|
||||
Behavior captcha = new Behavior();
|
||||
captcha.setType(TYPE);
|
||||
captcha.setAnswer(String.valueOf(360 - rotateAngle));
|
||||
captcha.setData(Kv.create()
|
||||
.set("image", toJpegBase64(background))
|
||||
.set("ring", toPngBase64(ring))
|
||||
.set("ringSize", CIRCLE_SIZE)
|
||||
.set("width", WIDTH)
|
||||
.set("height", HEIGHT));
|
||||
return captcha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String answer, String userAnswer) {
|
||||
try {
|
||||
int target = Integer.parseInt(answer);
|
||||
int rotated = (int) Double.parseDouble(userAnswer.trim());
|
||||
int delta = Math.abs(rotated - target) % 360;
|
||||
return Math.min(delta, 360 - delta) <= TOLERANCE;
|
||||
} catch (NumberFormatException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从画布中心截取圆形区域并预旋转,输出带透明通道的内圆图
|
||||
*/
|
||||
private BufferedImage cutRotatedCircle(BufferedImage background, int rotateAngle) {
|
||||
BufferedImage circle = new BufferedImage(CIRCLE_SIZE * RENDER_SCALE, CIRCLE_SIZE * RENDER_SCALE, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = scaledGraphics(circle);
|
||||
graphics.setClip(new Ellipse2D.Double(0, 0, CIRCLE_SIZE, CIRCLE_SIZE));
|
||||
double radius = CIRCLE_SIZE / 2.0;
|
||||
graphics.rotate(Math.toRadians(rotateAngle), radius, radius);
|
||||
graphics.drawImage(background, -(WIDTH - CIRCLE_SIZE) / 2, -(HEIGHT - CIRCLE_SIZE) / 2, WIDTH, HEIGHT, null);
|
||||
graphics.setClip(null);
|
||||
// 内圆细描边柔化与外围遮罩的拼接边缘
|
||||
graphics.setColor(new Color(255, 255, 255, 90));
|
||||
graphics.setStroke(new BasicStroke(1.2F));
|
||||
graphics.draw(new Ellipse2D.Double(0.6, 0.6, CIRCLE_SIZE - 1.2, CIRCLE_SIZE - 1.2));
|
||||
graphics.dispose();
|
||||
return circle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在底图中心叠加暗色圆形遮罩与描边,内圆区域由客户端组件覆盖渲染
|
||||
*/
|
||||
private void maskCircle(BufferedImage background) {
|
||||
Graphics2D graphics = scaledGraphics(background);
|
||||
int offsetX = (WIDTH - CIRCLE_SIZE) / 2;
|
||||
int offsetY = (HEIGHT - CIRCLE_SIZE) / 2;
|
||||
Ellipse2D hole = new Ellipse2D.Double(offsetX, offsetY, CIRCLE_SIZE, CIRCLE_SIZE);
|
||||
graphics.setColor(new Color(10, 14, 22, 165));
|
||||
graphics.fill(hole);
|
||||
graphics.setColor(new Color(255, 255, 255, 110));
|
||||
graphics.setStroke(new BasicStroke(1.2F));
|
||||
graphics.draw(hole);
|
||||
graphics.dispose();
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.behavior;
|
||||
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 文字点选验证码生成器
|
||||
*
|
||||
* <p>底图随机布置若干字符,提示以字符文本数组下发,由客户端自行渲染提示样式,用户按提示顺序点击命中即通过。
|
||||
* 答案格式为按序的目标字符中心坐标串「x,y;x,y;x,y」,作答格式与其一致。
|
||||
* 画布布字与作答校验逻辑同时服务于语序点选等文字类子模式。</p>
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class WordBehaviorGenerator extends AbstractBehaviorGenerator {
|
||||
|
||||
/**
|
||||
* 模式编码
|
||||
*/
|
||||
public static final String TYPE = "word";
|
||||
|
||||
private static final int WIDTH = 340;
|
||||
private static final int HEIGHT = 220;
|
||||
private static final int MIN_FONT_SIZE = 34;
|
||||
private static final int MAX_FONT_SIZE = 44;
|
||||
private static final int DRAW_COUNT = 6;
|
||||
private static final int TARGET_COUNT = 3;
|
||||
/**
|
||||
* 命中容差(像素),覆盖最大字号包围盒并预留触点抖动余量
|
||||
*/
|
||||
private static final int TOLERANCE = 28;
|
||||
|
||||
/**
|
||||
* 常用简体汉字池,选取笔画清晰、辨识度高的字符,池内字符严格互异,避免同图出现相同字导致作答歧义
|
||||
*/
|
||||
private static final String CJK_CHARSET =
|
||||
"天地月星辰云雨雪风霜露雷电虹霞光影" +
|
||||
"春夏秋冬晨昏昼夜今古年岁时刻早晚" +
|
||||
"东南西北中上下左右前后内外高低远深浅" +
|
||||
"大小多少长短宽窄厚薄轻重快慢冷暖湿新旧" +
|
||||
"红黄蓝绿紫青黑白灰彩明暗浓淡" +
|
||||
"花草树叶林竹梅兰菊荷松柏桃李杏枝根果种" +
|
||||
"鸟鱼虫马牛羊狗猫鸭鹅龙凤虎豹狼鹿熊猴象鹰燕蜂蝶蛙龟鹤" +
|
||||
"山水江河湖海溪泉波浪田原岭峰谷" +
|
||||
"门窗屋房墙院园路街巷城乡村镇楼阁亭桥塔仓库店铺" +
|
||||
"书画琴棋诗词歌赋笔墨纸砚文章句音律曲调" +
|
||||
"茶酒饭菜米面油盐糖醋汤饼瓜豆蛋奶" +
|
||||
"衣帽鞋袜布丝绸棉麻皮革绒线" +
|
||||
"金银铜铁钢玉石珠宝砖瓦泥沙" +
|
||||
"心手足口耳目眉发肩背腰腿骨肉脉" +
|
||||
"仁义礼智信忠孝勇谦和善美真诚正直清" +
|
||||
"福寿康宁安泰吉祥瑞庆贺" +
|
||||
"走跑跳飞游泳行摇摆抛掷接送迎" +
|
||||
"学问知识才技能功课业教写算";
|
||||
|
||||
/**
|
||||
* 字体缺失时的降级字符池,仅保留无歧义的字母数字
|
||||
*/
|
||||
private static final String ASCII_CHARSET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
|
||||
/**
|
||||
* 可用字体池,绘制时每字随机取用,混排字体使字形特征碎片化;
|
||||
* 服务器缺少中文字体时字符会渲染为占位方框,启动时探测一次并降级为字母数字字符池
|
||||
*/
|
||||
private static final List<Font> TEXT_FONTS = resolveTextFonts();
|
||||
|
||||
/**
|
||||
* 中文字体是否可用,语序点选等依赖汉字语义的子模式据此决定是否降级
|
||||
*/
|
||||
protected static final boolean CJK_AVAILABLE = TEXT_FONTS.get(0).canDisplay(CJK_CHARSET.charAt(0));
|
||||
|
||||
private static final String CHARSET = CJK_AVAILABLE ? CJK_CHARSET : ASCII_CHARSET;
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Behavior generate() {
|
||||
return renderWordPuzzle(pickDistinctChars(DRAW_COUNT, Collections.emptySet()), TARGET_COUNT, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String answer, String userAnswer) {
|
||||
String[] targets = answer.split(";");
|
||||
String[] clicks = userAnswer.split(";");
|
||||
if (clicks.length != targets.length) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
for (int i = 0; i < targets.length; i++) {
|
||||
String[] target = targets[i].split(",");
|
||||
String[] click = clicks[i].split(",");
|
||||
int deltaX = Math.abs((int) Double.parseDouble(click[0].trim()) - Integer.parseInt(target[0]));
|
||||
int deltaY = Math.abs((int) Double.parseDouble(click[1].trim()) - Integer.parseInt(target[1]));
|
||||
if (deltaX > TOLERANCE || deltaY > TOLERANCE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (NumberFormatException | ArrayIndexOutOfBoundsException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 布置字符画布并产出题面:前 targetCount 个字符为按序作答目标,其余为干扰字符
|
||||
*
|
||||
* @param drawChars 待布置字符,数量须与画布网格容量一致
|
||||
* @param targetCount 作答目标数
|
||||
* @param withPrompt 是否下发目标字符明文提示,语序类子模式不下发以要求用户凭语义辨识
|
||||
* @return 出题结果
|
||||
*/
|
||||
protected Behavior renderWordPuzzle(List<String> drawChars, int targetCount, boolean withPrompt) {
|
||||
// 字符伪装色与底图共用同一主题,保证同色系融合
|
||||
Color[] theme = randomTheme();
|
||||
BufferedImage image = createArtBackground(WIDTH, HEIGHT, theme);
|
||||
Graphics2D graphics = scaledGraphics(image);
|
||||
|
||||
// 画布按 3x2 网格布置字符,网格内随机抖动,保证字符互不重叠
|
||||
List<Point> centers = new ArrayList<>(drawChars.size());
|
||||
List<Integer> cells = shuffledCells(drawChars.size());
|
||||
int cellWidth = WIDTH / 3;
|
||||
int cellHeight = HEIGHT / 2;
|
||||
int margin = MAX_FONT_SIZE / 2 + 4;
|
||||
for (int i = 0; i < drawChars.size(); i++) {
|
||||
int cell = cells.get(i);
|
||||
int cellX = (cell % 3) * cellWidth;
|
||||
int cellY = (cell / 3) * cellHeight;
|
||||
int centerX = randomInt(cellX + margin, cellX + cellWidth - margin + 1);
|
||||
int centerY = randomInt(cellY + margin, cellY + cellHeight - margin + 1);
|
||||
drawCamouflagedChar(graphics, drawChars.get(i), centerX, centerY, theme);
|
||||
centers.add(new Point(centerX, centerY));
|
||||
}
|
||||
drawVeilGlows(graphics, theme);
|
||||
drawInterferenceLines(graphics, theme);
|
||||
graphics.dispose();
|
||||
|
||||
StringBuilder answer = new StringBuilder();
|
||||
for (int i = 0; i < targetCount; i++) {
|
||||
if (i > 0) {
|
||||
answer.append(';');
|
||||
}
|
||||
answer.append(centers.get(i).x).append(',').append(centers.get(i).y);
|
||||
}
|
||||
|
||||
Behavior captcha = new Behavior();
|
||||
captcha.setType(type());
|
||||
captcha.setAnswer(answer.toString());
|
||||
Kv data = Kv.create()
|
||||
.set("image", toJpegBase64(image))
|
||||
.set("targetCount", targetCount)
|
||||
.set("width", WIDTH)
|
||||
.set("height", HEIGHT);
|
||||
if (withPrompt) {
|
||||
data.set("prompt", drawChars.subList(0, targetCount));
|
||||
}
|
||||
captcha.setData(data);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字符池随机抽取互不重复且不在排除集内的字符
|
||||
*
|
||||
* @param count 抽取数量
|
||||
* @param excludes 排除字符集
|
||||
* @return 字符列表
|
||||
*/
|
||||
protected List<String> pickDistinctChars(int count, Set<String> excludes) {
|
||||
List<Integer> indexes = new ArrayList<>(CHARSET.length());
|
||||
for (int i = 0; i < CHARSET.length(); i++) {
|
||||
indexes.add(i);
|
||||
}
|
||||
Collections.shuffle(indexes, ThreadLocalRandom.current());
|
||||
List<String> chars = new ArrayList<>(count);
|
||||
for (Integer index : indexes) {
|
||||
String candidate = String.valueOf(CHARSET.charAt(index));
|
||||
if (!excludes.contains(candidate)) {
|
||||
chars.add(candidate);
|
||||
if (chars.size() == count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以指定中心点绘制伪装字符:取主题流线同系色并随机降低不透明度,使字符与底图色调融合而非高对比浮出,
|
||||
* 随机字体、字号、旋转与错切形变使字形特征碎片化;伪轮廓用主题暗色低透明度勾勒,仅保留细看可辨的边缘
|
||||
*/
|
||||
private void drawCamouflagedChar(Graphics2D graphics, String text, int centerX, int centerY, Color[] theme) {
|
||||
Graphics2D scoped = (Graphics2D) graphics.create();
|
||||
Font font = TEXT_FONTS.get(ThreadLocalRandom.current().nextInt(TEXT_FONTS.size()))
|
||||
.deriveFont(Font.BOLD, (float) randomInt(MIN_FONT_SIZE, MAX_FONT_SIZE + 1));
|
||||
scoped.setFont(font);
|
||||
scoped.rotate(Math.toRadians(randomInt(-35, 36)), centerX, centerY);
|
||||
scoped.translate(centerX, centerY);
|
||||
scoped.shear(randomInt(-18, 19) / 100.0, 0);
|
||||
scoped.translate(-centerX, -centerY);
|
||||
FontMetrics metrics = scoped.getFontMetrics();
|
||||
int textWidth = metrics.stringWidth(text);
|
||||
int baselineY = centerY + (metrics.getAscent() - metrics.getDescent()) / 2;
|
||||
scoped.setColor(withAlpha(theme[1], 60));
|
||||
scoped.drawString(text, centerX - textWidth / 2 + 1, baselineY + 1);
|
||||
scoped.setColor(withAlpha(themeLine(theme), randomInt(90, 150)));
|
||||
scoped.drawString(text, centerX - textWidth / 2, baselineY);
|
||||
scoped.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在字符层之上回叠主题柔光斑,压低字符与背景的整体对比度,形成若隐若现的观感
|
||||
*/
|
||||
private void drawVeilGlows(Graphics2D graphics, Color[] theme) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Color glow = theme[2 + randomInt(0, 2)];
|
||||
int radius = randomInt(HEIGHT / 2, HEIGHT);
|
||||
java.awt.geom.Point2D center = new java.awt.geom.Point2D.Double(randomInt(0, WIDTH), randomInt(0, HEIGHT));
|
||||
graphics.setPaint(new RadialGradientPaint(center, radius, new float[]{0F, 1F},
|
||||
new Color[]{withAlpha(glow, randomInt(26, 46)), withAlpha(glow, 0)}));
|
||||
graphics.fillOval((int) center.getX() - radius, (int) center.getY() - radius, radius * 2, radius * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在字符层之上叠加主题同系色弧线,切断字符轮廓的连续边缘
|
||||
*/
|
||||
private void drawInterferenceLines(Graphics2D graphics, Color[] theme) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
graphics.setColor(withAlpha(themeLine(theme), randomInt(60, 110)));
|
||||
graphics.setStroke(new BasicStroke(randomInt(1, 3) + 0.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
graphics.draw(new java.awt.geom.CubicCurve2D.Double(
|
||||
randomInt(-30, 10), randomInt(0, HEIGHT),
|
||||
randomInt(0, WIDTH), randomInt(-30, HEIGHT + 30),
|
||||
randomInt(0, WIDTH), randomInt(-30, HEIGHT + 30),
|
||||
randomInt(WIDTH - 10, WIDTH + 30), randomInt(0, HEIGHT)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打乱网格顺序,使目标字符位置分布随机
|
||||
*/
|
||||
private List<Integer> shuffledCells(int count) {
|
||||
List<Integer> cells = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
cells.add(i);
|
||||
}
|
||||
Collections.shuffle(cells, ThreadLocalRandom.current());
|
||||
return cells;
|
||||
}
|
||||
|
||||
/**
|
||||
* 探测可用的中文字体池:优先收集常见字体族的全部可用项,为空时扫描系统字体,仍为空时回退系统无衬线字体
|
||||
*/
|
||||
private static List<Font> resolveTextFonts() {
|
||||
List<Font> fonts = new ArrayList<>();
|
||||
String[] preferredFamilies = {"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||
"Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Micro Hei", "SimHei", "STHeiti", "KaiTi", "SimSun"};
|
||||
for (String family : preferredFamilies) {
|
||||
Font font = new Font(family, Font.BOLD, MAX_FONT_SIZE);
|
||||
if (font.canDisplay('验')) {
|
||||
fonts.add(font);
|
||||
}
|
||||
}
|
||||
if (fonts.isEmpty()) {
|
||||
for (Font candidate : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) {
|
||||
if (candidate.canDisplay('验')) {
|
||||
fonts.add(candidate.deriveFont(Font.BOLD, (float) MAX_FONT_SIZE));
|
||||
if (fonts.size() >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fonts.isEmpty()) {
|
||||
fonts.add(new Font(Font.SANS_SERIF, Font.BOLD, MAX_FONT_SIZE));
|
||||
}
|
||||
return List.copyOf(fonts);
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.config;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springblade.core.oauth2.granter.TokenGranter;
|
||||
import org.springblade.core.oauth2.granter.TokenGranterEnhancer;
|
||||
import org.springblade.core.oauth2.granter.TokenGranterFactory;
|
||||
import org.springblade.core.oauth2.handler.*;
|
||||
import org.springblade.core.oauth2.props.OAuth2Properties;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2ClientDetailService;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2UserDetailService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* OAuth2Configuration
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(OAuth2Properties.class)
|
||||
@ConditionalOnProperty(value = OAuth2Properties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class OAuth2AutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AuthorizationHandler authorizationHandler() {
|
||||
return new OAuth2AuthorizationHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public PasswordHandler passwordHandler(OAuth2Properties properties) {
|
||||
return new OAuth2PasswordHandler(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public TokenHandler tokenHandler(JwtProperties properties) {
|
||||
return new OAuth2TokenHandler(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OAuth2ClientService oAuth2ClientService(JdbcTemplate jdbcTemplate) {
|
||||
return new OAuth2ClientDetailService(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OAuth2UserService oAuth2UserService(JdbcTemplate jdbcTemplate) {
|
||||
return new OAuth2UserDetailService(jdbcTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenGranterFactory tokenGranterFactory(List<TokenGranter> tokenGranters, List<TokenGranterEnhancer> tokenGranterEnhancers, OAuth2Properties properties) {
|
||||
return new TokenGranterFactory(tokenGranters, tokenGranterEnhancers, properties);
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.config;
|
||||
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* OAuth资源配置
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class OAuth2WebConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
|
||||
}
|
||||
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* OAuth2AuthorizationConstant
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2AuthorizationConstant {
|
||||
|
||||
/**
|
||||
* 用户session key
|
||||
*/
|
||||
String AUTHORIZATION_SESSION_KEY = "user";
|
||||
|
||||
/**
|
||||
* 授权请求key
|
||||
*/
|
||||
String AUTHORIZATION_REQUEST_KEY = "authorizationRequest";
|
||||
|
||||
/**
|
||||
* 公钥
|
||||
*/
|
||||
String PUBLIC_KEY = "publicKey";
|
||||
|
||||
/**
|
||||
* 跳转参数
|
||||
*/
|
||||
String REDIRECT_URL = "redirect:";
|
||||
|
||||
/**
|
||||
* 授权地址
|
||||
*/
|
||||
String AUTHORIZE_URL = "/oauth/authorize";
|
||||
|
||||
/**
|
||||
* 登录地址
|
||||
*/
|
||||
String LOGIN_URL = "/oauth/login";
|
||||
|
||||
/**
|
||||
* 错误地址
|
||||
*/
|
||||
String ERROR_URL = "/oauth/error";
|
||||
|
||||
/**
|
||||
* 授权视图
|
||||
*/
|
||||
String AUTHORIZE_MODEL = "authorize";
|
||||
|
||||
/**
|
||||
* 登录视图
|
||||
*/
|
||||
String LOGIN_MODEL = "login";
|
||||
|
||||
/**
|
||||
* 错误视图
|
||||
*/
|
||||
String ERROR_MODEL = "error";
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* OAuth2ClientConstant
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2ClientConstant {
|
||||
|
||||
/**
|
||||
* blade_client表字段
|
||||
*/
|
||||
String CLIENT_FIELDS = "id, client_id, client_secret, resource_ids, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove";
|
||||
|
||||
/**
|
||||
* blade_client查询语句
|
||||
*/
|
||||
String BASE_STATEMENT = "select " + CLIENT_FIELDS + " from blade_client";
|
||||
|
||||
/**
|
||||
* blade_client查询排序
|
||||
*/
|
||||
String DEFAULT_FIND_STATEMENT = BASE_STATEMENT + " order by client_id";
|
||||
|
||||
/**
|
||||
* 查询client_id
|
||||
*/
|
||||
String DEFAULT_SELECT_STATEMENT = BASE_STATEMENT + " where client_id = ?";
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* GranterTypeConstant
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2GranterConstant {
|
||||
|
||||
/**
|
||||
* 授权码模式
|
||||
*/
|
||||
String AUTHORIZATION_CODE = "authorization_code";
|
||||
/**
|
||||
* 密码模式
|
||||
*/
|
||||
String PASSWORD = "password";
|
||||
/**
|
||||
* 刷新token模式
|
||||
*/
|
||||
String REFRESH_TOKEN = "refresh_token";
|
||||
/**
|
||||
* 客户端模式
|
||||
*/
|
||||
String CLIENT_CREDENTIALS = "client_credentials";
|
||||
/**
|
||||
* 简化模式
|
||||
*/
|
||||
String IMPLICIT = "implicit";
|
||||
/**
|
||||
* 验证码模式
|
||||
*/
|
||||
String CAPTCHA = "captcha";
|
||||
/**
|
||||
* 行为验证码模式
|
||||
*/
|
||||
String BEHAVIOR = "behavior";
|
||||
/**
|
||||
* 手机验证码模式
|
||||
*/
|
||||
String SMS_CODE = "sms_code";
|
||||
/**
|
||||
* 微信小程序模式
|
||||
*/
|
||||
String WECHAT_APPLET = "wechat_applet";
|
||||
/**
|
||||
* 开放平台模式
|
||||
*/
|
||||
String SOCIAL = "social";
|
||||
/**
|
||||
* 注册模式
|
||||
*/
|
||||
String REGISTER = "register";
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* OAuth2常量.
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2ParameterConstant {
|
||||
|
||||
/**
|
||||
* 客户端id
|
||||
*/
|
||||
String CLIENT_ID = "client_id";
|
||||
/**
|
||||
* 客户端密钥
|
||||
*/
|
||||
String CLIENT_SECRET = "client_secret";
|
||||
/**
|
||||
* 令牌
|
||||
*/
|
||||
String ACCESS_TOKEN = "access_token";
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
String REFRESH_TOKEN = "refresh_token";
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
String TENANT_ID = "tenant_id";
|
||||
/**
|
||||
* 用户名字
|
||||
*/
|
||||
String NAME = "name";
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
String USERNAME = "username";
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
String PASSWORD = "password";
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
String PHONE = "phone";
|
||||
/**
|
||||
* 电子游戏
|
||||
*/
|
||||
String EMAIL = "email";
|
||||
/**
|
||||
* 授权类型
|
||||
*/
|
||||
String GRANT_TYPE = "grant_type";
|
||||
/**
|
||||
* 响应类型
|
||||
*/
|
||||
String SCOPE = "scope";
|
||||
/**
|
||||
* 重定向地址
|
||||
*/
|
||||
String REDIRECT_URI = "redirect_uri";
|
||||
/**
|
||||
* 返回类型
|
||||
*/
|
||||
String RESPONSE_TYPE = "response_type";
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
String STATE = "state";
|
||||
/**
|
||||
* 验证
|
||||
*/
|
||||
String CODE = "code";
|
||||
/**
|
||||
* 来源
|
||||
*/
|
||||
String SOURCE = "source";
|
||||
/**
|
||||
* 自动授权
|
||||
*/
|
||||
String AUTO_APPROVE = "auto_approve";
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* OAuth2ResponseConstant
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2ResponseConstant {
|
||||
Integer DEFAULT_SUCCESS_CODE = 200;
|
||||
String SUCCESS = "success";
|
||||
String SUCCESS_CODE = "code";
|
||||
String SUCCESS_DESCRIPTION = "msg";
|
||||
String ERROR_CODE = "error_code";
|
||||
String ERROR_DESCRIPTION = "error_description";
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* TokenConstant
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2TokenConstant {
|
||||
|
||||
String HEADER_AUTHORIZATION = "Authorization";
|
||||
String HEADER_AUTHORIZATION_PREFIX = "Basic ";
|
||||
String TOKEN_HEADER = "Blade-Auth";
|
||||
String TENANT_HEADER = "Tenant-Id";
|
||||
String DEFAULT_TENANT_ID = "000000";
|
||||
String USER_HEADER = "User-Id";
|
||||
String DEPT_HEADER = "Dept-Id";
|
||||
String ROLE_HEADER = "Role-Id";
|
||||
String USER_TYPE_HEADER = "User-Type";
|
||||
String DEFAULT_USER_TYPE = "web";
|
||||
String USER_FAIL_KEY = "blade:user::blade:fail:";
|
||||
String CAPTCHA_CACHE_KEY = "blade:auth::blade:captcha:";
|
||||
String CAPTCHA_HEADER_KEY = "Captcha-Key";
|
||||
String CAPTCHA_HEADER_CODE = "Captcha-Code";
|
||||
String CAPTCHA_NOT_CORRECT = "验证码不正确";
|
||||
String BEHAVIOR_CACHE_KEY = "blade:auth::blade:behavior:";
|
||||
String BEHAVIOR_TICKET_KEY = "blade:auth::blade:behavior-ticket:";
|
||||
String BEHAVIOR_WATERMARK = "BladeX";
|
||||
String BEHAVIOR_INVALID = "行为验证码已失效,请重新验证";
|
||||
String BEHAVIOR_NOT_CORRECT = "行为验证未通过,请重新验证";
|
||||
String TOKEN_NOT_CORRECT = "令牌授权不正确";
|
||||
String TOKEN_NOT_PERMISSION = "令牌授权已过期";
|
||||
String USER_NOT_FOUND = "用户名或密码错误";
|
||||
String USER_PHONE_NOT_FOUND = "用户手机未注册";
|
||||
String USER_HAS_NO_ROLE = "未获得用户的角色信息";
|
||||
String USER_HAS_NO_TENANT = "未获得用户的租户信息";
|
||||
String USER_HAS_NO_TENANT_PERMISSION = "租户授权已过期,请联系管理员";
|
||||
String USER_HAS_TOO_MANY_FAILS = "登录错误次数过多,请稍后再试";
|
||||
String DEFAULT_AVATAR = "https://bladex.cn/images/logo-small.png";
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.constant;
|
||||
|
||||
/**
|
||||
* OAuth2UserConstant
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2UserConstant {
|
||||
|
||||
/**
|
||||
* blade_user查询
|
||||
*/
|
||||
String DEFAULT_USERID_SELECT_STATEMENT = "select id as user_id, tenant_id , account, password from blade_user where id = ?";
|
||||
|
||||
/**
|
||||
* blade_user查询
|
||||
*/
|
||||
String DEFAULT_USERNAME_SELECT_STATEMENT = "select id as user_id, tenant_id , account, password from blade_user where account = ?";
|
||||
|
||||
/**
|
||||
* blade_user查询
|
||||
*/
|
||||
String DEFAULT_PHONE_SELECT_STATEMENT = "select id as user_id, tenant_id , account, password from blade_user where phone = ?";
|
||||
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.endpoint;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.oauth2.constant.OAuth2AuthorizationConstant;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.props.OAuth2Properties;
|
||||
import org.springblade.core.oauth2.provider.OAuth2AuthorizationRequest;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.utils.OAuth2CodeUtil;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.SessionAttribute;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springblade.core.oauth2.constant.OAuth2ParameterConstant.*;
|
||||
|
||||
/**
|
||||
* AuthorizationEndpoint
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Controller
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "用户授权码模式认证", description = "2 - OAuth2授权码模式端点")
|
||||
public class OAuth2AuthorizationEndpoint implements OAuth2AuthorizationConstant {
|
||||
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
private final OAuth2ClientService clientService;
|
||||
|
||||
private final OAuth2UserService userService;
|
||||
|
||||
private final PasswordHandler passwordHandler;
|
||||
|
||||
private final OAuth2Properties oAuth2Properties;
|
||||
|
||||
@GetMapping("/oauth/login")
|
||||
public String loginPage(HttpSession session, Model model) {
|
||||
// 从session中获取授权请求参数
|
||||
Optional.ofNullable((OAuth2AuthorizationRequest) session.getAttribute(AUTHORIZATION_REQUEST_KEY))
|
||||
.ifPresent(authorizationRequest -> {
|
||||
model.addAttribute(PUBLIC_KEY, oAuth2Properties.getPublicKey());
|
||||
model.addAttribute(AUTHORIZATION_REQUEST_KEY, authorizationRequest);
|
||||
});
|
||||
// 返回登录页面视图
|
||||
return LOGIN_MODEL;
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/oauth/login/perform")
|
||||
public String performLogin(@SessionAttribute(AUTHORIZATION_REQUEST_KEY) OAuth2AuthorizationRequest authorizationRequest,
|
||||
RedirectAttributes redirectAttributes, HttpSession session) {
|
||||
// 根据用户名和密码验证用户
|
||||
return Optional.ofNullable(authenticateUser(session, authorizationRequest))
|
||||
.map(user -> {
|
||||
// 用户验证成功,处理授权请求参数和重定向
|
||||
authorizationRequest.setTenantId(user.getTenantId());
|
||||
session.setAttribute(AUTHORIZATION_REQUEST_KEY, authorizationRequest);
|
||||
redirectAttributes.addAllAttributes(authorizationRequest.getParameters());
|
||||
return REDIRECT_URL + AUTHORIZE_URL; // 重定向回授权视图
|
||||
})
|
||||
.orElse(REDIRECT_URL + ERROR_URL); // 用户验证失败,重定向回失败视图
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/authorize")
|
||||
public String authorize(@SessionAttribute(value = AUTHORIZATION_REQUEST_KEY, required = false) OAuth2AuthorizationRequest authorizationRequest,
|
||||
HttpSession session, Model model) {
|
||||
// 获取授权请求参数
|
||||
OAuth2AuthorizationRequest request = OAuth2AuthorizationRequest.create().buildParameters();
|
||||
|
||||
// 设置请求参数
|
||||
Optional.ofNullable(authorizationRequest).ifPresentOrElse(authReq -> {
|
||||
if (request.getState() == null) {
|
||||
request.setState(authReq.getState());
|
||||
}
|
||||
if (request.getClientId() != null) {
|
||||
session.setAttribute(AUTHORIZATION_REQUEST_KEY, request);
|
||||
}
|
||||
}, () -> session.setAttribute(AUTHORIZATION_REQUEST_KEY, request));
|
||||
|
||||
// 获取用户信息并跳转
|
||||
return Optional.ofNullable(session.getAttribute(AUTHORIZATION_SESSION_KEY))
|
||||
.map(obj -> (OAuth2User) obj)
|
||||
.map(user -> {
|
||||
String clientId = request.getParameters().get(CLIENT_ID);
|
||||
OAuth2Client client = clientService.loadByClientId(clientId);
|
||||
if (client == null) {
|
||||
return ERROR_MODEL;
|
||||
}
|
||||
model.addAttribute(AUTO_APPROVE, client.getAutoapprove());
|
||||
model.addAttribute(USERNAME, user.getAccount());
|
||||
model.addAllAttributes(request.getParameters());
|
||||
return AUTHORIZE_MODEL; // 用户已登录,显示授权页面
|
||||
})
|
||||
.orElse(REDIRECT_URL + LOGIN_URL); // 用户未登录,重定向到登录页面
|
||||
}
|
||||
|
||||
@PostMapping("/oauth/authorize/perform")
|
||||
public String performAuthorize(@RequestParam(required = false) String state,
|
||||
@SessionAttribute(AUTHORIZATION_REQUEST_KEY) OAuth2AuthorizationRequest authorizationRequest,
|
||||
RedirectAttributes redirectAttributes, HttpSession session) {
|
||||
|
||||
// 获取客户端信息
|
||||
OAuth2Client client = clientService.loadByClientId(authorizationRequest.getClientId());
|
||||
|
||||
// 校验回调地址信息
|
||||
if (!clientService.validateRedirectUri(client, authorizationRequest.getRedirectUri())) {
|
||||
// 重定向URI参数不匹配,返回错误页面
|
||||
return ERROR_MODEL;
|
||||
}
|
||||
|
||||
// 生成授权码
|
||||
String code = createCode();
|
||||
|
||||
// 设置用户信息
|
||||
OAuth2User user = (OAuth2User) session.getAttribute(AUTHORIZATION_SESSION_KEY);
|
||||
if (user == null) {
|
||||
// 用户未登录,重定向到登录页面
|
||||
return REDIRECT_URL + LOGIN_URL;
|
||||
}
|
||||
|
||||
// 校验state参数
|
||||
if (Func.equalsSafe(authorizationRequest.getState(), state)) {
|
||||
// 保存授权码
|
||||
saveCode(code, user);
|
||||
} else {
|
||||
// 重定向URI和state参数不匹配,返回错误页面
|
||||
return ERROR_MODEL;
|
||||
}
|
||||
|
||||
// 使用RedirectAttributes添加授权码和state参数
|
||||
redirectAttributes.addAttribute(CODE, code);
|
||||
|
||||
// 添加tenantId参数为state参数
|
||||
if (authorizationRequest.getTenantId() != null) {
|
||||
redirectAttributes.addAttribute(STATE, authorizationRequest.getTenantId());
|
||||
}
|
||||
// 用户自定义state参数则覆盖
|
||||
if (state != null) {
|
||||
redirectAttributes.addAttribute(STATE, state);
|
||||
}
|
||||
|
||||
// 重定向到客户端提供的重定向URI
|
||||
return REDIRECT_URL + authorizationRequest.getRedirectUri();
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/authorize/logout")
|
||||
public String logout(HttpSession session) {
|
||||
// 创建 OAuth2 请求对象并构建参数
|
||||
OAuth2Request request = OAuth2Request.create().buildParameterArgs();
|
||||
// 退出登录,清除session中的用户信息
|
||||
if (session != null) {
|
||||
// 销毁当前会话
|
||||
session.removeAttribute(AUTHORIZATION_SESSION_KEY);
|
||||
}
|
||||
if (StringUtil.isNotBlank(request.getRedirectUri())) {
|
||||
// 重定向到客户端提供的重定向URI
|
||||
return REDIRECT_URL + request.getRedirectUri();
|
||||
}
|
||||
// 重定向到默认登录页面
|
||||
return REDIRECT_URL + LOGIN_URL;
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/error")
|
||||
public String error() {
|
||||
// 返回错误页面
|
||||
return ERROR_MODEL;
|
||||
}
|
||||
|
||||
private OAuth2User authenticateUser(HttpSession session, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
// 创建 OAuth2 请求对象并构建参数
|
||||
OAuth2Request request = OAuth2Request.create().buildParameterArgs().buildHeaderArgs();
|
||||
|
||||
// 获取请求参数
|
||||
String username = request.getUsername();
|
||||
String password = request.getPassword();
|
||||
String clientId = authorizationRequest.getClientId();
|
||||
String redirectUri = authorizationRequest.getRedirectUri();
|
||||
|
||||
// 获取客户端信息
|
||||
OAuth2Client client = clientService.loadByClientId(clientId);
|
||||
|
||||
// 校验回调地址信息
|
||||
if (!clientService.validateRedirectUri(client, redirectUri)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
OAuth2User user = userService.loadByUsername(username, request);
|
||||
|
||||
// 校验用户信息
|
||||
if (!userService.validateUser(user)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 校验用户密码
|
||||
if (!passwordHandler.matches(password, user.getPassword())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 将用户信息存入session
|
||||
session.setAttribute(AUTHORIZATION_SESSION_KEY, user);
|
||||
|
||||
// 返回用户信息
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权码模式获取授权码
|
||||
*
|
||||
* @return 授权码
|
||||
*/
|
||||
private String createCode() {
|
||||
// 生成6位随机数作为授权码
|
||||
String code = StringUtil.random(6);
|
||||
if (bladeRedis.exists(OAuth2CodeUtil.codeKey(code))) {
|
||||
// 如果生成的授权码已存在,则递归调用重新生成
|
||||
return createCode();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存code信息
|
||||
*/
|
||||
private void saveCode(String code, OAuth2User user) {
|
||||
// 保存code信息到redis,30分钟过期
|
||||
bladeRedis.setEx(OAuth2CodeUtil.codeKey(code), user, Duration.ofMinutes(30));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code获取用户信息
|
||||
*
|
||||
* @param code code
|
||||
* @return 用户信息
|
||||
*/
|
||||
public OAuth2User getUserByCode(String code) {
|
||||
// 根据code从redis中获取用户信息
|
||||
return bladeRedis.get(OAuth2CodeUtil.codeKey(code));
|
||||
}
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.endpoint;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.zhyd.oauth.model.AuthCallback;
|
||||
import me.zhyd.oauth.model.AuthToken;
|
||||
import me.zhyd.oauth.request.AuthRequest;
|
||||
import me.zhyd.oauth.utils.AuthStateUtils;
|
||||
import org.springblade.core.social.props.SocialProperties;
|
||||
import org.springblade.core.social.utils.SocialUtil;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 第三方登录端点
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@ConditionalOnProperty(value = "social.enabled", havingValue = "true")
|
||||
@Tag(name = "开放平台登录", description = "3 - 开放平台登录端点")
|
||||
public class OAuth2SocialEndpoint {
|
||||
|
||||
private final SocialProperties socialProperties;
|
||||
|
||||
/**
|
||||
* 授权完毕跳转
|
||||
*/
|
||||
@Operation(summary = "授权完毕跳转")
|
||||
@RequestMapping("/oauth/render/{source}")
|
||||
public void renderAuth(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
|
||||
AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
|
||||
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
|
||||
response.sendRedirect(authorizeUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取认证信息
|
||||
*/
|
||||
@Operation(summary = "获取认证信息")
|
||||
@RequestMapping("/oauth/callback/{source}")
|
||||
public Object login(@PathVariable("source") String source, AuthCallback callback) {
|
||||
AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
|
||||
return authRequest.login(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销授权
|
||||
*/
|
||||
@Operation(summary = "撤销授权")
|
||||
@RequestMapping("/oauth/revoke/{source}/{token}")
|
||||
public Object revokeAuth(@PathVariable("source") String source, @PathVariable("token") String token) {
|
||||
AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
|
||||
return authRequest.revoke(AuthToken.builder().accessToken(token).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 续期accessToken
|
||||
*/
|
||||
@Operation(summary = "续期令牌")
|
||||
@RequestMapping("/oauth/refresh/{source}")
|
||||
public Object refreshAuth(@PathVariable("source") String source, String token) {
|
||||
AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
|
||||
return authRequest.refresh(AuthToken.builder().refreshToken(token).build());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.endpoint;
|
||||
|
||||
import com.wf.captcha.SpecCaptcha;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.jwt.JwtUtil;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springblade.core.launch.props.BladeProperties;
|
||||
import org.springblade.core.oauth2.behavior.Behavior;
|
||||
import org.springblade.core.oauth2.behavior.BehaviorFactory;
|
||||
import org.springblade.core.oauth2.behavior.BehaviorGenerator;
|
||||
import org.springblade.core.oauth2.constant.OAuth2ParameterConstant;
|
||||
import org.springblade.core.oauth2.exception.OAuth2Exception;
|
||||
import org.springblade.core.oauth2.granter.TokenGranter;
|
||||
import org.springblade.core.oauth2.granter.TokenGranterFactory;
|
||||
import org.springblade.core.oauth2.handler.AuthorizationHandler;
|
||||
import org.springblade.core.oauth2.handler.TokenHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Response;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Validation;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
|
||||
import org.springblade.core.oauth2.utils.OAuth2LogUtil;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.secure.BladeUser;
|
||||
import org.springblade.core.secure.utils.AuthUtil;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.CharPool;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.springblade.core.cache.constant.CacheConstant.*;
|
||||
import static org.springblade.core.oauth2.constant.OAuth2TokenConstant.*;
|
||||
|
||||
/**
|
||||
* OAuth2认证端点
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RestController
|
||||
@AllArgsConstructor
|
||||
@Tag(name = "用户授权认证", description = "1 - OAuth2授权认证端点")
|
||||
public class OAuth2TokenEndPoint {
|
||||
|
||||
private final BladeRedis bladeRedis;
|
||||
private final JwtProperties jwtProperties;
|
||||
private final BladeProperties bladeProperties;
|
||||
|
||||
private final TokenGranterFactory granterFactory;
|
||||
private final AuthorizationHandler authorizationHandler;
|
||||
private final TokenHandler tokenHandler;
|
||||
|
||||
|
||||
@PostMapping("/oauth/token")
|
||||
@Operation(
|
||||
summary = "获取Token",
|
||||
description = "OAuth2认证接口",
|
||||
parameters = {
|
||||
@Parameter(in = ParameterIn.QUERY, name = OAuth2ParameterConstant.USERNAME, description = "账号", schema = @Schema(type = "string")),
|
||||
@Parameter(in = ParameterIn.QUERY, name = OAuth2ParameterConstant.PASSWORD, description = "密码", schema = @Schema(type = "string")),
|
||||
@Parameter(in = ParameterIn.QUERY, name = OAuth2ParameterConstant.GRANT_TYPE, description = "授权类型", schema = @Schema(type = "string")),
|
||||
@Parameter(in = ParameterIn.QUERY, name = OAuth2ParameterConstant.REFRESH_TOKEN, description = "刷新token", schema = @Schema(type = "string")),
|
||||
@Parameter(in = ParameterIn.QUERY, name = OAuth2ParameterConstant.SCOPE, description = "权限范围", schema = @Schema(type = "string"))
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Kv> token() {
|
||||
// 创建 OAuth2 请求对象并构建参数
|
||||
OAuth2Request request = OAuth2Request.create().buildArgs();
|
||||
|
||||
// 使用授权处理器对登录前的用户进行验证
|
||||
OAuth2Validation preValidation = authorizationHandler.preValidation(request);
|
||||
if (!preValidation.isSuccess()) {
|
||||
// 认证失败处理逻辑
|
||||
authorizationHandler.preFailure(request, preValidation);
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(preValidation.getCode(), preValidation.getMessage()));
|
||||
}
|
||||
|
||||
// 根据请求的授权类型创建对应的 TokenGranter
|
||||
TokenGranter tokenGranter = granterFactory.create(request.getGrantType());
|
||||
|
||||
// 使用 TokenGranter 获取用户信息
|
||||
OAuth2User user;
|
||||
try {
|
||||
// 使用 TokenGranter 获取用户信息
|
||||
user = tokenGranter.user(request);
|
||||
} catch (OAuth2Exception ex) {
|
||||
// 认证失败日志记录
|
||||
OAuth2LogUtil.logOAuth2Exception(ex, request, bladeProperties.isProd());
|
||||
// 认证失败处理逻辑
|
||||
authorizationHandler.preFailure(request, OAuth2Validation.create().setSuccess(false)
|
||||
.setCode(ex.getExceptionCode().getCode()).setMessage(ex.getMessage()));
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(ex.getExceptionCode().getCode(), ex.getMessage()));
|
||||
}
|
||||
|
||||
// 使用授权处理器对登录后的用户进行验证
|
||||
OAuth2Validation authValidation = authorizationHandler.authValidation(user, request);
|
||||
|
||||
// 检查验证是否成功
|
||||
if (!authValidation.isSuccess()) {
|
||||
// 验证失败处理逻辑
|
||||
authorizationHandler.authFailure(user, request, authValidation);
|
||||
|
||||
// 根据验证失败的错误代码抛出异常
|
||||
OAuth2ExceptionUtil.throwFromCode(authValidation.getCode());
|
||||
}
|
||||
|
||||
// 创建令牌
|
||||
OAuth2Token token = tokenGranter.token(user, request);
|
||||
|
||||
// 对令牌进行增强处理
|
||||
OAuth2Token enhanceToken = tokenHandler.enhance(user, token, request);
|
||||
|
||||
// 验证成功处理逻辑
|
||||
authorizationHandler.authSuccessful(user, request);
|
||||
|
||||
// 返回增强后的令牌
|
||||
return ResponseEntity.ok(enhanceToken.getArgs());
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/oauth/logout")
|
||||
@Operation(summary = "退出登录")
|
||||
public ResponseEntity<Kv> logout() {
|
||||
BladeUser user = AuthUtil.getUser();
|
||||
// 退出登录,清除有状态模式下缓存的token
|
||||
if (user != null && jwtProperties.getState()) {
|
||||
OAuth2Request request = OAuth2Request.create().buildHeaderArgs();
|
||||
String token = JwtUtil.getToken(request.getToken());
|
||||
JwtUtil.removeAccessToken(user.getTenantId(), user.getClientId(), String.valueOf(user.getUserId()), token);
|
||||
JwtUtil.removeRefreshToken(user.getTenantId(), user.getClientId(), String.valueOf(user.getUserId()), token);
|
||||
}
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofSuccessful("退出登录成功"));
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/captcha")
|
||||
@Operation(summary = "获取验证码")
|
||||
public ResponseEntity<Kv> captcha() {
|
||||
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 5);
|
||||
String verCode = specCaptcha.text().toLowerCase();
|
||||
String key = UUID.randomUUID().toString();
|
||||
// 存入redis并设置过期时间为30分钟
|
||||
bladeRedis.setEx(CAPTCHA_CACHE_KEY + key, verCode, Duration.ofMinutes(30));
|
||||
// 将key和base64返回给前端
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofSuccessful("获取验证码成功").set("key", key).set("image", specCaptcha.toBase64()));
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/behavior")
|
||||
@Operation(summary = "获取行为验证码")
|
||||
public ResponseEntity<Kv> behavior() {
|
||||
// 随机选取模式出题,模式与答案一并留存,校验时不信任客户端上送的类型
|
||||
Behavior behavior = BehaviorFactory.random().generate();
|
||||
String key = UUID.randomUUID().toString();
|
||||
bladeRedis.setEx(BEHAVIOR_CACHE_KEY + key, behavior.getType() + StringPool.PIPE + behavior.getAnswer(), Duration.ofMinutes(5));
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofSuccessful("获取行为验证码成功")
|
||||
.set("key", key).set("type", behavior.getType()).set("data", behavior.getData()));
|
||||
}
|
||||
|
||||
@PostMapping("/oauth/behavior/check")
|
||||
@Operation(
|
||||
summary = "校验行为验证码",
|
||||
parameters = {
|
||||
@Parameter(in = ParameterIn.QUERY, name = "key", description = "验证码key", schema = @Schema(type = "string")),
|
||||
@Parameter(in = ParameterIn.QUERY, name = "answer", description = "用户作答内容", schema = @Schema(type = "string"))
|
||||
}
|
||||
)
|
||||
public ResponseEntity<Kv> checkBehavior(String key, String answer) {
|
||||
// 答案一次性消费,校验失败须重新出题,防止对同一题目穷举作答
|
||||
String cache = bladeRedis.getAndDel(BEHAVIOR_CACHE_KEY + key);
|
||||
if (StringUtil.isBlank(cache) || StringUtil.isBlank(answer)) {
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(400, BEHAVIOR_INVALID));
|
||||
}
|
||||
int separator = cache.indexOf(CharPool.PIPE);
|
||||
BehaviorGenerator generator = BehaviorFactory.of(cache.substring(0, separator));
|
||||
if (generator == null || !generator.verify(cache.substring(separator + 1), answer)) {
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(400, BEHAVIOR_NOT_CORRECT));
|
||||
}
|
||||
// 校验通过发放一次性通行票据,登录时由行为验证码授权器核销
|
||||
String ticket = UUID.randomUUID().toString();
|
||||
bladeRedis.setEx(BEHAVIOR_TICKET_KEY + key, ticket, Duration.ofMinutes(3));
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofSuccessful("行为验证通过").set("ticket", ticket));
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/user-info")
|
||||
@Operation(summary = "获取用户信息")
|
||||
public ResponseEntity<BladeUser> userInfo(BladeUser user) {
|
||||
return ResponseEntity.ok(user);
|
||||
}
|
||||
|
||||
@GetMapping("/oauth/clear-cache")
|
||||
@Operation(summary = "清除缓存")
|
||||
public ResponseEntity<Kv> clearCache() {
|
||||
CacheUtil.clear(BIZ_CACHE);
|
||||
CacheUtil.clear(USER_CACHE);
|
||||
CacheUtil.clear(DICT_CACHE);
|
||||
CacheUtil.clear(FLOW_CACHE);
|
||||
CacheUtil.clear(SYS_CACHE);
|
||||
CacheUtil.clear(PARAM_CACHE);
|
||||
CacheUtil.clear(RESOURCE_CACHE);
|
||||
CacheUtil.clear(MENU_CACHE);
|
||||
CacheUtil.clear(DICT_CACHE, Boolean.FALSE);
|
||||
CacheUtil.clear(MENU_CACHE, Boolean.FALSE);
|
||||
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
|
||||
CacheUtil.clear(PARAM_CACHE, Boolean.FALSE);
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofSuccessful("清除缓存成功"));
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 客户端认证失败
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class ClientInvalidException extends OAuth2Exception {
|
||||
|
||||
public ClientInvalidException(String msg) {
|
||||
super(ExceptionCode.INVALID_CLIENT, msg);
|
||||
}
|
||||
|
||||
public ClientInvalidException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.INVALID_CLIENT, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 客户端未找到
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class ClientNotFoundException extends OAuth2Exception {
|
||||
|
||||
public ClientNotFoundException(String msg) {
|
||||
super(ExceptionCode.CLIENT_NOT_FOUND, msg);
|
||||
}
|
||||
|
||||
public ClientNotFoundException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.CLIENT_NOT_FOUND, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 客户端未授权
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class ClientUnauthorizedException extends OAuth2Exception {
|
||||
|
||||
public ClientUnauthorizedException(String msg) {
|
||||
super(ExceptionCode.UNAUTHORIZED_CLIENT, msg);
|
||||
}
|
||||
|
||||
public ClientUnauthorizedException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.UNAUTHORIZED_CLIENT, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import static org.springblade.core.oauth2.exception.OAuth2ErrorMessage.INVALID_ERROR_CODE;
|
||||
|
||||
/**
|
||||
* OAuth2ExceptionCode
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ExceptionCode implements Oauth2ExceptionCode {
|
||||
|
||||
/**
|
||||
* 无效请求 - 请求缺少必要的参数或格式不正确。
|
||||
*/
|
||||
INVALID_REQUEST(OAuth2ErrorCode.INVALID_REQUEST, "无效请求"),
|
||||
|
||||
/**
|
||||
* 用户不存在 - 指定的用户ID不存在或无效。
|
||||
*/
|
||||
USER_NOT_FOUND(OAuth2ErrorCode.USER_NOT_FOUND, "用户不存在"),
|
||||
|
||||
/**
|
||||
* 用户租户不存在 - 指定的用户租户未授权。
|
||||
*/
|
||||
USER_TENANT_NOT_FOUND(OAuth2ErrorCode.USER_TENANT_NOT_FOUND, "用户租户不存在"),
|
||||
|
||||
/**
|
||||
* 用户登录失败次数过多 - 用户登录失败次数过多。
|
||||
*/
|
||||
USER_TOO_MANY_FAILS(OAuth2ErrorCode.USER_TOO_MANY_FAILS, "用户登录失败次数过多"),
|
||||
|
||||
/**
|
||||
* 用户认证失败 - 指定的用户认证信息错误或无效。
|
||||
*/
|
||||
INVALID_USER(OAuth2ErrorCode.INVALID_USER, "认证信息错误或无效"),
|
||||
|
||||
/**
|
||||
* 用户密码不合法 - 指定的用户密码不合法。
|
||||
*/
|
||||
INVALID_USER_PASSWORD(OAuth2ErrorCode.INVALID_USER, "用户密码强度过低"),
|
||||
|
||||
/**
|
||||
* 用户未授权 - 指定的用户未授权。
|
||||
*/
|
||||
UNAUTHORIZED_USER(OAuth2ErrorCode.UNAUTHORIZED_USER, "认证信息错误或无效"),
|
||||
|
||||
/**
|
||||
* 用户租户未授权 - 指定的用户租户未授权。
|
||||
*/
|
||||
UNAUTHORIZED_USER_TENANT(OAuth2ErrorCode.UNAUTHORIZED_USER_TENANT, "用户租户未授权"),
|
||||
|
||||
/**
|
||||
* 用户未授权 - 指定的用户未授权。
|
||||
*/
|
||||
INVALID_REFRESH_TOKEN(OAuth2ErrorCode.INVALID_REFRESH_TOKEN, "令牌刷新错误或无效"),
|
||||
|
||||
/**
|
||||
* 客户端不存在 - 指定的客户端ID不存在或无效。
|
||||
*/
|
||||
CLIENT_NOT_FOUND(OAuth2ErrorCode.CLIENT_NOT_FOUND, "客户端不存在"),
|
||||
|
||||
/**
|
||||
* 客户端认证失败 - 客户端提供的认证信息错误或无效。
|
||||
*/
|
||||
INVALID_CLIENT(OAuth2ErrorCode.INVALID_CLIENT, "客户端认证失败"),
|
||||
|
||||
/**
|
||||
* 回调地址错误或无效 - 客户端回调地址错误或无效。
|
||||
*/
|
||||
INVALID_CLIENT_REDIRECT_URI(OAuth2ErrorCode.INVALID_CLIENT_REDIRECT_URI, "客户端未授权"),
|
||||
|
||||
/**
|
||||
* 客户端未授权 - 客户端无权执行此操作。
|
||||
*/
|
||||
UNAUTHORIZED_CLIENT(OAuth2ErrorCode.UNAUTHORIZED_CLIENT, "客户端未授权"),
|
||||
|
||||
/**
|
||||
* 不支持的授权类型 - 请求的授权类型不被服务器支持。
|
||||
*/
|
||||
UNSUPPORTED_GRANT_TYPE(OAuth2ErrorCode.UNSUPPORTED_GRANT_TYPE, "不支持的授权类型"),
|
||||
|
||||
/**
|
||||
* 无效的授权类型 - 提供的授权令牌无效、过期或被撤销。
|
||||
*/
|
||||
INVALID_GRANTER(OAuth2ErrorCode.INVALID_GRANTER, "无效的授权类型"),
|
||||
|
||||
/**
|
||||
* 无效的无效的授权范围 - 请求的无效的授权范围无效、未知或格式不正确。
|
||||
*/
|
||||
INVALID_SCOPE(OAuth2ErrorCode.INVALID_SCOPE, "授权范围"),
|
||||
|
||||
/**
|
||||
* 服务器错误 - 服务器内部错误,无法完成请求。
|
||||
*/
|
||||
SERVER_ERROR(OAuth2ErrorCode.SERVER_ERROR, "服务器错误"),
|
||||
|
||||
/**
|
||||
* 访问被拒绝 - 由于各种原因,服务器拒绝执行此操作。
|
||||
*/
|
||||
ACCESS_DENIED(OAuth2ErrorCode.ACCESS_DENIED, "访问被拒绝"),
|
||||
|
||||
/**
|
||||
* 服务暂不可用 - 服务器暂时过载或维护,无法处理请求。
|
||||
*/
|
||||
TEMPORARILY_UNAVAILABLE(OAuth2ErrorCode.TEMPORARILY_UNAVAILABLE, "服务暂不可用");
|
||||
|
||||
final int code;
|
||||
final String message;
|
||||
|
||||
/**
|
||||
* 通过错误代码获取枚举
|
||||
*
|
||||
* @param code 错误代码
|
||||
* @return ExceptionCodeEnum
|
||||
*/
|
||||
public static ExceptionCode of(int code) {
|
||||
for (ExceptionCode value : ExceptionCode.values()) {
|
||||
if (value.code == code) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(String.format(INVALID_ERROR_CODE, code));
|
||||
}
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 无效的授权
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class GranterInvalidException extends OAuth2Exception {
|
||||
|
||||
public GranterInvalidException(String msg) {
|
||||
super(ExceptionCode.INVALID_GRANTER, msg);
|
||||
}
|
||||
|
||||
public GranterInvalidException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.INVALID_GRANTER, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* OAuth2ErrorCodes
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2ErrorCode {
|
||||
/**
|
||||
* 无效请求 - 请求缺少必要的参数或格式不正确。
|
||||
*/
|
||||
int INVALID_REQUEST = 2000;
|
||||
/**
|
||||
* 用户不存在 - 指定的用户ID不存在或无效。
|
||||
*/
|
||||
int USER_NOT_FOUND = 2001;
|
||||
/**
|
||||
* 用户租户不存在 - 指定的用户租户未授权。
|
||||
*/
|
||||
int USER_TENANT_NOT_FOUND = 2002;
|
||||
/**
|
||||
* 用户登录失败次数过多 - 用户登录失败次数过多。
|
||||
*/
|
||||
int USER_TOO_MANY_FAILS = 2003;
|
||||
/**
|
||||
* 用户认证失败 - 指定的用户认证信息错误或无效。
|
||||
*/
|
||||
int INVALID_USER = 2004;
|
||||
/**
|
||||
* 用户未授权 - 指定的用户未授权。
|
||||
*/
|
||||
int UNAUTHORIZED_USER = 2005;
|
||||
/**
|
||||
* 用户租户未授权 - 指定的用户租户未授权。
|
||||
*/
|
||||
int UNAUTHORIZED_USER_TENANT = 2006;
|
||||
/**
|
||||
* 令牌刷新错误或无效 - 刷新令牌认证信息错误或无效。
|
||||
*/
|
||||
int INVALID_REFRESH_TOKEN = 2010;
|
||||
/**
|
||||
* 客户端不存在 - 指定的客户端ID不存在或无效。
|
||||
*/
|
||||
int CLIENT_NOT_FOUND = 3000;
|
||||
/**
|
||||
* 客户端认证失败 - 客户端提供的认证信息错误或无效。
|
||||
*/
|
||||
int INVALID_CLIENT = 3001;
|
||||
/**
|
||||
* 回调地址错误或无效 - 客户端回调地址错误或无效。
|
||||
*/
|
||||
int INVALID_CLIENT_REDIRECT_URI = 3002;
|
||||
/**
|
||||
* 客户端未授权 - 客户端无权执行此操作。
|
||||
*/
|
||||
int UNAUTHORIZED_CLIENT = 3003;
|
||||
/**
|
||||
* 不支持的授权类型 - 请求的授权类型不被服务器支持。
|
||||
*/
|
||||
int UNSUPPORTED_GRANT_TYPE = 4000;
|
||||
/**
|
||||
* 无效的授权类型 - 提供的授权类型无效、过期或被撤销。
|
||||
*/
|
||||
int INVALID_GRANTER = 4001;
|
||||
/**
|
||||
* 无效的授权范围 - 请求的授权范围无效、未知或格式不正确。
|
||||
*/
|
||||
int INVALID_SCOPE = 4002;
|
||||
/**
|
||||
* 服务器错误 - 服务器内部错误,无法完成请求。
|
||||
*/
|
||||
int SERVER_ERROR = 5000;
|
||||
/**
|
||||
* 访问被拒绝 - 由于各种原因,服务器拒绝执行此操作。
|
||||
*/
|
||||
int ACCESS_DENIED = 5001;
|
||||
/**
|
||||
* 服务暂不可用 - 服务器暂时过载或维护,无法处理请求。
|
||||
*/
|
||||
int TEMPORARILY_UNAVAILABLE = 5002;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* OAuth2ErrorMessage
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2ErrorMessage {
|
||||
String INVALID_GRANT_TYPE = "无效的授权类型: %s";
|
||||
|
||||
String CLIENT_AUTHORIZATION_FAILED = "客户端认证失败, 请检查请求头 [Authorization] 信息";
|
||||
|
||||
String CLIENT_TOKEN_PARSE_FAILED = "客户端令牌解析失败";
|
||||
|
||||
String INVALID_CLIENT_TOKEN = "客户端令牌不合法";
|
||||
|
||||
String AUTHORIZATION_NOT_FOUND = "请求头中未找到 [Authorization] 信息";
|
||||
String INVALID_ERROR_CODE = "无效的错误代码: %s";
|
||||
String USER_HAS_NO_TENANT = "未获得用户的租户信息";
|
||||
String USER_HAS_NO_TENANT_PERMISSION = "租户授权已过期,请联系管理员";
|
||||
String USER_HAS_TOO_MANY_FAILS = "登录错误次数过多,请稍后再试";
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* OAuth2通用异常
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Getter
|
||||
public class OAuth2Exception extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Oauth2ExceptionCode exceptionCode;
|
||||
|
||||
public OAuth2Exception(String message) {
|
||||
super(message);
|
||||
this.exceptionCode = ExceptionCode.ACCESS_DENIED;
|
||||
}
|
||||
|
||||
public OAuth2Exception(Oauth2ExceptionCode exceptionCode) {
|
||||
super(exceptionCode.getMessage());
|
||||
this.exceptionCode = exceptionCode;
|
||||
}
|
||||
|
||||
public OAuth2Exception(Oauth2ExceptionCode exceptionCode, String message) {
|
||||
super(message);
|
||||
this.exceptionCode = exceptionCode;
|
||||
}
|
||||
|
||||
public OAuth2Exception(Oauth2ExceptionCode exceptionCode, Throwable cause) {
|
||||
super(cause);
|
||||
this.exceptionCode = exceptionCode;
|
||||
}
|
||||
|
||||
public OAuth2Exception(Oauth2ExceptionCode exceptionCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.exceptionCode = exceptionCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import org.springblade.core.oauth2.endpoint.OAuth2AuthorizationEndpoint;
|
||||
import org.springblade.core.oauth2.endpoint.OAuth2SocialEndpoint;
|
||||
import org.springblade.core.oauth2.endpoint.OAuth2TokenEndPoint;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Response;
|
||||
import org.springblade.core.secure.exception.SecureException;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* OAuth2ExceptionHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@ControllerAdvice(basePackageClasses = {OAuth2AuthorizationEndpoint.class, OAuth2SocialEndpoint.class, OAuth2TokenEndPoint.class})
|
||||
public class OAuth2ExceptionHandler {
|
||||
@ExceptionHandler(OAuth2Exception.class)
|
||||
public ResponseEntity<?> handleOAuth2Exception(OAuth2Exception ex) {
|
||||
// 统一处理验证失败的情况
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(ex.getExceptionCode().getCode(), ex.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(SecureException.class)
|
||||
public ResponseEntity<?> handleSecureException(SecureException ex) {
|
||||
// 统一处理验证失败的情况
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(OAuth2ErrorCode.INVALID_USER, ex.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(JwtException.class)
|
||||
public ResponseEntity<?> handleJwtException(JwtException ex) {
|
||||
// 统一处理验证失败的情况
|
||||
return ResponseEntity.ok(OAuth2Response.create().ofFailure(OAuth2ErrorCode.ACCESS_DENIED, ex.getMessage()));
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* Oauth2ExceptionCode
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface Oauth2ExceptionCode {
|
||||
/**
|
||||
* 获取异常编码
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
int getCode();
|
||||
|
||||
/**
|
||||
* 获取异常消息
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getMessage();
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 用户认证失败
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class UserInvalidException extends OAuth2Exception {
|
||||
|
||||
public UserInvalidException(String msg) {
|
||||
super(ExceptionCode.INVALID_USER, msg);
|
||||
}
|
||||
|
||||
public UserInvalidException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.INVALID_USER, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 用户未授权
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class UserUnauthorizedException extends OAuth2Exception {
|
||||
|
||||
public UserUnauthorizedException(String msg) {
|
||||
super(ExceptionCode.UNAUTHORIZED_USER, msg);
|
||||
}
|
||||
|
||||
public UserUnauthorizedException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.UNAUTHORIZED_USER, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.exception;
|
||||
|
||||
/**
|
||||
* 用户名未找到
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class UsernameNotFoundException extends OAuth2Exception {
|
||||
|
||||
public UsernameNotFoundException(String msg) {
|
||||
super(ExceptionCode.USER_NOT_FOUND, msg);
|
||||
}
|
||||
|
||||
public UsernameNotFoundException(String msg, Throwable cause) {
|
||||
super(ExceptionCode.USER_NOT_FOUND, msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.launch.constant.TokenConstant;
|
||||
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
|
||||
import org.springblade.core.oauth2.utils.OAuth2Util;
|
||||
import org.springblade.core.secure.TokenInfo;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* AbstractTokenGranter
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public abstract class AbstractTokenGranter implements TokenGranter {
|
||||
|
||||
/**
|
||||
* 认证失败原因:用户不存在
|
||||
*/
|
||||
private static final String REASON_USER_NOT_FOUND = "用户不存在";
|
||||
/**
|
||||
* 认证失败原因:用户状态异常
|
||||
*/
|
||||
private static final String REASON_USER_INVALID = "用户状态异常";
|
||||
/**
|
||||
* 认证失败原因:密码匹配失败
|
||||
*/
|
||||
private static final String REASON_PASSWORD_MISMATCH = "密码匹配失败";
|
||||
|
||||
private final OAuth2ClientService clientService;
|
||||
private final OAuth2UserService userService;
|
||||
private final PasswordHandler passwordHandler;
|
||||
|
||||
protected TokenGranterEnhancer enhancer;
|
||||
|
||||
public abstract String type();
|
||||
|
||||
@Override
|
||||
public void enhancer(TokenGranterEnhancer enhancer) {
|
||||
this.enhancer = enhancer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Client client(OAuth2Request request) {
|
||||
// 获取clientId
|
||||
String clientId = request.getClientId();
|
||||
// 获取clientSecret
|
||||
String clientSecret = request.getClientSecret();
|
||||
|
||||
// 获取客户端信息
|
||||
OAuth2Client client = clientService.loadByClientId(clientId);
|
||||
|
||||
// 校验客户端信息
|
||||
if (!clientService.validateClient(client, clientId, clientSecret)) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_CLIENT);
|
||||
}
|
||||
|
||||
// 校验授权类型
|
||||
if (!clientService.validateGranter(client, type())) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_GRANTER);
|
||||
}
|
||||
|
||||
// 返回客户端信息
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
// 获取用户信息
|
||||
OAuth2User user = (StringUtil.isNotBlank(request.getUserId()) && request.isRefreshToken())
|
||||
? userService.loadByUserId(request.getUserId(), request)
|
||||
: userService.loadByUsername(request.getUsername(), request);
|
||||
|
||||
// 用户不存在
|
||||
if (user == null) {
|
||||
rejectUser(request, REASON_USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 校验用户信息
|
||||
if (!userService.validateUser(Objects.requireNonNull(user))) {
|
||||
rejectUser(request, REASON_USER_INVALID);
|
||||
}
|
||||
|
||||
// 校验用户密码
|
||||
if (request.isCaptchaCode() || request.isPassword()) {
|
||||
// 验证码模式和密码模式需要校验密码
|
||||
boolean matches = passwordHandler.matches(request.getPassword(), user.getPassword());
|
||||
if (!matches) {
|
||||
rejectUser(request, REASON_PASSWORD_MISMATCH);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置客户端信息
|
||||
user.setClient(client(request));
|
||||
|
||||
// 返回用户信息
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝认证
|
||||
* <p>
|
||||
* 将真实失败原因记录到服务端日志便于排查,对外统一返回通用错误以防止账号枚举。
|
||||
*
|
||||
* @param request 认证请求
|
||||
* @param reason 真实失败原因
|
||||
*/
|
||||
private void rejectUser(OAuth2Request request, String reason) {
|
||||
log.warn("OAuth2 认证失败 → {} | tenantId={}, username={}, grantType={}",
|
||||
reason, request.getTenantId(), request.getUsername(), request.getGrantType());
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Token token(OAuth2User user, OAuth2Request request) {
|
||||
// 首次认证时间作为绝对有效期基线:刷新授权沿用旧令牌透传的时间,其余授权以当前时间为起点
|
||||
long authTime = (request.getAuthTime() != null) ? request.getAuthTime() : Instant.now().getEpochSecond();
|
||||
TokenInfo accessToken = OAuth2Util.createAccessToken(user);
|
||||
TokenInfo refreshToken = OAuth2Util.createRefreshToken(user, authTime);
|
||||
|
||||
OAuth2Token token = OAuth2Token.create();
|
||||
|
||||
token.getArgs().set(TokenConstant.TENANT_ID, user.getTenantId())
|
||||
.set(TokenConstant.USER_ID, user.getUserId())
|
||||
.set(TokenConstant.DEPT_ID, user.getDeptId())
|
||||
.set(TokenConstant.POST_ID, user.getPostId())
|
||||
.set(TokenConstant.ROLE_ID, user.getRoleId())
|
||||
.set(TokenConstant.OAUTH_ID, user.getOauthId())
|
||||
.set(TokenConstant.ACCOUNT, user.getAccount())
|
||||
.set(TokenConstant.USER_NAME, user.getAccount())
|
||||
.set(TokenConstant.NICK_NAME, user.getName())
|
||||
.set(TokenConstant.REAL_NAME, user.getRealName())
|
||||
.set(TokenConstant.ROLE_NAME, Func.join(user.getAuthorities()))
|
||||
.set(TokenConstant.AVATAR, Func.toStr(user.getAvatar(), TokenConstant.DEFAULT_AVATAR))
|
||||
.set(TokenConstant.ACCESS_TOKEN, accessToken.getToken())
|
||||
.set(TokenConstant.REFRESH_TOKEN, refreshToken.getToken())
|
||||
.set(TokenConstant.TOKEN_TYPE, TokenConstant.BEARER)
|
||||
.set(TokenConstant.EXPIRES_IN, accessToken.getExpire())
|
||||
.set(TokenConstant.DETAIL, user.getDetail())
|
||||
.set(TokenConstant.LICENSE, TokenConstant.LICENSE_NAME);
|
||||
|
||||
return token.setAccessToken(accessToken.getToken())
|
||||
.setAccessTokenExpire(accessToken.getExpire())
|
||||
.setRefreshToken(refreshToken.getToken())
|
||||
.setRefreshTokenExpire(refreshToken.getExpire());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import org.springblade.core.oauth2.exception.ExceptionCode;
|
||||
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
|
||||
import org.springblade.core.oauth2.exception.UserInvalidException;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.utils.OAuth2CodeUtil;
|
||||
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
|
||||
import org.springblade.core.redis.cache.BladeRedis;
|
||||
import org.springblade.core.tool.utils.ObjectUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* AuthorizationCodeGranter
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Component
|
||||
public class AuthorizationCodeGranter extends AbstractTokenGranter {
|
||||
|
||||
private final OAuth2UserService userService;
|
||||
private final PasswordHandler passwordHandler;
|
||||
private final BladeRedis bladeRedis;
|
||||
|
||||
public AuthorizationCodeGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, BladeRedis bladeRedis) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
this.userService = userService;
|
||||
this.passwordHandler = passwordHandler;
|
||||
this.bladeRedis = bladeRedis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return AUTHORIZATION_CODE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
// 获取客户端信息并校验
|
||||
OAuth2Client client = client(request);
|
||||
if (!StringUtil.equals(client.getWebServerRedirectUri(), request.getRedirectUri())) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_CLIENT_REDIRECT_URI);
|
||||
}
|
||||
// 根据code获取用户信息
|
||||
String code = request.getCode();
|
||||
OAuth2User user = bladeRedis.get(OAuth2CodeUtil.codeKey(code));
|
||||
// 判断用户是否存在
|
||||
if (ObjectUtil.isNotEmpty(user)) {
|
||||
// 校验用户信息
|
||||
if (!userService.validateUser(user)) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
// 校验用户密码
|
||||
if ((request.isCaptchaCode() || request.isPassword()) && !passwordHandler.matches(request.getPassword(), user.getPassword())) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
// 设置客户端信息
|
||||
user.setClient(client);
|
||||
// 删除缓存的code信息防止二次认证
|
||||
bladeRedis.del(OAuth2CodeUtil.codeKey(code));
|
||||
// 返回user
|
||||
return Optional.ofNullable(this.enhancer)
|
||||
.map(enhancer -> enhancer.enhance(user, request))
|
||||
.orElse(user);
|
||||
}
|
||||
throw new UserInvalidException(ExceptionCode.INVALID_USER.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import org.springblade.core.launch.constant.TokenConstant;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
|
||||
import org.springblade.core.oauth2.utils.OAuth2Util;
|
||||
import org.springblade.core.secure.TokenInfo;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* ClientCredentialsGranter
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Component
|
||||
public class ClientCredentialsGranter extends AbstractTokenGranter {
|
||||
|
||||
public ClientCredentialsGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return CLIENT_CREDENTIALS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
OAuth2UserDetail user = new OAuth2UserDetail();
|
||||
OAuth2Client client = client(request);
|
||||
user.setTenantId(request.getTenantId());
|
||||
user.setClient(client);
|
||||
user.setAccount(client.getClientId());
|
||||
user.setName(client.getClientId());
|
||||
return Optional.ofNullable(this.enhancer)
|
||||
.map(enhancer -> enhancer.enhance(user, request))
|
||||
.orElse(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Token token(OAuth2User user, OAuth2Request request) {
|
||||
TokenInfo accessToken = OAuth2Util.createClientAccessToken(user);
|
||||
|
||||
OAuth2Token token = OAuth2Token.create();
|
||||
|
||||
token.getArgs().set(TokenConstant.TENANT_ID, user.getTenantId())
|
||||
.set(TokenConstant.CLIENT_ID, user.getClient().getClientId())
|
||||
.set(TokenConstant.AVATAR, Func.toStr(user.getAvatar(), TokenConstant.DEFAULT_AVATAR))
|
||||
.set(TokenConstant.ACCESS_TOKEN, accessToken.getToken())
|
||||
.set(TokenConstant.TOKEN_TYPE, TokenConstant.BEARER)
|
||||
.set(TokenConstant.EXPIRES_IN, accessToken.getExpire())
|
||||
.set(TokenConstant.DETAIL, user.getDetail())
|
||||
.set(TokenConstant.LICENSE, TokenConstant.LICENSE_NAME);
|
||||
|
||||
return token.setAccessToken(accessToken.getToken()).setAccessTokenExpire(accessToken.getExpire());
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import org.springblade.core.launch.constant.TokenConstant;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2ClientDetail;
|
||||
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
|
||||
import org.springblade.core.oauth2.utils.OAuth2Util;
|
||||
import org.springblade.core.secure.TokenInfo;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* ImplicitGranter
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Component
|
||||
public class ImplicitGranter extends AbstractTokenGranter {
|
||||
|
||||
public ImplicitGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return IMPLICIT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Client client(OAuth2Request request) {
|
||||
return new OAuth2ClientDetail();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
OAuth2UserDetail user = new OAuth2UserDetail();
|
||||
user.setTenantId(request.getTenantId());
|
||||
user.setAccount(request.getUsername());
|
||||
user.setName(request.getUsername());
|
||||
return Optional.ofNullable(this.enhancer)
|
||||
.map(enhancer -> enhancer.enhance(user, request))
|
||||
.orElse(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Token token(OAuth2User user, OAuth2Request request) {
|
||||
TokenInfo accessToken = OAuth2Util.createImplicitAccessToken(user);
|
||||
|
||||
OAuth2Token token = OAuth2Token.create();
|
||||
|
||||
token.getArgs().set(TokenConstant.TENANT_ID, user.getTenantId())
|
||||
.set(TokenConstant.USER_NAME, user.getAccount())
|
||||
.set(TokenConstant.AVATAR, Func.toStr(user.getAvatar(), TokenConstant.DEFAULT_AVATAR))
|
||||
.set(TokenConstant.ACCESS_TOKEN, accessToken.getToken())
|
||||
.set(TokenConstant.TOKEN_TYPE, TokenConstant.BEARER)
|
||||
.set(TokenConstant.EXPIRES_IN, accessToken.getExpire())
|
||||
.set(TokenConstant.DETAIL, user.getDetail())
|
||||
.set(TokenConstant.LICENSE, TokenConstant.LICENSE_NAME);
|
||||
|
||||
return token.setAccessToken(accessToken.getToken()).setAccessTokenExpire(accessToken.getExpire());
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PasswordTokenGranter
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Component
|
||||
public class PasswordTokenGranter extends AbstractTokenGranter {
|
||||
|
||||
public PasswordTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
OAuth2User user = super.user(request);
|
||||
return Optional.ofNullable(this.enhancer)
|
||||
.map(enhancer -> enhancer.enhance(user, request))
|
||||
.orElse(user);
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.jwt.JwtUtil;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springblade.core.launch.constant.TokenConstant;
|
||||
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
|
||||
import org.springblade.core.oauth2.handler.PasswordHandler;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springblade.core.oauth2.utils.OAuth2ExceptionUtil;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PasswordTokenGranter
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RefreshTokenGranter extends PasswordTokenGranter {
|
||||
|
||||
private final JwtProperties jwtProperties;
|
||||
|
||||
public RefreshTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, JwtProperties jwtProperties) {
|
||||
super(clientService, userService, passwordHandler);
|
||||
this.jwtProperties = jwtProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String type() {
|
||||
return REFRESH_TOKEN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User user(OAuth2Request request) {
|
||||
String refreshToken = request.getRefreshToken();
|
||||
// 刷新令牌缺失属于无效请求,提前拦截并返回明确的错误码,避免空令牌进入解析链路
|
||||
if (StringUtil.isBlank(refreshToken)) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_REFRESH_TOKEN);
|
||||
}
|
||||
// 解析refreshToken,获取其中的声明信息
|
||||
Claims refreshClaims = JwtUtil.parseJWT(refreshToken);
|
||||
if (refreshClaims == null) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
// 校验refreshToken的合法性
|
||||
if (!judgeRefreshToken(refreshClaims, refreshToken, request)) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_REFRESH_TOKEN);
|
||||
}
|
||||
// 校验refreshToken的格式
|
||||
String tokenType = String.valueOf(Objects.requireNonNull(refreshClaims).get(TokenConstant.TOKEN_TYPE));
|
||||
if (!StringUtil.equals(tokenType, TokenConstant.REFRESH_TOKEN)) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_REFRESH_TOKEN);
|
||||
}
|
||||
// 校验refreshToken是否可获取username
|
||||
String userId = String.valueOf(refreshClaims.get(TokenConstant.USER_ID));
|
||||
if (StringUtil.isBlank(userId)) {
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
|
||||
}
|
||||
// 设置username
|
||||
request.setUserId(userId);
|
||||
// 认证绝对有效期校验并透传首次认证时间
|
||||
enforceAuthMaxAge(refreshClaims, request);
|
||||
return super.user(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验认证是否超过最长有效期上限,并将首次认证时间透传给后续令牌构建。
|
||||
* <p>
|
||||
* 首次认证时间存放于刷新令牌的签名声明中,滑动续期时原样沿用而不重置,使认证总寿命存在硬性上限。
|
||||
* 该校验不依赖服务端令牌状态,无状态模式下同样生效,用于收敛刷新令牌失窃后的最长可用时长。
|
||||
* 存量令牌不含该声明时视为本次刷新起始,平滑兼容历史令牌,避免升级即触发全员重新登录。
|
||||
*
|
||||
* @param refreshClaims 刷新令牌声明
|
||||
* @param request 授权请求
|
||||
*/
|
||||
private void enforceAuthMaxAge(Claims refreshClaims, OAuth2Request request) {
|
||||
Long authMaxAge = jwtProperties.getAuthMaxAge();
|
||||
if (authMaxAge == null || authMaxAge <= 0) {
|
||||
return;
|
||||
}
|
||||
Object authTimeClaim = refreshClaims.get(TokenConstant.AUTH_TIME);
|
||||
if (authTimeClaim == null) {
|
||||
return;
|
||||
}
|
||||
long authTime = Long.parseLong(String.valueOf(authTimeClaim));
|
||||
long elapsedSeconds = Instant.now().getEpochSecond() - authTime;
|
||||
long maxAgeSeconds = Duration.ofDays(authMaxAge).getSeconds();
|
||||
if (elapsedSeconds > maxAgeSeconds) {
|
||||
log.warn("刷新令牌已超过认证最长有效期,拒绝续期 → userId={}, clientId={}, elapsedSeconds={}, maxAgeDays={}",
|
||||
refreshClaims.get(TokenConstant.USER_ID), request.getClientId(), elapsedSeconds, authMaxAge);
|
||||
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_REFRESH_TOKEN);
|
||||
}
|
||||
request.setAuthTime(authTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验refreshToken的合法性
|
||||
*
|
||||
* @param refreshToken 待校验的refreshToken
|
||||
* @return refreshToken是否合法
|
||||
*/
|
||||
private boolean judgeRefreshToken(Claims refreshClaims, String refreshToken, OAuth2Request request) {
|
||||
// 首先检查JWT是否启用单人登录模式,如果不是则直接返回true
|
||||
if (!jwtProperties.getState() || !jwtProperties.getSingle()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 解析JWT,如果无法解析则认为不合法
|
||||
return Optional.ofNullable(refreshClaims)
|
||||
.map(claims -> {
|
||||
// 提取 tenantId clientId 和 userId
|
||||
String tenantId = request.getTenantId();
|
||||
String clientId = request.getClientId();
|
||||
String userId = String.valueOf(claims.get(TokenConstant.USER_ID));
|
||||
|
||||
// 根据提取的信息和refreshToken生成新的token
|
||||
String token = JwtUtil.getRefreshToken(tenantId, clientId, userId, refreshToken);
|
||||
|
||||
// 比较新生成的token与传入的refreshToken是否一致,如果一致则认为合法
|
||||
return StringUtil.equalsIgnoreCase(token, refreshToken);
|
||||
})
|
||||
.orElse(false); // 如果claims为空,则返回false
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import org.springblade.core.oauth2.constant.OAuth2GranterConstant;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
|
||||
/**
|
||||
* 授权认证统一接口.
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface TokenGranter extends OAuth2GranterConstant {
|
||||
|
||||
/**
|
||||
* 获取授权模式
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String type();
|
||||
|
||||
/**
|
||||
* 获取客户端信息
|
||||
*
|
||||
* @param request 授权参数
|
||||
* @return OAuth2Client
|
||||
*/
|
||||
OAuth2Client client(OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param request 授权参数
|
||||
* @return OAuth2User
|
||||
*/
|
||||
OAuth2User user(OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 创建令牌
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param request 授权参数
|
||||
* @return OAuth2Token
|
||||
*/
|
||||
OAuth2Token token(OAuth2User user, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 自定义增强
|
||||
*
|
||||
* @param enhancer enhancer
|
||||
*/
|
||||
void enhancer(TokenGranterEnhancer enhancer);
|
||||
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import org.springblade.core.oauth2.constant.OAuth2GranterConstant;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
|
||||
/**
|
||||
* TokenGranterEnhancer
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface TokenGranterEnhancer extends OAuth2GranterConstant {
|
||||
|
||||
/**
|
||||
* 获取授权模式
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String type();
|
||||
|
||||
/**
|
||||
* 增强用户令牌
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param request 授权参数
|
||||
* @return OAuth2User
|
||||
*/
|
||||
OAuth2User enhance(OAuth2User user, OAuth2Request request);
|
||||
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.granter;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.oauth2.exception.GranterInvalidException;
|
||||
import org.springblade.core.oauth2.props.OAuth2Properties;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static org.springblade.core.oauth2.constant.OAuth2GranterConstant.*;
|
||||
import static org.springblade.core.oauth2.exception.OAuth2ErrorMessage.INVALID_GRANT_TYPE;
|
||||
|
||||
/**
|
||||
* TokenGranterFactory
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class TokenGranterFactory {
|
||||
|
||||
/**
|
||||
* TokenGranter 集合
|
||||
*/
|
||||
private final List<TokenGranter> tokenGranters;
|
||||
|
||||
/**
|
||||
* TokenGranterEnhancer 集合
|
||||
*/
|
||||
private final List<TokenGranterEnhancer> tokenGranterEnhancers;
|
||||
|
||||
/**
|
||||
* OAuth2 属性配置
|
||||
*/
|
||||
private final OAuth2Properties properties;
|
||||
|
||||
/**
|
||||
* TokenGranter 缓存池,使用 ConcurrentHashMap 保证线程安全。
|
||||
*/
|
||||
private static final Map<String, TokenGranter> GRANTER_POOL = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 根据授权模式获取 TokenGranter,如果缓存中不存在,则尝试创建。
|
||||
*
|
||||
* @param grantType 授权模式
|
||||
* @return TokenGranter 实例
|
||||
* @throws IllegalArgumentException 如果请求的授权类型不支持
|
||||
*/
|
||||
public TokenGranter create(String grantType) {
|
||||
// 使用 computeIfAbsent 实现延迟加载
|
||||
return GRANTER_POOL.computeIfAbsent(grantType, this::initializeTokenGranter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试根据授权类型初始化 TokenGranter。
|
||||
*
|
||||
* @param grantType 授权模式
|
||||
* @return 初始化的 TokenGranter
|
||||
* @throws GranterInvalidException 如果无法识别授权类型
|
||||
*/
|
||||
private TokenGranter initializeTokenGranter(String grantType) {
|
||||
// 根据授权类型查找对应的 TokenGranter 并应用第一个找到的增强类
|
||||
return tokenGranters.stream()
|
||||
.filter(granter -> granter.type().equals(grantType) && isGranterEnabled(granter))
|
||||
.peek(granter -> tokenGranterEnhancers.stream()
|
||||
.filter(enhancer -> enhancer.type().equals(grantType))
|
||||
.findFirst()
|
||||
.ifPresent(granter::enhancer))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new GranterInvalidException(String.format(INVALID_GRANT_TYPE, grantType)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 TokenGranter 是否启用。
|
||||
*
|
||||
* @param granter TokenGranter 实例
|
||||
* @return 是否启用
|
||||
*/
|
||||
private boolean isGranterEnabled(TokenGranter granter) {
|
||||
return switch (granter.type()) {
|
||||
case AUTHORIZATION_CODE -> properties.getGranter().getAuthorizationCode();
|
||||
case PASSWORD -> properties.getGranter().getPassword();
|
||||
case REFRESH_TOKEN -> properties.getGranter().getRefreshToken();
|
||||
case CLIENT_CREDENTIALS -> properties.getGranter().getClientCredentials();
|
||||
case IMPLICIT -> properties.getGranter().getImplicit();
|
||||
case CAPTCHA -> properties.getGranter().getCaptcha();
|
||||
case BEHAVIOR -> properties.getGranter().getBehavior();
|
||||
case SMS_CODE -> properties.getGranter().getSmsCode();
|
||||
case WECHAT_APPLET -> properties.getGranter().getWechatApplet();
|
||||
case SOCIAL -> properties.getGranter().getSocial();
|
||||
case REGISTER -> properties.getGranter().getRegister();
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
import org.springblade.core.oauth2.exception.ExceptionCode;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Validation;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
|
||||
/**
|
||||
* AbstractAuthorizationHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public abstract class AbstractAuthorizationHandler implements AuthorizationHandler {
|
||||
|
||||
|
||||
/**
|
||||
* 认证前校验
|
||||
*
|
||||
* @param request 请求信息
|
||||
* @return boolean
|
||||
*/
|
||||
@Override
|
||||
public OAuth2Validation preValidation(OAuth2Request request) {
|
||||
if (request.isClientCredentials() || request.isImplicit() || request.isSocial()) {
|
||||
return new OAuth2Validation();
|
||||
}
|
||||
if (request.isPassword() || request.isCaptchaCode()) {
|
||||
if (Func.hasEmpty(request.getUsername())) {
|
||||
return buildValidationFailure(ExceptionCode.INVALID_USER);
|
||||
}
|
||||
}
|
||||
return new OAuth2Validation();
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证前失败回调
|
||||
*
|
||||
* @param validation 失败信息
|
||||
*/
|
||||
@Override
|
||||
public abstract void preFailure(OAuth2Request request, OAuth2Validation validation);
|
||||
|
||||
|
||||
/**
|
||||
* 认证校验
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param request 请求信息
|
||||
* @return boolean
|
||||
*/
|
||||
@Override
|
||||
public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
|
||||
if (request.isClientCredentials() || request.isImplicit() || request.isSocial()) {
|
||||
return new OAuth2Validation();
|
||||
}
|
||||
if (Func.hasEmpty(user, user.getUserId())) {
|
||||
return buildValidationFailure(ExceptionCode.INVALID_USER);
|
||||
}
|
||||
if (Func.isEmpty(user.getAuthorities())) {
|
||||
return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER);
|
||||
}
|
||||
return new OAuth2Validation();
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证成功回调
|
||||
*
|
||||
* @param user 用户信息
|
||||
*/
|
||||
@Override
|
||||
public abstract void authSuccessful(OAuth2User user, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 认证失败回调
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param validation 失败信息
|
||||
*/
|
||||
@Override
|
||||
public abstract void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation);
|
||||
|
||||
/**
|
||||
* 构建认证失败返回
|
||||
*
|
||||
* @param errorCode 错误码
|
||||
* @return 认证结果
|
||||
*/
|
||||
public OAuth2Validation buildValidationFailure(ExceptionCode errorCode) {
|
||||
return new OAuth2Validation().setSuccess(false)
|
||||
.setCode(errorCode.getCode())
|
||||
.setMessage(errorCode.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Validation;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
|
||||
/**
|
||||
* OAuth2AuthorizationHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface AuthorizationHandler {
|
||||
|
||||
/**
|
||||
* 认证前校验
|
||||
*
|
||||
* @param request 请求信息
|
||||
* @return boolean
|
||||
*/
|
||||
OAuth2Validation preValidation(OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 认证前失败回调
|
||||
*
|
||||
* @param validation 失败信息
|
||||
*/
|
||||
void preFailure(OAuth2Request request, OAuth2Validation validation);
|
||||
|
||||
/**
|
||||
* 认证校验
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param request 请求信息
|
||||
* @return boolean
|
||||
*/
|
||||
OAuth2Validation authValidation(OAuth2User user, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 认证成功回调
|
||||
*
|
||||
* @param user 用户信息
|
||||
*/
|
||||
void authSuccessful(OAuth2User user, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 认证失败回调
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param validation 失败信息
|
||||
*/
|
||||
void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation);
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Validation;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
|
||||
/**
|
||||
* AbstractAuthorizationHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Slf4j
|
||||
public class OAuth2AuthorizationHandler extends AbstractAuthorizationHandler {
|
||||
|
||||
@Override
|
||||
public void preFailure(OAuth2Request request, OAuth2Validation validation) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证成功回调
|
||||
*
|
||||
* @param user 用户信息
|
||||
*/
|
||||
@Override
|
||||
public void authSuccessful(OAuth2User user, OAuth2Request request) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证失败回调
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param validation 失败信息
|
||||
*/
|
||||
@Override
|
||||
public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.oauth2.props.OAuth2Properties;
|
||||
import org.springblade.core.tool.utils.DigestUtil;
|
||||
import org.springblade.core.tool.utils.SM2Util;
|
||||
|
||||
/**
|
||||
* BladePasswordHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class OAuth2PasswordHandler implements PasswordHandler {
|
||||
|
||||
private final OAuth2Properties properties;
|
||||
|
||||
/**
|
||||
* 判断密码是否匹配
|
||||
*
|
||||
* @param rawPassword 请求时提交的原密码
|
||||
* @param encodedPassword 数据库加密后的密码
|
||||
* @return boolean
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(String rawPassword, String encodedPassword) {
|
||||
// 获取公钥
|
||||
String publicKey = properties.getPublicKey();
|
||||
// 获取私钥
|
||||
String privateKey = properties.getPrivateKey();
|
||||
// 解密密码
|
||||
String decryptPassword = SM2Util.decrypt(rawPassword, publicKey, privateKey);
|
||||
// 二次加密后与数据库加密密码匹配
|
||||
return encodedPassword.equals(encode(decryptPassword));
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码规则
|
||||
*
|
||||
* @param rawPassword 密码
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
@Override
|
||||
public String encode(String rawPassword) {
|
||||
return DigestUtil.encrypt(rawPassword);
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.jwt.JwtUtil;
|
||||
import org.springblade.core.jwt.props.JwtProperties;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
|
||||
/**
|
||||
* BladeTokenHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class OAuth2TokenHandler implements TokenHandler {
|
||||
|
||||
private final JwtProperties properties;
|
||||
|
||||
/**
|
||||
* 令牌增强
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param token 令牌信息
|
||||
* @param request 授权参数
|
||||
* @return OAuth2Token
|
||||
*/
|
||||
@Override
|
||||
public OAuth2Token enhance(OAuth2User user, OAuth2Token token, OAuth2Request request) {
|
||||
|
||||
//令牌状态配置, 仅在生成AccessToken时候执行
|
||||
if (properties.getState() && token.hasAccessToken()) {
|
||||
JwtUtil.addAccessToken(
|
||||
user.getTenantId(),
|
||||
user.getClient().getClientId(),
|
||||
user.getUserId(),
|
||||
token.getAccessToken(),
|
||||
token.getAccessTokenExpire()
|
||||
);
|
||||
}
|
||||
//令牌状态配置, 仅在生成RefreshToken时候执行
|
||||
if (properties.getState() && properties.getSingle() && token.hasRefreshToken()) {
|
||||
JwtUtil.addRefreshToken(
|
||||
user.getTenantId(),
|
||||
user.getClient().getClientId(),
|
||||
user.getUserId(),
|
||||
token.getRefreshToken(),
|
||||
token.getRefreshTokenExpire()
|
||||
);
|
||||
}
|
||||
|
||||
// 返回令牌
|
||||
return token;
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
/**
|
||||
* PasswordHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface PasswordHandler {
|
||||
|
||||
/**
|
||||
* 判断密码是否匹配
|
||||
*
|
||||
* @param rawPassword 请求时提交的原密码
|
||||
* @param encodedPassword 数据库加密后的密码
|
||||
* @return boolean
|
||||
*/
|
||||
boolean matches(String rawPassword, String encodedPassword);
|
||||
|
||||
/**
|
||||
* 加密密码规则
|
||||
*
|
||||
* @param rawPassword 密码
|
||||
* @return 加密后的密码
|
||||
*/
|
||||
String encode(String rawPassword);
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.handler;
|
||||
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Token;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
|
||||
/**
|
||||
* TokenHandler
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface TokenHandler {
|
||||
|
||||
/**
|
||||
* 令牌增强
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param token 令牌信息
|
||||
* @param request 授权参数
|
||||
* @return OAuth2Token
|
||||
*/
|
||||
OAuth2Token enhance(OAuth2User user, OAuth2Token token, OAuth2Request request);
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.props;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* OAuth2Property
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties(OAuth2Properties.PREFIX)
|
||||
public class OAuth2Properties {
|
||||
/**
|
||||
* 配置前缀
|
||||
*/
|
||||
public static final String PREFIX = "blade.oauth2";
|
||||
|
||||
/**
|
||||
* 是否开启OAuth2
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* code缓存时间
|
||||
*/
|
||||
private long codeTimeout = 10 * 60L;
|
||||
|
||||
/**
|
||||
* sm2公钥
|
||||
*/
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* sm2私钥
|
||||
*/
|
||||
private String privateKey;
|
||||
|
||||
/**
|
||||
* 授权模式
|
||||
*/
|
||||
private Granter granter = new Granter();
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public static class Granter {
|
||||
/**
|
||||
* 是否开启授权码模式
|
||||
*/
|
||||
private Boolean authorizationCode = true;
|
||||
/**
|
||||
* 是否开启验证码模式
|
||||
*/
|
||||
private Boolean captcha = true;
|
||||
/**
|
||||
* 是否开启行为验证码模式
|
||||
*/
|
||||
private Boolean behavior = true;
|
||||
/**
|
||||
* 是否开启密码模式
|
||||
*/
|
||||
private Boolean password = true;
|
||||
/**
|
||||
* 是否开启刷新token模式
|
||||
*/
|
||||
private Boolean refreshToken = true;
|
||||
/**
|
||||
* 是否开启客户端模式
|
||||
*/
|
||||
private Boolean clientCredentials = true;
|
||||
/**
|
||||
* 是否开启简化模式
|
||||
*/
|
||||
private Boolean implicit = true;
|
||||
/**
|
||||
* 是否手机验证码模式
|
||||
*/
|
||||
private Boolean smsCode = true;
|
||||
/**
|
||||
* 是否开启微信小程序模式
|
||||
*/
|
||||
private Boolean wechatApplet = true;
|
||||
/**
|
||||
* 是否开启开放平台模式
|
||||
*/
|
||||
private Boolean social = true;
|
||||
/**
|
||||
* 是否开启注册模式
|
||||
*/
|
||||
private Boolean register = true;
|
||||
}
|
||||
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.provider;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.springblade.core.oauth2.constant.OAuth2ParameterConstant.*;
|
||||
|
||||
/**
|
||||
* OAuth2AuthorizationRequest
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
public class OAuth2AuthorizationRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String responseType;
|
||||
private String tenantId;
|
||||
private String clientId;
|
||||
private String redirectUri;
|
||||
private String scope;
|
||||
private String state;
|
||||
|
||||
/**
|
||||
* 实例化
|
||||
*/
|
||||
public static OAuth2AuthorizationRequest create() {
|
||||
return new OAuth2AuthorizationRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建参数
|
||||
*
|
||||
* @return OAuth2AuthorizationRequest
|
||||
*/
|
||||
public OAuth2AuthorizationRequest buildParameters() {
|
||||
HttpServletRequest request = Objects.requireNonNull(WebUtil.getRequest());
|
||||
this.responseType = request.getParameter(RESPONSE_TYPE);
|
||||
this.tenantId = request.getParameter(TENANT_ID);
|
||||
this.clientId = request.getParameter(CLIENT_ID);
|
||||
this.redirectUri = request.getParameter(REDIRECT_URI);
|
||||
this.scope = request.getParameter(SCOPE);
|
||||
this.state = request.getParameter(STATE);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数
|
||||
*
|
||||
* @return Map
|
||||
*/
|
||||
public Map<String, String> getParameters() {
|
||||
Map<String, String> parameters = new HashMap<>();
|
||||
parameters.put(RESPONSE_TYPE, this.responseType);
|
||||
parameters.put(CLIENT_ID, this.clientId);
|
||||
parameters.put(REDIRECT_URI, this.redirectUri);
|
||||
if (scope != null) {
|
||||
parameters.put(SCOPE, this.scope);
|
||||
}
|
||||
if (this.tenantId != null) {
|
||||
parameters.put(STATE, this.tenantId);
|
||||
}
|
||||
if (state != null) {
|
||||
parameters.put(STATE, this.state);
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return StringUtil.isBlank(this.state) ? this.tenantId : this.state;
|
||||
}
|
||||
}
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.provider;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.oauth2.utils.OAuth2Util;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springblade.core.tool.utils.WebUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.springblade.core.oauth2.constant.OAuth2GranterConstant.PASSWORD;
|
||||
import static org.springblade.core.oauth2.constant.OAuth2GranterConstant.REFRESH_TOKEN;
|
||||
import static org.springblade.core.oauth2.constant.OAuth2GranterConstant.*;
|
||||
import static org.springblade.core.oauth2.constant.OAuth2ParameterConstant.*;
|
||||
import static org.springblade.core.oauth2.constant.OAuth2TokenConstant.*;
|
||||
|
||||
/**
|
||||
* OAuth2参数类
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
public class OAuth2Request {
|
||||
|
||||
/**
|
||||
* 实例化
|
||||
*/
|
||||
public static OAuth2Request create() {
|
||||
return new OAuth2Request();
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端参数
|
||||
*/
|
||||
private Kv clientArgs = Kv.create();
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private Kv parameterArgs = Kv.create();
|
||||
|
||||
/**
|
||||
* 头部参数
|
||||
*/
|
||||
private Kv headerArgs = Kv.create();
|
||||
|
||||
/**
|
||||
* 当前HTTP请求对象
|
||||
*/
|
||||
private HttpServletRequest httpRequest;
|
||||
|
||||
/**
|
||||
* 首次认证时间(epoch 秒)
|
||||
*/
|
||||
private Long authTime;
|
||||
|
||||
/**
|
||||
* 自动构建参数
|
||||
*
|
||||
* @return OAuth2Request
|
||||
*/
|
||||
public OAuth2Request buildArgs() {
|
||||
return this.buildHttpRequest().buildClientArgs().buildParameterArgs().buildHeaderArgs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动构建HTTP请求对象
|
||||
*
|
||||
* @return OAuth2Request
|
||||
*/
|
||||
public OAuth2Request buildHttpRequest() {
|
||||
if (this.httpRequest == null) {
|
||||
this.httpRequest = WebUtil.getRequest();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动构建客户端参数
|
||||
*
|
||||
* @return OAuth2Request
|
||||
*/
|
||||
public OAuth2Request buildClientArgs() {
|
||||
String[] tokens = getClientFromAuthorization();
|
||||
assert tokens.length == 2;
|
||||
clientArgs.set(CLIENT_ID, tokens[0]);
|
||||
clientArgs.set(CLIENT_SECRET, tokens[1]);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动构建请求头参数
|
||||
*
|
||||
* @return OAuth2Request
|
||||
*/
|
||||
public OAuth2Request buildParameterArgs() {
|
||||
HttpServletRequest request = Objects.requireNonNull(buildHttpRequest().httpRequest);
|
||||
Arrays.stream(new String[]{
|
||||
CLIENT_ID, CLIENT_SECRET, ACCESS_TOKEN, REFRESH_TOKEN, TENANT_ID, USERNAME, PASSWORD, NAME, PHONE, EMAIL, GRANT_TYPE, SCOPE, REDIRECT_URI, RESPONSE_TYPE, CODE, STATE, SOURCE
|
||||
}).forEach(param -> Optional.ofNullable(request.getParameter(param)).ifPresent(value -> parameterArgs.set(param, value)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动构建头部参数
|
||||
*
|
||||
* @return OAuth2Request
|
||||
*/
|
||||
public OAuth2Request buildHeaderArgs() {
|
||||
HttpServletRequest request = Objects.requireNonNull(buildHttpRequest().httpRequest);
|
||||
Arrays.stream(new String[]{
|
||||
HEADER_AUTHORIZATION, TOKEN_HEADER, TENANT_HEADER, USER_HEADER, ROLE_HEADER, DEPT_HEADER, USER_TYPE_HEADER, CAPTCHA_HEADER_KEY, CAPTCHA_HEADER_CODE
|
||||
}).forEach(param -> Optional.ofNullable(request.getHeader(param)).ifPresent(value -> headerArgs.set(param, value)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求头中的客户端id
|
||||
*/
|
||||
public String getClientId() {
|
||||
return clientArgs.getStr(CLIENT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求头中的客户端密钥
|
||||
*/
|
||||
public String getClientSecret() {
|
||||
return clientArgs.getStr(CLIENT_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端ID和密钥
|
||||
*
|
||||
* @return String[]
|
||||
*/
|
||||
public String[] getClientFromAuthorization() {
|
||||
return OAuth2Util.extractAndDecodeAuthorization();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端ID
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getClientIdFromParameter() {
|
||||
return parameterArgs.getStr(CLIENT_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端密钥
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getClientSecretFromParameter() {
|
||||
return parameterArgs.getStr(CLIENT_SECRET);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取令牌
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getToken() {
|
||||
return headerArgs.getStr(TOKEN_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户编号
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getTenantId() {
|
||||
if (StringUtil.isBlank(headerArgs.getStr(TENANT_HEADER))) {
|
||||
return parameterArgs.getStr(TENANT_ID);
|
||||
}
|
||||
return headerArgs.getStr(TENANT_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getUserId() {
|
||||
return headerArgs.getStr(USER_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getUsername() {
|
||||
return parameterArgs.getStr(USERNAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取密码
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getPassword() {
|
||||
return parameterArgs.getStr(PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名字
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getName() {
|
||||
return parameterArgs.getStr(NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取手机号
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getPhone() {
|
||||
return parameterArgs.getStr(PHONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取电子邮箱
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getEmail() {
|
||||
return parameterArgs.getStr(EMAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户类型
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getUserType() {
|
||||
return headerArgs.getStr(USER_TYPE_HEADER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户部门
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getUserDept() {
|
||||
return headerArgs.getStr(DEPT_HEADER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户角色
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getUserRole() {
|
||||
return headerArgs.getStr(ROLE_HEADER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取验证码key
|
||||
*/
|
||||
public String getCaptchaKey() {
|
||||
return headerArgs.getStr(CAPTCHA_HEADER_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码code
|
||||
*/
|
||||
public String getCaptchaCode() {
|
||||
return headerArgs.getStr(CAPTCHA_HEADER_CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取授权类型
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getGrantType() {
|
||||
return parameterArgs.getStr(GRANT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取刷新令牌
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getRefreshToken() {
|
||||
return parameterArgs.getStr(REFRESH_TOKEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证code
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getCode() {
|
||||
return parameterArgs.getStr(CODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getState() {
|
||||
return parameterArgs.getStr(STATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getSource() {
|
||||
return parameterArgs.getStr(SOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取回调地址
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getRedirectUri() {
|
||||
return parameterArgs.getStr(REDIRECT_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否密码模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean isPassword() {
|
||||
return PASSWORD.equals(getGrantType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否刷新模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean isRefreshToken() {
|
||||
return REFRESH_TOKEN.equals(getGrantType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否验证码模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean isCaptchaCode() {
|
||||
return CAPTCHA.equals(getGrantType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否密码模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean isClientCredentials() {
|
||||
return CLIENT_CREDENTIALS.equals(getGrantType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否简化模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean isImplicit() {
|
||||
return IMPLICIT.equals(getGrantType());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否开放平台模式
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean isSocial() {
|
||||
return SOCIAL.equals(getGrantType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户ID
|
||||
*
|
||||
* @param tenantId 户ID
|
||||
*/
|
||||
public void setTenantId(String tenantId) {
|
||||
this.headerArgs.set(TENANT_HEADER, tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户ID
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void setUserId(String userId) {
|
||||
this.headerArgs.set(USER_HEADER, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户名
|
||||
*
|
||||
* @param username 用户名
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.parameterArgs.set(USERNAME, username);
|
||||
}
|
||||
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.provider;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import static org.springblade.core.oauth2.constant.OAuth2ResponseConstant.*;
|
||||
|
||||
/**
|
||||
* OAuth2Response
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
public class OAuth2Response implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实例化
|
||||
*/
|
||||
public static OAuth2Response create() {
|
||||
return new OAuth2Response();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应参数
|
||||
*/
|
||||
private Kv args = Kv.create();
|
||||
|
||||
public Kv ofFailure(int errorCode, String errorDescription) {
|
||||
args.set(SUCCESS, Boolean.FALSE);
|
||||
args.set(ERROR_CODE, errorCode);
|
||||
args.set(ERROR_DESCRIPTION, errorDescription);
|
||||
return args;
|
||||
}
|
||||
|
||||
public Kv ofSuccessful(String successDescription) {
|
||||
args.set(SUCCESS, Boolean.TRUE);
|
||||
args.set(SUCCESS_CODE, DEFAULT_SUCCESS_CODE);
|
||||
args.set(SUCCESS_DESCRIPTION, successDescription);
|
||||
return args;
|
||||
}
|
||||
|
||||
public Kv ofSuccessful(int successCode, String successDescription) {
|
||||
args.set(SUCCESS, Boolean.TRUE);
|
||||
args.set(SUCCESS_CODE, successCode);
|
||||
args.set(SUCCESS_DESCRIPTION, successDescription);
|
||||
return args;
|
||||
}
|
||||
|
||||
public Kv ofSuccessful(int successCode, String successDescription, Kv successData) {
|
||||
args.set(SUCCESS, Boolean.TRUE);
|
||||
args.set(SUCCESS_CODE, successCode);
|
||||
args.set(SUCCESS_DESCRIPTION, successDescription);
|
||||
args.setAll(successData);
|
||||
return args;
|
||||
}
|
||||
|
||||
public Kv ofValidation(OAuth2Validation validation) {
|
||||
args.set(SUCCESS, validation.isSuccess());
|
||||
args.set(ERROR_CODE, validation.getCode());
|
||||
args.set(ERROR_DESCRIPTION, validation.getMessage());
|
||||
return args;
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.provider;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* OAuth2Token
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OAuth2Token implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 实例化
|
||||
*/
|
||||
public static OAuth2Token create() {
|
||||
return new OAuth2Token();
|
||||
}
|
||||
|
||||
/**
|
||||
* 令牌值
|
||||
*/
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* 刷新令牌值
|
||||
*/
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 令牌过期秒数
|
||||
*/
|
||||
private int accessTokenExpire;
|
||||
|
||||
/**
|
||||
* 刷新令牌过期秒数
|
||||
*/
|
||||
private int refreshTokenExpire;
|
||||
|
||||
/**
|
||||
* 令牌参数
|
||||
*/
|
||||
private Kv args = Kv.create();
|
||||
|
||||
|
||||
/**
|
||||
* 是否包含令牌
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean hasAccessToken() {
|
||||
return StringUtil.isNotBlank(accessToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否包含刷新令牌
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
public Boolean hasRefreshToken() {
|
||||
return StringUtil.isNotBlank(refreshToken);
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.provider;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* OAuth2Validation
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class OAuth2Validation {
|
||||
|
||||
/**
|
||||
* 实例化
|
||||
*/
|
||||
public static OAuth2Validation create() {
|
||||
return new OAuth2Validation();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
boolean success = true;
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
int code = 1000;
|
||||
|
||||
/**
|
||||
* 验证信息
|
||||
*/
|
||||
String message = "认证通过";
|
||||
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 多终端详情接口
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2Client extends Serializable {
|
||||
|
||||
/**
|
||||
* 获取客户端ID.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getClientId();
|
||||
|
||||
/**
|
||||
* 获取客户端密钥.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getClientSecret();
|
||||
|
||||
/**
|
||||
* 获取资源集合.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getResourceIds();
|
||||
|
||||
/**
|
||||
* 获取授权范围.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getScope();
|
||||
|
||||
/**
|
||||
* 获取授权类型.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getAuthorizedGrantTypes();
|
||||
|
||||
/**
|
||||
* 获取回调地址.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getWebServerRedirectUri();
|
||||
|
||||
/**
|
||||
* 获取权限.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getAuthorities();
|
||||
|
||||
/**
|
||||
* 获取访问令牌有效期.
|
||||
*
|
||||
* @return Integer
|
||||
*/
|
||||
Integer getAccessTokenValidity();
|
||||
|
||||
/**
|
||||
* 获取刷新令牌有效期.
|
||||
*
|
||||
* @return Integer
|
||||
*/
|
||||
Integer getRefreshTokenValidity();
|
||||
|
||||
/**
|
||||
* 获取附加信息.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getAdditionalInformation();
|
||||
|
||||
/**
|
||||
* 获取自动授权.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getAutoapprove();
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service;
|
||||
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
|
||||
/**
|
||||
* 多终端注册接口
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2ClientService {
|
||||
|
||||
/**
|
||||
* 根据clientId获取Client详情
|
||||
*
|
||||
* @param clientId 客户端id
|
||||
* @return 客户端信息
|
||||
*/
|
||||
OAuth2Client loadByClientId(String clientId);
|
||||
|
||||
/**
|
||||
* 根据clientId获取Client详情
|
||||
*
|
||||
* @param clientId 客户端id
|
||||
* @param request 授权参数
|
||||
* @return 客户端信息
|
||||
*/
|
||||
OAuth2Client loadByClientId(String clientId, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 验证Client信息
|
||||
*
|
||||
* @param client client信息
|
||||
* @param clientId 客户端id
|
||||
* @param clientSecret 客户端密钥
|
||||
* @return boolean
|
||||
*/
|
||||
boolean validateClient(OAuth2Client client, String clientId, String clientSecret);
|
||||
|
||||
/**
|
||||
* 验证Client信息
|
||||
*
|
||||
* @param client client信息
|
||||
* @param redirectUri 回调地址
|
||||
* @return boolean
|
||||
*/
|
||||
boolean validateRedirectUri(OAuth2Client client, String redirectUri);
|
||||
|
||||
/**
|
||||
* 验证授权类型
|
||||
*
|
||||
* @param client client信息
|
||||
* @param grantType 授权类型
|
||||
* @return boolean
|
||||
*/
|
||||
boolean validateGranter(OAuth2Client client, String grantType);
|
||||
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service;
|
||||
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户基础信息
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2User extends Serializable {
|
||||
|
||||
/**
|
||||
* 获取用户ID.
|
||||
*
|
||||
* @return Long
|
||||
*/
|
||||
String getUserId();
|
||||
|
||||
/**
|
||||
* 获取租户ID.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getTenantId();
|
||||
|
||||
/**
|
||||
* 获取第三方认证ID.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getOauthId();
|
||||
|
||||
/**
|
||||
* 获取昵称.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* 获取真名.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getRealName();
|
||||
|
||||
/**
|
||||
* 获取账号.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getAccount();
|
||||
|
||||
/**
|
||||
* 获取密码.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getPassword();
|
||||
|
||||
/**
|
||||
* 获取手机.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getPhone();
|
||||
|
||||
/**
|
||||
* 获取邮箱.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getEmail();
|
||||
|
||||
/**
|
||||
* 获取部门ID.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getDeptId();
|
||||
|
||||
/**
|
||||
* 获取岗位ID.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getPostId();
|
||||
|
||||
/**
|
||||
* 获取角色ID.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getRoleId();
|
||||
|
||||
/**
|
||||
* 获取角色名.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getRoleName();
|
||||
|
||||
/**
|
||||
* 获取头像.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String getAvatar();
|
||||
|
||||
/**
|
||||
* 获取权限集合.
|
||||
*
|
||||
* @return List<String>
|
||||
*/
|
||||
List<String> getPermissions();
|
||||
|
||||
/**
|
||||
* 获取角色集合.
|
||||
*
|
||||
* @return List<String>
|
||||
*/
|
||||
List<String> getAuthorities();
|
||||
|
||||
/**
|
||||
* 获取客户端信息.
|
||||
*
|
||||
* @return OAuth2Client
|
||||
*/
|
||||
OAuth2Client getClient();
|
||||
|
||||
/**
|
||||
* 设置客户端信息.
|
||||
*/
|
||||
void setClient(OAuth2Client client);
|
||||
|
||||
/**
|
||||
* 获取用户详情.
|
||||
*
|
||||
* @return Kv
|
||||
*/
|
||||
Kv getDetail();
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service;
|
||||
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
|
||||
/**
|
||||
* OAuth2UserService
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public interface OAuth2UserService {
|
||||
|
||||
/**
|
||||
* 根据用户名获取用户信息
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param request 授权参数
|
||||
* @return 用户信息
|
||||
*/
|
||||
OAuth2User loadByUserId(String userId, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 根据用户名获取用户信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param request 授权参数
|
||||
* @return 用户信息
|
||||
*/
|
||||
OAuth2User loadByUsername(String username, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 根据手机号获取用户信息
|
||||
*
|
||||
* @param phone 手机号
|
||||
* @param request 授权参数
|
||||
* @return 用户信息
|
||||
*/
|
||||
OAuth2User loadByPhone(String phone, OAuth2Request request);
|
||||
|
||||
/**
|
||||
* 校验用户信息
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return 是否通过
|
||||
*/
|
||||
boolean validateUser(OAuth2User user);
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service.impl;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 客户端详情
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "oauth2客户端实体类")
|
||||
public class OAuth2ClientDetail implements OAuth2Client {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客户端id
|
||||
*/
|
||||
@Schema(description = "客户端id")
|
||||
private String clientId;
|
||||
/**
|
||||
* 客户端密钥
|
||||
*/
|
||||
@Schema(description = "客户端密钥")
|
||||
private String clientSecret;
|
||||
/**
|
||||
* 资源集合
|
||||
*/
|
||||
@Schema(description = "资源集合")
|
||||
private String resourceIds;
|
||||
/**
|
||||
* 授权范围
|
||||
*/
|
||||
@Schema(description = "授权范围")
|
||||
private String scope;
|
||||
/**
|
||||
* 授权类型
|
||||
*/
|
||||
@Schema(description = "授权类型")
|
||||
private String authorizedGrantTypes;
|
||||
/**
|
||||
* 回调地址
|
||||
*/
|
||||
@Schema(description = "回调地址")
|
||||
private String webServerRedirectUri;
|
||||
/**
|
||||
* 权限
|
||||
*/
|
||||
@Schema(description = "权限")
|
||||
private String authorities;
|
||||
/**
|
||||
* 令牌过期秒数
|
||||
*/
|
||||
@Schema(description = "令牌过期秒数")
|
||||
private Integer accessTokenValidity;
|
||||
/**
|
||||
* 刷新令牌过期秒数
|
||||
*/
|
||||
@Schema(description = "刷新令牌过期秒数")
|
||||
private Integer refreshTokenValidity;
|
||||
/**
|
||||
* 附加说明
|
||||
*/
|
||||
@Schema(description = "附加说明")
|
||||
private String additionalInformation;
|
||||
/**
|
||||
* 自动授权
|
||||
*/
|
||||
@Schema(description = "自动授权")
|
||||
private String autoapprove;
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.oauth2.constant.OAuth2ClientConstant;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2ClientService;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.core.tool.utils.StringPool;
|
||||
import org.springblade.core.tool.utils.StringUtil;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 获取客户端详情
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class OAuth2ClientDetailService implements OAuth2ClientService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public OAuth2Client loadByClientId(String clientId) {
|
||||
return loadByClientId(clientId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2Client loadByClientId(String clientId, OAuth2Request request) {
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(OAuth2ClientConstant.DEFAULT_SELECT_STATEMENT, new BeanPropertyRowMapper<>(OAuth2ClientDetail.class), clientId);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateClient(OAuth2Client client, String clientId, String clientSecret) {
|
||||
return Optional.ofNullable(client)
|
||||
.map(c -> StringUtil.equals(clientId, c.getClientId()) && StringUtil.equals(clientSecret, c.getClientSecret()))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateRedirectUri(OAuth2Client client, String redirectUri) {
|
||||
return Optional.ofNullable(client)
|
||||
.map(c -> StringUtil.equals(redirectUri, c.getWebServerRedirectUri()))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateGranter(OAuth2Client client, String grantType) {
|
||||
return Optional.ofNullable(client)
|
||||
.map(c -> Arrays.stream(Func.split(c.getAuthorizedGrantTypes(), StringPool.COMMA))
|
||||
.anyMatch(s -> s.trim().equals(grantType)))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "oauth2用户实体类")
|
||||
public class OAuth2UserDetail implements OAuth2User {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@Schema(description = "用户id")
|
||||
private String userId;
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
@Schema(description = "租户ID")
|
||||
private String tenantId;
|
||||
/**
|
||||
* 第三方认证ID
|
||||
*/
|
||||
@Schema(description = "第三方认证ID")
|
||||
private String oauthId;
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@Schema(description = "昵称")
|
||||
private String name;
|
||||
/**
|
||||
* 真名
|
||||
*/
|
||||
@Schema(description = "真名")
|
||||
private String realName;
|
||||
/**
|
||||
* 账号
|
||||
*/
|
||||
@Schema(description = "账号")
|
||||
private String account;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Schema(description = "密码")
|
||||
private String password;
|
||||
/**
|
||||
* 手机
|
||||
*/
|
||||
@Schema(description = "手机")
|
||||
private String phone;
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@Schema(description = "邮箱")
|
||||
private String email;
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@Schema(description = "部门id")
|
||||
private String deptId;
|
||||
/**
|
||||
* 岗位id
|
||||
*/
|
||||
@Schema(description = "岗位id")
|
||||
private String postId;
|
||||
/**
|
||||
* 角色id
|
||||
*/
|
||||
@Schema(description = "角色id")
|
||||
private String roleId;
|
||||
/**
|
||||
* 角色名
|
||||
*/
|
||||
@Schema(description = "角色名")
|
||||
private String roleName;
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 权限标识集合
|
||||
*/
|
||||
@Schema(description = "权限集合")
|
||||
private List<String> permissions;
|
||||
|
||||
/**
|
||||
* 角色集合
|
||||
*/
|
||||
@Schema(description = "角色集合")
|
||||
private List<String> authorities;
|
||||
|
||||
/**
|
||||
* 客户端
|
||||
*/
|
||||
@Schema(description = "客户端")
|
||||
private OAuth2Client client;
|
||||
|
||||
/**
|
||||
* 用户详情
|
||||
*/
|
||||
@Schema(description = "用户详情")
|
||||
private Kv detail;
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.service.impl;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springblade.core.oauth2.constant.OAuth2UserConstant;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.oauth2.service.OAuth2UserService;
|
||||
import org.springframework.jdbc.core.BeanPropertyRowMapper;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class OAuth2UserDetailService implements OAuth2UserService {
|
||||
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(OAuth2UserConstant.DEFAULT_USERID_SELECT_STATEMENT, new BeanPropertyRowMapper<>(OAuth2UserDetail.class), userId);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByUsername(String username, OAuth2Request request) {
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(OAuth2UserConstant.DEFAULT_USERNAME_SELECT_STATEMENT, new BeanPropertyRowMapper<>(OAuth2UserDetail.class), username);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadByPhone(String phone, OAuth2Request request) {
|
||||
try {
|
||||
return jdbcTemplate.queryForObject(OAuth2UserConstant.DEFAULT_PHONE_SELECT_STATEMENT, new BeanPropertyRowMapper<>(OAuth2UserDetail.class), phone);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateUser(OAuth2User user) {
|
||||
return Optional.ofNullable(user)
|
||||
.filter(u -> u.getUserId() != null && !u.getUserId().isEmpty()) // 检查userId不为空
|
||||
.filter(u -> u.getAuthorities() != null && !u.getAuthorities().isEmpty()) // 检查authorities不为空
|
||||
.isPresent(); // 如果上述条件都满足,则返回true,否则返回false
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.utils;
|
||||
|
||||
/**
|
||||
* OAuth2CodeUtil
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public class OAuth2CodeUtil {
|
||||
|
||||
/**
|
||||
* 授权码缓存key
|
||||
*/
|
||||
public static final String AUTHORIZATION_CODE_KEY = "blade:auth::code:";
|
||||
|
||||
/**
|
||||
* code key格式
|
||||
*
|
||||
* @param code code
|
||||
* @return key
|
||||
*/
|
||||
public static String codeKey(String code) {
|
||||
return AUTHORIZATION_CODE_KEY + code;
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.utils;
|
||||
|
||||
import org.springblade.core.oauth2.exception.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* OAuth2ExceptionUtil
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class OAuth2ExceptionUtil {
|
||||
private static final Map<ExceptionCode, Supplier<OAuth2Exception>> OAUTH2_EXCEPTION = new ConcurrentHashMap<>(16);
|
||||
|
||||
static {
|
||||
// 初始化异常映射
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_REQUEST, () -> new OAuth2Exception(ExceptionCode.INVALID_REQUEST, ExceptionCode.INVALID_REQUEST.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.USER_NOT_FOUND, () -> new UsernameNotFoundException(ExceptionCode.USER_NOT_FOUND.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.USER_TENANT_NOT_FOUND, () -> new UserInvalidException(ExceptionCode.USER_TENANT_NOT_FOUND.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.USER_TOO_MANY_FAILS, () -> new UserInvalidException(ExceptionCode.USER_TOO_MANY_FAILS.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_USER, () -> new UserInvalidException(ExceptionCode.INVALID_USER.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.UNAUTHORIZED_USER, () -> new UserUnauthorizedException(ExceptionCode.UNAUTHORIZED_USER.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.UNAUTHORIZED_USER_TENANT, () -> new UserUnauthorizedException(ExceptionCode.UNAUTHORIZED_USER_TENANT.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_REFRESH_TOKEN, () -> new GranterInvalidException(ExceptionCode.INVALID_REFRESH_TOKEN.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.CLIENT_NOT_FOUND, () -> new ClientNotFoundException(ExceptionCode.CLIENT_NOT_FOUND.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_CLIENT, () -> new ClientInvalidException(ExceptionCode.INVALID_CLIENT.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_CLIENT_REDIRECT_URI, () -> new ClientInvalidException(ExceptionCode.INVALID_CLIENT_REDIRECT_URI.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.UNAUTHORIZED_CLIENT, () -> new ClientUnauthorizedException(ExceptionCode.UNAUTHORIZED_CLIENT.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.UNSUPPORTED_GRANT_TYPE, () -> new OAuth2Exception(ExceptionCode.UNSUPPORTED_GRANT_TYPE, ExceptionCode.UNSUPPORTED_GRANT_TYPE.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_GRANTER, () -> new GranterInvalidException(ExceptionCode.INVALID_GRANTER.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.INVALID_SCOPE, () -> new OAuth2Exception(ExceptionCode.INVALID_SCOPE, ExceptionCode.INVALID_SCOPE.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.SERVER_ERROR, () -> new OAuth2Exception(ExceptionCode.SERVER_ERROR, ExceptionCode.SERVER_ERROR.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.ACCESS_DENIED, () -> new OAuth2Exception(ExceptionCode.ACCESS_DENIED, ExceptionCode.ACCESS_DENIED.getMessage(), new Throwable())
|
||||
);
|
||||
OAUTH2_EXCEPTION.put(
|
||||
ExceptionCode.TEMPORARILY_UNAVAILABLE, () -> new OAuth2Exception(ExceptionCode.TEMPORARILY_UNAVAILABLE, ExceptionCode.TEMPORARILY_UNAVAILABLE.getMessage(), new Throwable())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据错误代码抛出异常
|
||||
*
|
||||
* @param code 错误代码
|
||||
*/
|
||||
public static void throwFromCode(int code) {
|
||||
Supplier<OAuth2Exception> exceptionSupplier = OAUTH2_EXCEPTION.get(ExceptionCode.of(code));
|
||||
if (exceptionSupplier != null) {
|
||||
throw exceptionSupplier.get();
|
||||
} else {
|
||||
throw new UserInvalidException(ExceptionCode.INVALID_USER.getMessage(), new Throwable());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.oauth2.exception.OAuth2Exception;
|
||||
import org.springblade.core.oauth2.provider.OAuth2Request;
|
||||
|
||||
/**
|
||||
* OAuth2LogUtil
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
public class OAuth2LogUtil {
|
||||
|
||||
public static void logOAuth2Exception(OAuth2Exception ex, OAuth2Request request, boolean isProd) {
|
||||
if (!isProd) {
|
||||
log.warn("----------OAuth2认证异常,具体信息如下----------");
|
||||
log.warn("OAuth2Request: {}", request);
|
||||
log.warn("OAuth2Exception: ", ex);
|
||||
log.warn("--------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.core.oauth2.utils;
|
||||
|
||||
import org.springblade.core.oauth2.service.OAuth2Client;
|
||||
import org.springblade.core.oauth2.service.OAuth2User;
|
||||
import org.springblade.core.secure.TokenInfo;
|
||||
import org.springblade.core.secure.utils.SecureUtil;
|
||||
import org.springblade.core.tool.support.Kv;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.springblade.core.launch.constant.TokenConstant.*;
|
||||
|
||||
/**
|
||||
* OAuth2Util
|
||||
*
|
||||
* @author BladeX
|
||||
*/
|
||||
public class OAuth2Util extends SecureUtil {
|
||||
|
||||
/**
|
||||
* 创建accessToken
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return accessToken
|
||||
*/
|
||||
public static TokenInfo createAccessToken(OAuth2User user) {
|
||||
Kv kv = Kv.create().set(TOKEN_TYPE, ACCESS_TOKEN)
|
||||
.set(CLIENT_ID, user.getClient().getClientId())
|
||||
.set(TENANT_ID, user.getTenantId())
|
||||
.set(USER_ID, user.getUserId())
|
||||
.set(DEPT_ID, user.getDeptId())
|
||||
.set(POST_ID, user.getPostId())
|
||||
.set(ROLE_ID, user.getRoleId())
|
||||
.set(OAUTH_ID, user.getOauthId())
|
||||
.set(ACCOUNT, user.getAccount())
|
||||
.set(USER_NAME, user.getAccount())
|
||||
.set(NICK_NAME, user.getName())
|
||||
.set(REAL_NAME, user.getRealName())
|
||||
.set(ROLE_NAME, user.getRoleName())
|
||||
.set(DETAIL, user.getDetail());
|
||||
return createToken(kv, user.getClient().getAccessTokenValidity());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建refreshToken
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return refreshToken
|
||||
*/
|
||||
public static TokenInfo createRefreshToken(OAuth2User user) {
|
||||
return createRefreshToken(user, Instant.now().getEpochSecond());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建refreshToken
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @param authTime 首次认证时间(epoch 秒),刷新续期时沿用旧令牌的值,作为绝对有效期基线
|
||||
* @return refreshToken
|
||||
*/
|
||||
public static TokenInfo createRefreshToken(OAuth2User user, long authTime) {
|
||||
Kv kv = Kv.create().set(TOKEN_TYPE, REFRESH_TOKEN)
|
||||
.set(USER_ID, user.getUserId())
|
||||
.set(DEPT_ID, user.getDeptId())
|
||||
.set(ROLE_ID, user.getRoleId())
|
||||
.set(AUTH_TIME, authTime);
|
||||
return createToken(kv, user.getClient().getRefreshTokenValidity());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建clientAccessToken
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return clientToken
|
||||
*/
|
||||
public static TokenInfo createClientAccessToken(OAuth2User user) {
|
||||
OAuth2Client client = user.getClient();
|
||||
Kv kv = Kv.create().set(TOKEN_TYPE, CLIENT_ACCESS_TOKEN)
|
||||
.set(TENANT_ID, user.getTenantId())
|
||||
.set(CLIENT_ID, client.getClientId());
|
||||
return createToken(kv, client.getAccessTokenValidity());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建implicitAccessToken
|
||||
*
|
||||
* @param user 用户信息
|
||||
* @return implicitAccessToken
|
||||
*/
|
||||
public static TokenInfo createImplicitAccessToken(OAuth2User user) {
|
||||
Kv kv = Kv.create().set(TOKEN_TYPE, IMPLICIT_ACCESS_TOKEN)
|
||||
.set(TENANT_ID, user.getTenantId())
|
||||
.set(ACCOUNT, user.getAccount());
|
||||
return createToken(kv);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+6
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,571 @@
|
||||
/*------------------------------------------------------------------
|
||||
* Theme Name: iofrm - form templates
|
||||
* Theme URI: http://www.brandio.io/envato/iofrm
|
||||
* Author: Brandio
|
||||
* Author URI: http://www.brandio.io/
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
-------------------------------------------------------------------*/
|
||||
|
||||
body {
|
||||
background-color: #152733;
|
||||
}
|
||||
|
||||
.form-body {
|
||||
background-color: #152733;
|
||||
}
|
||||
|
||||
.website-logo {
|
||||
display: none;
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
right: initial;
|
||||
bottom: initial;
|
||||
}
|
||||
|
||||
.website-logo img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.website-logo .logo {
|
||||
background-image: url("../images/logo-light.svg");
|
||||
}
|
||||
|
||||
.website-logo .logo img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.website-logo-inside img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.website-logo-inside .logo {
|
||||
background-image: url("../images/logo-light.svg");
|
||||
}
|
||||
|
||||
.website-logo-inside .logo img {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.img-holder {
|
||||
width: 0;
|
||||
background-color: #5CBAFF;
|
||||
}
|
||||
|
||||
.img-holder .info-holder h3 {
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.img-holder .info-holder h3 span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.img-holder .info-holder h2 {
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.img-holder .info-holder h2 span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.img-holder .info-holder p {
|
||||
color: #fff;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.img-holder .bg {
|
||||
opacity: 0.23;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.form-holder {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.form-holder .form-content ::-webkit-input-placeholder {
|
||||
color: #8D8D8D !important;
|
||||
}
|
||||
|
||||
.form-holder .form-content :-moz-placeholder {
|
||||
color: #8D8D8D !important;
|
||||
}
|
||||
|
||||
.form-holder .form-content ::-moz-placeholder {
|
||||
color: #8D8D8D !important;
|
||||
}
|
||||
|
||||
.form-holder .form-content :-ms-input-placeholder {
|
||||
color: #8D8D8D !important;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
background-color: #152733;
|
||||
}
|
||||
|
||||
.form-content .form-group {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .form-items {
|
||||
max-width: 380px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content .form-icon {
|
||||
margin-top: calc(-42px - 35px);
|
||||
}
|
||||
|
||||
.form-content .form-icon .icon-holder {
|
||||
background-color: #4A77F7;
|
||||
}
|
||||
|
||||
.form-content h1 {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content h2 {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content h3 {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content p {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content label {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-content .page-links a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .page-links a:after {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.form-content .page-links a.active:after {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-content .page-links a:hover:after, .form-content .page-links a:focus:after {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-content input, .form-content .dropdown-toggle.btn-default {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-content input:hover, .form-content input:focus, .form-content .dropdown-toggle.btn-default:hover, .form-content .dropdown-toggle.btn-default:focus {
|
||||
border: 0;
|
||||
background-color: #ebeff8;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-content textarea {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-content textarea:hover, .form-content textarea:focus {
|
||||
border: 0;
|
||||
background-color: #ebeff8;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-content .custom-file-label {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-content .custom-file-label:after {
|
||||
color: #0093FF;
|
||||
}
|
||||
|
||||
.form-content .custom-file:hover .custom-file-label, .form-content .custom-file:focus .custom-file-label {
|
||||
border: 0;
|
||||
background-color: #ebeff8;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-content input[type=checkbox]:not(:checked) + label, .form-content input[type=checkbox]:checked + label, .form-content input[type=radio]:not(:checked) + label, .form-content input[type=radio]:checked + label {
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form-content input[type=checkbox]:checked + label, .form-content input[type=radio]:checked + label {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content input[type=checkbox]:checked + label:before, .form-content input[type=radio]:checked + label:before {
|
||||
background: #fff;
|
||||
border: 0px solid #fff;
|
||||
}
|
||||
|
||||
.form-content input[type=checkbox]:not(:checked) + label:before, .form-content input[type=radio]:not(:checked) + label:before {
|
||||
background: transparent;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
|
||||
.form-content input[type=checkbox]:not(:checked) + label:after, .form-content input[type=checkbox]:checked + label:after {
|
||||
color: #152733;
|
||||
}
|
||||
|
||||
.form-content input[type=radio]:not(:checked) + label:after, .form-content input[type=radio]:checked + label:after {
|
||||
background-color: #152733;
|
||||
}
|
||||
|
||||
.form-content .custom-options input[type=checkbox]:not(:checked) + label, .form-content .custom-options input[type=checkbox]:checked + label, .form-content .custom-options input[type=radio]:not(:checked) + label, .form-content .custom-options input[type=radio]:checked + label {
|
||||
color: #606060;
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.form-content .custom-options input[type=checkbox]:checked + label, .form-content .custom-options input[type=radio]:checked + label {
|
||||
color: #fff;
|
||||
background-color: #1592E6;
|
||||
-webkit-box-shadow: 0 3px 8px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-content .form-button .lbtn {
|
||||
margin-top: 10px;
|
||||
background-color: #cb3444;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-content .form-button .lbtn:hover, .form-content .form-button .lbtn:focus {
|
||||
background-color: #cb3444;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-content .form-button .ibtn {
|
||||
background-color: #1592E6;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-content .form-button .ibtn:hover, .form-content .form-button .ibtn:focus {
|
||||
background-color: #1592E6;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-content .form-button a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .other-links span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .other-links a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .form-sent .tick-holder .tick-icon {
|
||||
background-color: rgba(14, 30, 41, 0);
|
||||
}
|
||||
|
||||
.form-content .form-sent .tick-holder .tick-icon:before {
|
||||
background-color: #8CCB57;
|
||||
}
|
||||
|
||||
.form-content .form-sent .tick-holder .tick-icon:after {
|
||||
background-color: #8CCB57;
|
||||
}
|
||||
|
||||
.form-content .form-sent h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .form-sent p {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .form-sent .info-holder {
|
||||
color: #fff;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.form-content .form-sent .info-holder span {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-content .form-sent .info-holder a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@keyframes tick-anime3 {
|
||||
0% {
|
||||
background-color: rgba(14, 30, 41, 0);
|
||||
-webkit-transform: rotate(35deg) scale(2);
|
||||
-moz-transform: rotate(35deg) scale(2);
|
||||
-ms-transform: rotate(35deg) scale(2);
|
||||
transform: rotate(35deg) scale(2);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: #0E1E29;
|
||||
-webkit-transform: rotate(45deg) scale(1);
|
||||
-moz-transform: rotate(45deg) scale(1);
|
||||
-ms-transform: rotate(45deg) scale(1);
|
||||
transform: rotate(45deg) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alert.alert-primary {
|
||||
background-color: rgba(226, 240, 255, 0);
|
||||
border-color: #3a86d6;
|
||||
}
|
||||
|
||||
.alert.alert-primary hr {
|
||||
border-top-color: #3a86d6;
|
||||
}
|
||||
|
||||
.alert.alert-secondary {
|
||||
background-color: rgba(240, 240, 240, 0);
|
||||
border-color: #8e9396;
|
||||
}
|
||||
|
||||
.alert.alert-secondary hr {
|
||||
border-top-color: #8e9396;
|
||||
}
|
||||
|
||||
.alert.alert-success {
|
||||
background-color: rgba(247, 255, 240, 0);
|
||||
border-color: #8CCB57;
|
||||
}
|
||||
|
||||
.alert.alert-success hr {
|
||||
border-top-color: #8CCB57;
|
||||
}
|
||||
|
||||
.alert.alert-danger {
|
||||
background-color: rgba(255, 250, 250, 0);
|
||||
border-color: #F55050;
|
||||
}
|
||||
|
||||
.alert.alert-danger hr {
|
||||
border-top-color: #F55050;
|
||||
}
|
||||
|
||||
.alert.alert-warning {
|
||||
background-color: rgba(255, 248, 225, 0);
|
||||
border-color: #f1cb4b;
|
||||
}
|
||||
|
||||
.alert.alert-warning hr {
|
||||
border-top-color: #f1cb4b;
|
||||
}
|
||||
|
||||
.alert.alert-info {
|
||||
background-color: rgba(220, 237, 241, 0);
|
||||
border-color: #42bfdb;
|
||||
}
|
||||
|
||||
.alert.alert-info hr {
|
||||
border-top-color: #42bfdb;
|
||||
}
|
||||
|
||||
.alert.alert-light {
|
||||
background-color: rgba(254, 254, 254, 0);
|
||||
border-color: #a7a4a4;
|
||||
}
|
||||
|
||||
.alert.alert-light hr {
|
||||
border-top-color: #a7a4a4;
|
||||
}
|
||||
|
||||
.alert.alert-dark {
|
||||
background-color: rgba(214, 216, 217, 0);
|
||||
border-color: #525557;
|
||||
}
|
||||
|
||||
.alert.alert-dark hr {
|
||||
border-top-color: #525557;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-primary:before {
|
||||
color: #3a86d6;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-secondary:before {
|
||||
color: #8e9396;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-success:before {
|
||||
color: #8CCB57;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-danger:before {
|
||||
color: #F55050;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-warning:before {
|
||||
color: #f1cb4b;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-info:before {
|
||||
color: #42bfdb;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-light:before {
|
||||
color: #a7a4a4;
|
||||
}
|
||||
|
||||
.alert.with-icon.alert-dark:before {
|
||||
color: #525557;
|
||||
}
|
||||
|
||||
.alert a, .alert a.alert-link {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.alert .close {
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.alert .close span {
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.form-subtitle {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.rad-with-details .more-info {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.form-body.without-side h3 {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.form-body.without-side p {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.form-body.without-side label {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.form-body.without-side .img-holder .info-holder img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-items {
|
||||
padding: 35px 30px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-items .other-links .text {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-items .other-links a {
|
||||
color: #000;
|
||||
background-color: #F7F7F7;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .page-links a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .page-links a:after {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .page-links a.active:after {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .page-links a:hover:after, .form-body.without-side .form-content .page-links a:focus:after {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content input, .form-body.without-side .form-content .dropdown-toggle.btn-default {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content input:hover, .form-body.without-side .form-content input:focus, .form-body.without-side .form-content .dropdown-toggle.btn-default:hover, .form-body.without-side .form-content .dropdown-toggle.btn-default:focus {
|
||||
border: 0;
|
||||
background-color: #fff;
|
||||
color: #8D8D8D;
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-button .lbtn {
|
||||
background-color: #cb3444;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-button .lbtn:hover, .form-body.without-side .form-content .form-button .lbtn:focus {
|
||||
-webkit-box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-button .ibtn {
|
||||
background-color: #1592E6;
|
||||
color: #fff;
|
||||
-webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-button .ibtn:hover, .form-body.without-side .form-content .form-button .ibtn:focus {
|
||||
-webkit-box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: 0 5px 6px rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
|
||||
.form-body.without-side .form-content .form-button a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* -----------------------------------
|
||||
2 - Responsive Styles
|
||||
------------------------------------*/
|
||||
@media (max-width: 992px) {
|
||||
.form-holder {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.website-logo {
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
right: initial;
|
||||
bottom: initial;
|
||||
}
|
||||
|
||||
.website-logo .logo {
|
||||
background-image: url("../images/logo-light.svg");
|
||||
}
|
||||
|
||||
.form-body.without-side .website-logo .logo {
|
||||
background-image: url("../images/logo-light.svg");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BladeX 统一认证系统</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/iofrm-style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/iofrm-theme.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-body">
|
||||
<div class="row">
|
||||
<div class="form-holder">
|
||||
<div class="form-content">
|
||||
<div class="form-items">
|
||||
<div class="website-logo-inside">
|
||||
<img class="logo-size" src="/static/images/bladex-logo.png">
|
||||
<h1>BladeX 统一认证系统</h1>
|
||||
</div>
|
||||
<p th:text="'应用 [' + ${client_id} + '] 请求授权'">应用 [未获取到应用信息] 请求授权</p>
|
||||
<p>授权后该应用将取得系统操作权限</p>
|
||||
<div class="page-links">
|
||||
<a th:text="'授权账号 [' + ${username} + '] 请求授权'">授权账号 [未获取到账号信息]</a>
|
||||
</div>
|
||||
<form action="/oauth/authorize/perform" method="post">
|
||||
<input id='approval' name='approval' th:value="${auto_approve}" type='hidden'/>
|
||||
<input id='state' name='state' th:value="${state}" type='hidden'/>
|
||||
<div class="form-button">
|
||||
<button id="submit" type="submit" class="ibtn">同意/授权</button>
|
||||
<button id="logout" class="lbtn">退出登录</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="other-links">
|
||||
<span>Copyrights © <span id="copyright-notice"></span> <a href="https://bladex.cn" target="_blank">BladeX</a> All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/popper.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
$(window).on("load", function () {
|
||||
// 加载版权信息
|
||||
const currentYear = new Date().getFullYear();
|
||||
$("#copyright-notice").html(currentYear);
|
||||
// 初始化页面增加租户id传递
|
||||
const state = getUrlParam("state");
|
||||
if (state !== null && state !== "") {
|
||||
$("#state").val(state);
|
||||
}
|
||||
// 获取approve的值,如果为true则自动提交表单
|
||||
const approve = $("#approval").val();
|
||||
if (approve === "true") {
|
||||
$("#submit").click();
|
||||
}
|
||||
});
|
||||
|
||||
$("#logout").click(function(event){
|
||||
event.preventDefault();
|
||||
window.location.href = "/oauth/authorize/logout";
|
||||
});
|
||||
|
||||
// 获取url中参数的值
|
||||
function getUrlParam(name) {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get(name);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BladeX 统一认证系统</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/iofrm-style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/iofrm-theme.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-body">
|
||||
<div class="row">
|
||||
<div class="form-holder">
|
||||
<div class="form-content">
|
||||
<div class="form-items">
|
||||
<div class="website-logo-inside">
|
||||
<img class="logo-size" src="/static/images/bladex-logo.png">
|
||||
<h1>BladeX 统一认证系统</h1>
|
||||
</div>
|
||||
<p>应用授权失败</p>
|
||||
<div class="page-links">
|
||||
<a>请返回 [认证页面] 重新认证</a>
|
||||
</div>
|
||||
<div class="form-button">
|
||||
<button id="logout" class="lbtn">前往认证</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="other-links">
|
||||
<span>Copyrights © <span id="copyright-notice"></span> <a href="https://bladex.cn" target="_blank">BladeX</a> All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/popper.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
<script>
|
||||
$(window).on("load", function () {
|
||||
// 加载版权信息
|
||||
const currentYear = new Date().getFullYear();
|
||||
$("#copyright-notice").html(currentYear);
|
||||
});
|
||||
|
||||
$("#logout").click(function(){
|
||||
window.location.href = "/oauth/authorize/logout";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BladeX 统一认证系统</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/iofrm-style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/iofrm-theme.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="form-body">
|
||||
<div class="row">
|
||||
<div class="form-holder">
|
||||
<div class="form-content">
|
||||
<div class="form-items">
|
||||
<div class="website-logo-inside">
|
||||
<img class="logo-size" src="/static/images/bladex-logo.png">
|
||||
<h1>BladeX 统一认证系统</h1>
|
||||
</div>
|
||||
<p>欢迎使用统一认证,提交后请对应用进行授权</p>
|
||||
<div class="page-links">
|
||||
<a>请输入认证信息</a>
|
||||
</div>
|
||||
<div>
|
||||
<input id='pk' name='pk' th:value="${publicKey}" type='hidden'/>
|
||||
</div>
|
||||
<form id="form" action="/oauth/login/perform" method="post">
|
||||
<input class="form-control" type="text" name="tenant_id" placeholder="请输入租户ID" required>
|
||||
<input class="form-control" type="text" name="username" placeholder="请输入用户名" required>
|
||||
<input class="form-control" id="ipt" type="password" name="password" placeholder="请输入密码" required>
|
||||
<div class="form-button">
|
||||
<button id="btn" type="button" class="ibtn">登 录</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="other-links">
|
||||
<span>Copyrights © <span id="copyright-notice"></span> <a href="https://bladex.cn" target="_blank">BladeX</a> All Rights Reserved.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/jquery.min.js"></script>
|
||||
<script src="/static/js/popper.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
<script src="/static/js/sm2.min.js"></script>
|
||||
<script>
|
||||
$(window).on("load", function () {
|
||||
// 加载版权信息
|
||||
const currentYear = new Date().getFullYear();
|
||||
$("#copyright-notice").html(currentYear);
|
||||
});
|
||||
|
||||
$("#btn").click(function(){
|
||||
const val = $("#ipt").val();
|
||||
$("#ipt").val(sm2.doEncrypt(val, $("#pk").val(), 0));
|
||||
$("#btn").attr("disabled", "disabled");
|
||||
$("#btn").text("登 录 中");
|
||||
$("#form").submit();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user