This commit is contained in:
kk
2026-07-07 18:05:54 +08:00
commit d4ef46bf97
743 changed files with 159169 additions and 0 deletions

View File

@@ -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.auth;
import org.springblade.core.cloud.client.BladeCloudApplication;
import org.springblade.core.launch.BladeApplication;
import org.springblade.core.launch.constant.AppConstant;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
/**
* 用户认证服务器
*
* @author Chill
*/
@EnableRedisHttpSession
@BladeCloudApplication
public class AuthApplication {
public static void main(String[] args) {
BladeApplication.run(AppConstant.APPLICATION_AUTH_NAME, AuthApplication.class, args);
}
}

View File

@@ -0,0 +1,101 @@
/**
* 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.auth.config;
import org.springblade.auth.handler.BladeAuthorizationHandler;
import org.springblade.auth.handler.BladeLockHandler;
import org.springblade.auth.handler.BladeLogHandler;
import org.springblade.auth.handler.BladePasswordHandler;
import org.springblade.auth.handler.BladeTokenHandler;
import org.springblade.auth.service.BladeClientDetailService;
import org.springblade.auth.service.BladeUserDetailService;
import org.springblade.core.jwt.props.JwtProperties;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.oauth2.config.OAuth2AutoConfiguration;
import org.springblade.core.oauth2.handler.AuthorizationHandler;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.handler.TokenHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.service.OAuth2ClientService;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.system.feign.IAuthLockClient;
import org.springblade.system.feign.IAuthLogClient;
import org.springblade.core.tenant.BladeTenantProperties;
import org.springblade.system.feign.IUserClient;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* BladeAuthConfiguration
*
* @author Chill
*/
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(OAuth2AutoConfiguration.class)
public class BladeAuthConfiguration {
@Bean
public AuthorizationHandler authorizationHandler(BladeProperties bladeProperties,
BladeTenantProperties tenantProperties,
OAuth2Properties oAuth2Properties,
BladeLockHandler lockHandler,
BladeLogHandler logHandler) {
return new BladeAuthorizationHandler(bladeProperties, tenantProperties, oAuth2Properties, lockHandler, logHandler);
}
@Bean
public BladeLockHandler lockHandler(IAuthLockClient authLockClient) {
return new BladeLockHandler(authLockClient);
}
@Bean
public BladeLogHandler logHandler(IAuthLogClient authLogClient, BladeProperties bladeProperties, ServerInfo serverInfo) {
return new BladeLogHandler(authLogClient, bladeProperties, serverInfo);
}
@Bean
public PasswordHandler passwordHandler(OAuth2Properties properties) {
return new BladePasswordHandler(properties);
}
@Bean
public TokenHandler tokenHandler(JwtProperties jwtProperties) {
return new BladeTokenHandler(jwtProperties);
}
@Bean
public OAuth2ClientService oAuth2ClientService(JdbcTemplate jdbcTemplate) {
return new BladeClientDetailService(jdbcTemplate);
}
@Bean
public OAuth2UserService oAuth2UserService(IUserClient userClient) {
return new BladeUserDetailService(userClient);
}
}

View File

@@ -0,0 +1,35 @@
/**
* 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.auth.constant;
/**
* AuthorizationConstant
*
* @author Chill
*/
public interface BladeAuthConstant {
}

View File

@@ -0,0 +1,98 @@
/**
* 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.auth.endpoint;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springblade.core.oauth2.props.OAuth2Properties;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.OAuth2UserService;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.resource.feign.ISmsClient;
import org.springblade.resource.utils.SmsUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static org.springblade.core.oauth2.constant.OAuth2TokenConstant.USER_PHONE_NOT_FOUND;
/**
* Oauth2SmsEndpoint
*
* @author Chill
*/
@RestController
@AllArgsConstructor
@Tag(name = "用户短信认证", description = "4 - OAuth2短信认证端点")
public class Oauth2SmsEndpoint {
/**
* 短信服务构建类
*/
private final ISmsClient smsClient;
/**
* 用户服务类
*/
private final OAuth2UserService userService;
/**
* OAuth2配置类
*/
private final OAuth2Properties properties;
/**
* 短信验证码发送
*
* @param tenantId 租户ID
* @param phone 手机号
*/
@SneakyThrows
@PostMapping("/oauth/sms/send-validate")
public R sendValidate(@RequestParam String tenantId, @RequestParam String phone) {
// 校验手机加密认证,防止恶意发送验证码
String decryptedPhone = SM2Util.decrypt(phone, properties.getPublicKey(), properties.getPrivateKey());
if (StringUtil.isBlank(decryptedPhone)) {
return R.fail(USER_PHONE_NOT_FOUND);
}
// 校验手机是否已注册,防止恶意发送验证码
OAuth2Request request = OAuth2Request.create();
request.setTenantId(tenantId);
OAuth2User oAuth2User = userService.loadByPhone(decryptedPhone, request);
if (oAuth2User == null) {
return R.fail(USER_PHONE_NOT_FOUND);
}
// 用户存在则发送验证码
R result = smsClient.sendValidate(tenantId, StringPool.EMPTY, decryptedPhone);
return result.isSuccess() ? R.data(result.getData(), SmsUtil.SEND_SUCCESS) : R.fail(SmsUtil.SEND_FAIL);
}
}

View File

@@ -0,0 +1,76 @@
/**
* 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.auth.granter;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.PasswordTokenGranter;
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.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* BehaviorTokenGranter
*
* <p>行为验证码授权模式。行为验证(点选/滑块/旋转)在专用端点完成并换取一次性通行票据,
* 登录请求经由验证码请求头携带票据,本授权器核销票据后进入密码认证链路。</p>
*
* @author Chill
*/
@Component
public class BehaviorTokenGranter extends PasswordTokenGranter {
private final BladeRedis bladeRedis;
public BehaviorTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, BladeRedis bladeRedis) {
super(clientService, userService, passwordHandler);
this.bladeRedis = bladeRedis;
}
@Override
public String type() {
return BEHAVIOR;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 获取行为验证票据信息
String key = request.getCaptchaKey();
String ticket = request.getCaptchaCode();
// 票据一次性核销,防止同一票据重复登录
String cacheTicket = bladeRedis.getAndDel(OAuth2TokenConstant.BEHAVIOR_TICKET_KEY + key);
// 判断票据有效性
if (StringUtil.isBlank(ticket) || !StringUtil.equals(cacheTicket, ticket)) {
throw new UserInvalidException(OAuth2TokenConstant.BEHAVIOR_NOT_CORRECT);
}
return super.user(request);
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.auth.granter;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.PasswordTokenGranter;
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.redis.cache.BladeRedis;
import org.springblade.core.tool.utils.StringUtil;
import org.springframework.stereotype.Component;
/**
* CaptchaTokenGranter
*
* @author BladeX
*/
@Component
public class CaptchaTokenGranter extends PasswordTokenGranter {
private final BladeRedis bladeRedis;
public CaptchaTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, BladeRedis bladeRedis) {
super(clientService, userService, passwordHandler);
this.bladeRedis = bladeRedis;
}
@Override
public String type() {
return CAPTCHA;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 获取验证码信息
String key = request.getCaptchaKey();
String code = request.getCaptchaCode();
// 获取验证码
String redisCode = bladeRedis.getAndDel(OAuth2TokenConstant.CAPTCHA_CACHE_KEY + key);
// 判断验证码
if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {
throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
}
return super.user(request);
}
}

View File

@@ -0,0 +1,161 @@
/**
* 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.auth.granter;
import org.jetbrains.annotations.NotNull;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
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.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.NumberUtil;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.system.cache.ParamCache;
import org.springblade.system.feign.IUserClient;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.enums.UserType;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.function.Predicate;
import static org.springblade.common.constant.ParamConstant.REGISTER_USER_VALUE;
/**
* RegisterTokenGranter
*
* @author BladeX
*/
@Component
public class RegisterTokenGranter extends AbstractTokenGranter {
private final IUserClient userClient;
private final OAuth2Properties properties;
public RegisterTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, IUserClient userClient, OAuth2Properties properties) {
super(clientService, userService, passwordHandler);
this.userClient = userClient;
this.properties = properties;
}
@Override
public String type() {
return REGISTER;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 校验注册功能是否开启
Boolean registerOpen = Func.toBoolean(ParamCache.getValue(REGISTER_USER_VALUE), false);
if (!registerOpen) {
throw new UserInvalidException("注册功能暂未开启,请联系管理员");
}
// 用户注册信息
User user = new User();
user.setUserType(UserType.of(request.getUserType()).getCategory());
user.setTenantId(request.getTenantId());
user.setAccount(request.getUsername());
user.setPassword(SM2Util.decrypt(request.getPassword(), properties.getPublicKey(), properties.getPrivateKey()));
user.setName(request.getName());
user.setRealName(request.getName());
user.setPhone(request.getPhone());
user.setEmail(request.getEmail());
// 校验用户格式
validateUser(user);
R<String> result = userClient.registerUser(user);
// 执行用户注册
if (result.isSuccess()) {
// 构建oauth2所需用户信息
user.setId(NumberUtil.toLong(result.getData()));
return convertOAuth2UserDetail(user, client(request));
}
throw new UserInvalidException(result.getMsg());
}
@Override
public OAuth2Token token(OAuth2User user, OAuth2Request request) {
// 移除注册后返回的令牌与刷新令牌,防止外部攻击采用注册接口获取令牌并调用低权接口
// 注意:
// 1. 框架已默认开启严格模式blade.secure.strict-token=true不移除令牌则不受影响注册令牌会被框架校验并拒绝
// 2. 若自行关闭严格模式blade.secure.strict-token=false必须将令牌移除否则注册获取令牌后可调用低权接口
OAuth2Token token = super.token(user, request);
token.getArgs().remove(TokenConstant.ACCESS_TOKEN);
token.getArgs().remove(TokenConstant.REFRESH_TOKEN);
return token;
}
private void validateUser(User user) {
Predicate<String> isNameValid = name -> name.matches("^([\\u4e00-\\u9fa5]{2,20}|[a-zA-Z]{2,10})$");
Predicate<String> isUsernameValid = username -> username.matches("^(?=.*[a-zA-Z])[a-zA-Z0-9_\\-@]{3,20}$");
Predicate<String> isPasswordValid = password -> password.matches("^(?=.*[0-9])(?=.*[a-zA-Z])[\\w@-]{6,20}$");
Predicate<String> isPhoneValid = phone -> phone.matches("^1[3-9]\\d{9}$");
Predicate<String> isEmailValid = email -> email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
if (!isNameValid.test(user.getName())) {
throw new UserInvalidException("用户姓名长度必须在2-10之间且仅能设置纯中文或纯英文");
}
if (!isUsernameValid.test(user.getAccount())) {
throw new UserInvalidException("用户账号长度必须在3-20之间且需要包含英文可额外携带数字、下划线、横杠、@");
}
if (!isPasswordValid.test(user.getPassword())) {
throw new UserInvalidException("用户密码长度必须在6-20之间且需要包含英文与数字可额外携带下划线、横杠、@");
}
if (!isPhoneValid.test(user.getPhone())) {
throw new UserInvalidException("手机号格式不正确");
}
if (!isEmailValid.test(user.getEmail())) {
throw new UserInvalidException("邮箱格式不正确");
}
}
@NotNull
private OAuth2UserDetail convertOAuth2UserDetail(User user, OAuth2Client client) {
OAuth2UserDetail userDetail = new OAuth2UserDetail();
userDetail.setUserId(String.valueOf(user.getId()));
userDetail.setTenantId(user.getTenantId());
userDetail.setName(user.getName());
userDetail.setRealName(user.getName());
userDetail.setAccount(user.getAccount());
userDetail.setPassword(user.getPassword());
userDetail.setPhone(user.getPhone());
userDetail.setEmail(user.getEmail());
userDetail.setAuthorities(Collections.singletonList(REGISTER));
userDetail.setClient(client);
return userDetail;
}
}

View File

@@ -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.auth.granter;
import jakarta.servlet.http.HttpServletRequest;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.UserInvalidException;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
import org.springblade.core.oauth2.handler.PasswordHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
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.sms.model.SmsCode;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.resource.feign.ISmsClient;
import org.springframework.stereotype.Component;
/**
* SmsTokenGranter
*
* @author BladeX
*/
@Component
public class SmsTokenGranter extends AbstractTokenGranter {
private final OAuth2UserService userService;
private final ISmsClient smsClient;
private final OAuth2Properties properties;
public SmsTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler, ISmsClient smsClient, OAuth2Properties properties) {
super(clientService, userService, passwordHandler);
this.userService = userService;
this.smsClient = smsClient;
this.properties = properties;
}
@Override
public String type() {
return SMS_CODE;
}
@Override
public OAuth2User user(OAuth2Request request) {
// 获取基础信息
String tenantId = request.getTenantId();
SmsCode smsCode = buildSmsCode();
// 校验手机加密认证
String decryptedPhone = SM2Util.decrypt(smsCode.getPhone(), properties.getPublicKey(), properties.getPrivateKey());
if (StringUtil.isBlank(decryptedPhone)) {
throw new UserInvalidException(OAuth2TokenConstant.USER_PHONE_NOT_FOUND);
}
// 获取短信验证信息
R result = smsClient.validateMessage(tenantId, StringPool.EMPTY, smsCode.getId(), smsCode.getValue(), decryptedPhone);
if (!result.isSuccess()) {
throw new UserInvalidException(OAuth2TokenConstant.CAPTCHA_NOT_CORRECT);
}
// 获取用户信息
OAuth2User user = userService.loadByPhone(decryptedPhone, request);
// 校验用户信息
if (!userService.validateUser(user)) {
throw new UserInvalidException(OAuth2TokenConstant.TOKEN_NOT_CORRECT);
}
// 设置客户端信息
user.setClient(client(request));
return user;
}
private SmsCode buildSmsCode() {
HttpServletRequest request = WebUtil.getRequest();
return new SmsCode().setId(request.getParameter("id"))
.setPhone(request.getParameter("phone"))
.setValue(request.getParameter("value"));
}
}

View File

@@ -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.auth.granter;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
import org.springblade.core.oauth2.granter.AbstractTokenGranter;
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.social.props.SocialProperties;
import org.springblade.core.social.utils.SocialUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.system.pojo.entity.UserInfo;
import org.springblade.system.pojo.entity.UserOauth;
import org.springblade.system.feign.IUserClient;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* SocialTokenGranter
*
* @author Chill
*/
@Component
public class SocialTokenGranter extends AbstractTokenGranter {
private static final Integer AUTH_SUCCESS_CODE = 2000;
private final IUserClient userClient;
private final SocialProperties socialProperties;
public SocialTokenGranter(OAuth2ClientService clientService, OAuth2UserService oAuth2UserService, PasswordHandler passwordHandler, IUserClient userClient, SocialProperties socialProperties) {
super(clientService, oAuth2UserService, passwordHandler);
this.userClient = userClient;
this.socialProperties = socialProperties;
}
@Override
public String type() {
return SOCIAL;
}
@Override
public OAuth2User user(OAuth2Request request) {
String tenantId = request.getTenantId();
// 开放平台来源
String sourceParameter = request.getSource();
// 匹配是否有别名定义
String source = socialProperties.getAlias().getOrDefault(sourceParameter, sourceParameter);
// 开放平台授权码
String code = request.getCode();
// 开放平台状态吗
String state = request.getState();
// 获取开放平台授权数据
AuthRequest authRequest = SocialUtil.getAuthRequest(source, socialProperties);
AuthCallback authCallback = new AuthCallback();
authCallback.setCode(code);
authCallback.setState(state);
AuthResponse<?> authResponse = authRequest.login(authCallback);
AuthUser authUser = null;
if (authResponse.getCode() == AUTH_SUCCESS_CODE) {
authUser = (AuthUser) authResponse.getData();
} else {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 组装数据
UserOauth userOauth = Objects.requireNonNull(BeanUtil.copyProperties(authUser, UserOauth.class));
userOauth.setSource(authUser.getSource());
userOauth.setTenantId(tenantId);
userOauth.setUuid(authUser.getUuid());
R<UserInfo> result = userClient.userAuthInfo(userOauth);
if (!result.isSuccess()) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 设置Oauth2用户信息
OAuth2User user = TokenUtil.convertUser(result.getData(), request);
// 设置客户端信息
user.setClient(client(request));
return user;
}
}

View File

@@ -0,0 +1,200 @@
/**
* 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.auth.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.constant.TenantConstant;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.handler.AbstractAuthorizationHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
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.tenant.BladeTenantProperties;
import org.springblade.core.tool.jackson.JsonUtil;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.system.cache.SysCache;
import org.springblade.system.pojo.entity.Tenant;
import java.util.Date;
import java.util.List;
/**
* BladeAuthorizationHandler
*
* @author BladeX
*/
@Slf4j
@RequiredArgsConstructor
public class BladeAuthorizationHandler extends AbstractAuthorizationHandler {
private final BladeProperties bladeProperties;
private final BladeTenantProperties tenantProperties;
private final OAuth2Properties oAuth2Properties;
private final BladeLockHandler lockHandler;
private final BladeLogHandler logHandler;
/**
* 自定义弱密码列表
*/
private static final List<String> WEAK_PASSWORDS = List.of("admin", "hr", "manager", "boss");
/**
* 认证前校验
*
* @param request 请求信息
* @return boolean
*/
@Override
public OAuth2Validation preValidation(OAuth2Request request) {
if (request.isPassword() || request.isCaptchaCode()) {
// 生产环境弱密码校验
if (bladeProperties.isProd() && isWeakPassword(request.getPassword())) {
return buildValidationFailure(ExceptionCode.INVALID_USER_PASSWORD);
}
// 判断账号是否锁定
OAuth2Validation accountValidation = lockHandler.validateAccountLock(request.getTenantId(), request.getUsername());
if (!accountValidation.isSuccess()) {
return accountValidation;
}
// 判断IP是否锁定
OAuth2Validation ipValidation = lockHandler.validateIpLock(request.getTenantId());
if (!ipValidation.isSuccess()) {
return ipValidation;
}
}
return super.preValidation(request);
}
/**
* 认证前失败回调
*
* @param validation 失败信息
*/
@Override
public void preFailure(OAuth2Request request, OAuth2Validation validation) {
// 处理认证失败,增加错误次数
lockHandler.handleAuthFailure(request.getTenantId(), request.getUsername());
log.error("用户:{},认证失败,失败原因:{}", request.getUsername(), validation.getMessage());
}
/**
* 认证校验
*
* @param user 用户信息
* @param request 请求信息
* @return boolean
*/
@Override
public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
// 密码模式、刷新token模式、验证码模式需要校验租户状态
if (request.isPassword() || request.isRefreshToken() || request.isCaptchaCode()) {
// 租户校验
OAuth2Validation tenantValidation = validateTenant(user.getTenantId());
if (!tenantValidation.isSuccess()) {
return tenantValidation;
}
}
return super.authValidation(user, request);
}
/**
* 认证成功回调
*
* @param user 用户信息
*/
@Override
public void authSuccessful(OAuth2User user, OAuth2Request request) {
// 处理认证成功,清空错误次数
lockHandler.handleAuthSuccess(user.getTenantId(), user.getAccount());
// 记录认证成功日志
logHandler.handleAuthLog(user, request);
log.info("用户:{},认证成功", user.getAccount());
}
/**
* 认证失败回调
*
* @param user 用户信息
* @param validation 失败信息
*/
@Override
public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
// 自定义认证失败回调
}
/**
* 判断是否为弱密码
*
* @param rawPassword 加密密码
* @return boolean
*/
private boolean isWeakPassword(String rawPassword) {
// 获取公钥
String publicKey = oAuth2Properties.getPublicKey();
// 获取私钥
String privateKey = oAuth2Properties.getPrivateKey();
// 解密密码
String decryptPassword = SM2Util.decrypt(rawPassword, publicKey, privateKey);
return WEAK_PASSWORDS.stream()
.anyMatch(weakPass -> weakPass.equalsIgnoreCase(decryptPassword));
}
/**
* 租户授权校验
*
* @param tenantId 租户id
* @return OAuth2Validation
*/
private OAuth2Validation validateTenant(String tenantId) {
// 租户校验
Tenant tenant = SysCache.getTenant(tenantId);
if (tenant == null) {
return buildValidationFailure(ExceptionCode.USER_TENANT_NOT_FOUND);
}
// 租户授权时间校验
Date expireTime = tenant.getExpireTime();
if (tenantProperties.getLicense()) {
String licenseKey = tenant.getLicenseKey();
String decrypt = DesUtil.decryptFormHex(licenseKey, TenantConstant.DES_KEY);
Tenant license = JsonUtil.parse(decrypt, Tenant.class);
if (license == null || !license.getId().equals(tenant.getId())) {
return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
}
expireTime = license.getExpireTime();
}
if (expireTime != null && expireTime.before(DateUtil.now())) {
return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
}
return new OAuth2Validation();
}
}

View File

@@ -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.auth.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.provider.OAuth2Validation;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.feign.IAuthLockClient;
import java.util.concurrent.CompletableFuture;
/**
* 失败锁定处理器
* 统一管理账号锁定和IP锁定逻辑
*
* @author BladeX
*/
@Slf4j
@RequiredArgsConstructor
public class BladeLockHandler {
private final IAuthLockClient authLockClient;
/**
* 校验账号是否锁定
*
* @param tenantId 租户id
* @param account 账号
* @return OAuth2Validation
*/
public OAuth2Validation validateAccountLock(String tenantId, String account) {
try {
R<Boolean> result = authLockClient.isAccountLocked(tenantId, account);
if (result.isSuccess() && Boolean.TRUE.equals(result.getData())) {
log.error("用户:{}已锁定请求ip{}", account, WebUtil.getIP());
return buildValidationFailure();
}
} catch (Exception e) {
log.warn("账号锁定校验异常: {}", e.getMessage());
}
return new OAuth2Validation();
}
/**
* 校验IP是否锁定
*
* @param tenantId 租户id
* @return OAuth2Validation
*/
public OAuth2Validation validateIpLock(String tenantId) {
String clientIp = WebUtil.getIP();
try {
R<Boolean> result = authLockClient.isIpLocked(tenantId, clientIp);
if (result.isSuccess() && Boolean.TRUE.equals(result.getData())) {
log.error("IP{},已锁定", clientIp);
return buildValidationFailure();
}
} catch (Exception e) {
log.warn("IP锁定校验异常: {}", e.getMessage());
}
return new OAuth2Validation();
}
/**
* 处理认证失败
* 同时增加账号和IP错误次数
*
* @param tenantId 租户id
* @param account 账号
* @param userId 用户ID
*/
public void handleAuthFailure(String tenantId, String account, Long userId) {
String ip = WebUtil.getIP();
String userAgent = WebUtil.getUserAgent();
CompletableFuture.runAsync(() -> {
try {
authLockClient.addAccountFailCount(tenantId, account, userId, ip, userAgent);
authLockClient.addIpFailCount(tenantId, ip, userAgent);
} catch (Exception e) {
log.warn("认证失败计数异常: {}", e.getMessage());
}
});
}
/**
* 处理认证失败
* 同时增加账号和IP错误次数
*
* @param tenantId 租户id
* @param account 账号
*/
public void handleAuthFailure(String tenantId, String account) {
handleAuthFailure(tenantId, account, null);
}
/**
* 处理认证成功
* 释放系统自动锁定
*
* @param tenantId 租户id
* @param account 账号
*/
public void handleAuthSuccess(String tenantId, String account) {
String ip = WebUtil.getIP();
CompletableFuture.runAsync(() -> {
try {
authLockClient.releaseSystemLock(tenantId, account);
authLockClient.releaseSystemIpLock(tenantId, ip);
} catch (Exception e) {
log.warn("认证成功释放锁定异常: {}", e.getMessage());
}
});
}
/**
* 构建校验失败结果
*
* @return OAuth2Validation
*/
private OAuth2Validation buildValidationFailure() {
OAuth2Validation validation = new OAuth2Validation();
validation.setSuccess(false);
validation.setCode(ExceptionCode.USER_TOO_MANY_FAILS.getCode());
validation.setMessage(ExceptionCode.USER_TOO_MANY_FAILS.getMessage());
return validation;
}
}

View File

@@ -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.auth.handler;
import org.springblade.core.launch.props.BladeProperties;
import org.springblade.core.launch.server.ServerInfo;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.system.pojo.entity.AuthLog;
import org.springblade.system.feign.IAuthLogClient;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
/**
* 认证日志处理器
* 在用户认证成功时异步记录登录日志
*
* @author BladeX
*/
@Slf4j
@RequiredArgsConstructor
public class BladeLogHandler {
private final IAuthLogClient authLogClient;
private final BladeProperties bladeProperties;
private final ServerInfo serverInfo;
/**
* 记录认证成功日志
*
* @param user 用户信息
* @param request 请求信息
*/
public void handleAuthLog(OAuth2User user, OAuth2Request request) {
// 异步记录日志,避免影响认证性能
CompletableFuture.runAsync(() -> {
try {
AuthLog authLog = buildAuthLog(user, request);
authLogClient.saveAuthLog(authLog);
} catch (Exception exception) {
log.error("记录认证日志异常:{}", exception.getMessage(), exception);
}
});
}
/**
* 构建认证日志实体
*
* @param user 用户信息
* @param request 请求信息
* @return AuthLog
*/
private AuthLog buildAuthLog(OAuth2User user, OAuth2Request request) {
AuthLog authLog = new AuthLog();
authLog.setUserId(Func.toLong(user.getUserId()));
authLog.setTenantId(user.getTenantId());
authLog.setServiceId(bladeProperties.getName());
authLog.setServerIp(serverInfo.getIpWithPort());
authLog.setServerHost(serverInfo.getHostName());
authLog.setEnv(bladeProperties.getEnv());
authLog.setAccount(user.getAccount());
authLog.setRealName(user.getRealName());
authLog.setGrantType(request.getGrantType());
authLog.setRemoteIp(WebUtil.getIP(request.getHttpRequest()));
authLog.setUserAgent(WebUtil.getUserAgent(request.getHttpRequest()));
authLog.setLoginTime(DateUtil.now());
return authLog;
}
}

View File

@@ -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.auth.handler;
import org.springblade.core.oauth2.handler.OAuth2PasswordHandler;
import org.springblade.core.oauth2.props.OAuth2Properties;
/**
* BladePasswordHandler
*
* @author BladeX
*/
public class BladePasswordHandler extends OAuth2PasswordHandler {
public BladePasswordHandler(OAuth2Properties properties) {
super(properties);
}
/**
* 判断密码是否匹配
*
* @param rawPassword 请求时提交的原密码
* @param encodedPassword 数据库加密后的密码
* @return boolean
*/
@Override
public boolean matches(String rawPassword, String encodedPassword) {
return super.matches(rawPassword, encodedPassword);
}
/**
* 加密密码规则
*
* @param rawPassword 密码
* @return 加密后的密码
*/
@Override
public String encode(String rawPassword) {
return super.encode(rawPassword);
}
}

View File

@@ -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.auth.handler;
import org.springblade.core.jwt.props.JwtProperties;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.oauth2.handler.OAuth2TokenHandler;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.provider.OAuth2Token;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.tool.support.Kv;
/**
* BladeTokenHandler
*
* @author BladeX
*/
public class BladeTokenHandler extends OAuth2TokenHandler {
public BladeTokenHandler(JwtProperties properties) {
super(properties);
}
@Override
public OAuth2Token enhance(OAuth2User user, OAuth2Token token, OAuth2Request request) {
// 父类令牌状态配置
OAuth2Token enhanceToken = super.enhance(user, token, request);
// 令牌统一处理,增加或删减字段
Kv args = enhanceToken.getArgs();
args.set(TokenConstant.USER_NAME, user.getAccount());
// 返回令牌
return enhanceToken;
}
}

View File

@@ -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.auth.service;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2Client;
import org.springblade.core.oauth2.service.impl.OAuth2ClientDetailService;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* BladeClientDetailService
*
* @author Chill
*/
public class BladeClientDetailService extends OAuth2ClientDetailService {
public BladeClientDetailService(JdbcTemplate jdbcTemplate) {
super(jdbcTemplate);
}
@Override
public OAuth2Client loadByClientId(String clientId) {
return super.loadByClientId(clientId);
}
@Override
public OAuth2Client loadByClientId(String clientId, OAuth2Request request) {
return super.loadByClientId(clientId, request);
}
@Override
public boolean validateClient(OAuth2Client client, String clientId, String clientSecret) {
return super.validateClient(client, clientId, clientSecret);
}
@Override
public boolean validateGranter(OAuth2Client client, String grantType) {
return super.validateGranter(client, grantType);
}
}

View File

@@ -0,0 +1,111 @@
/**
* 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.auth.service;
import lombok.RequiredArgsConstructor;
import org.springblade.auth.utils.TokenUtil;
import org.springblade.core.oauth2.exception.OAuth2ErrorCode;
import org.springblade.core.oauth2.provider.OAuth2Request;
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.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.system.feign.IUserClient;
import org.springblade.system.pojo.entity.UserInfo;
import org.springblade.system.pojo.enums.UserType;
import java.util.Optional;
/**
* BladeUserDetailService
*
* @author Chill
*/
@RequiredArgsConstructor
public class BladeUserDetailService implements OAuth2UserService {
private final IUserClient userClient;
@Override
public OAuth2User loadByUserId(String userId, OAuth2Request request) {
// 获取用户参数
String userType = Optional.ofNullable(request.getUserType())
.filter(s -> !StringUtil.isBlank(s))
.orElse(UserType.WEB.getName());
// 获取用户信息
R<UserInfo> result = userClient.userInfo(Func.toLong(userId), userType);
if (!result.isSuccess()) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 构建oauth2用户信息
return TokenUtil.convertUser(result.getData(), request);
}
@Override
public OAuth2User loadByUsername(String username, OAuth2Request request) {
// 获取用户参数
String userType = Optional.ofNullable(request.getUserType())
.filter(s -> !StringUtil.isBlank(s))
.orElse(UserType.WEB.getName());
String tenantId = request.getTenantId();
// 获取用户信息
R<UserInfo> result = userClient.userInfo(tenantId, username, userType);
if (!result.isSuccess()) {
return null;
}
// 构建oauth2用户信息
return TokenUtil.convertUser(result.getData(), request);
}
@Override
public OAuth2User loadByPhone(String phone, OAuth2Request request) {
// 获取用户参数
String userType = Optional.ofNullable(request.getUserType())
.filter(s -> !StringUtil.isBlank(s))
.orElse(UserType.WEB.getName());
String tenantId = request.getTenantId();
// 获取用户信息
R<UserInfo> result = userClient.userInfoByPhone(tenantId, phone, userType);
if (!result.isSuccess()) {
return null;
}
// 构建oauth2用户信息
return TokenUtil.convertUser(result.getData(), request);
}
@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
}
}

View File

@@ -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.auth.utils;
import org.springblade.core.oauth2.provider.OAuth2Request;
import org.springblade.core.oauth2.service.OAuth2User;
import org.springblade.core.oauth2.service.impl.OAuth2UserDetail;
import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.SysCache;
import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.entity.UserInfo;
import java.util.Collections;
/**
* 认证工具类
*
* @author Chill
*/
public class TokenUtil {
/**
* 系统用户转换为OAuth2标准用户
*
* @param userInfo 用户信息
* @param request 请求信息
* @return OAuth2User
*/
public static OAuth2User convertUser(UserInfo userInfo, OAuth2Request request) {
// 为空则返回null
if (userInfo == null || userInfo.getUser() == null) {
return null;
}
User user = userInfo.getUser();
String userDept = request.getUserDept();
String userRole = request.getUserRole();
// 单独指定部门
if (Func.isNotEmpty(userDept) && user.getDeptId().contains(userDept)) {
user.setDeptId(userDept);
}
// 单独指定角色
if (Func.isNotEmpty(userRole) && user.getRoleId().contains(userRole)) {
user.setRoleId(userRole);
userInfo.setRoles(SysCache.getRoleAliases(userRole));
}
// 构建oauth2所需用户信息
OAuth2UserDetail userDetail = new OAuth2UserDetail();
userDetail.setUserId(String.valueOf(user.getId()));
userDetail.setOauthId(userInfo.getOauthId());
userDetail.setTenantId(user.getTenantId());
userDetail.setName(user.getName());
userDetail.setRealName(user.getRealName());
userDetail.setAccount(user.getAccount());
userDetail.setPassword(user.getPassword());
userDetail.setDeptId(user.getDeptId());
userDetail.setPostId(user.getPostId());
userDetail.setRoleId(user.getRoleId());
userDetail.setRoleName(Func.join(userInfo.getRoles()));
userDetail.setAvatar(user.getAvatar());
userDetail.setAuthorities(userInfo.getRoles());
userDetail.setDetail(userInfo.getDetail());
return userDetail;
}
}

View File

@@ -0,0 +1,15 @@
#服务器端口
server:
port: 8100
#数据源配置
spring:
datasource:
url: ${blade.datasource.dev.url}
username: ${blade.datasource.dev.username}
password: ${blade.datasource.dev.password}
#第三方登陆
social:
enabled: true
domain: http://127.0.0.1:2888

View File

@@ -0,0 +1,15 @@
#服务器端口
server:
port: 8100
#数据源配置
spring:
datasource:
url: ${blade.datasource.prod.url}
username: ${blade.datasource.prod.username}
password: ${blade.datasource.prod.password}
#第三方登陆
social:
enabled: true
domain: http://127.0.0.1:2888

View File

@@ -0,0 +1,15 @@
#服务器端口
server:
port: 8100
#数据源配置
spring:
datasource:
url: ${blade.datasource.test.url}
username: ${blade.datasource.test.username}
password: ${blade.datasource.test.password}
#第三方登陆
social:
enabled: true
domain: http://127.0.0.1:2888

View File

@@ -0,0 +1,65 @@
# 在使用Spring默认数据源Hikari的情况下配置以下配置项
spring:
datasource:
hikari:
# 自动提交从池中返回的连接
auto-commit: true
# 连接池中维护的最小空闲连接数
minimum-idle: 10
# 连接池中允许的最大连接数。缺省值10推荐的公式((core_count * 2) + effective_spindle_count)
maximum-pool-size: 60
# 空闲连接超时时间默认值60000010分钟大于等于max-lifetime且max-lifetime>0会被重置为0不等于0且小于10秒会被重置为10秒。
# 只有空闲连接数大于最大连接数且空闲时间超过该值,才会被释放
idle-timeout: 30000
# 连接最大存活时间.不等于0且小于30秒会被重置为默认值30分钟.设置应该比mysql设置的超时时间短
max-lifetime: 1800000
# 等待连接池分配连接的最大时长毫秒超过这个时长还没可用的连接则发生SQLException 缺省:30秒
connection-timeout: 30000
# 连接测试查询
connection-test-query: select 1
#connection-test-query: select 1 from dual
freemarker:
# 模板后缀名
suffix: .ftl
# 文档类型
content-type: text/html
# 页面编码
charset: UTF-8
# 页面缓存
cache: false
# 模板路径
template-loader-path: classpath:/templates/
web:
# 资源路径
resources:
static-locations: classpath:/static/
#swagger文档
swagger:
base-packages:
- org.springblade
- org.springframework.security.oauth2.provider.endpoint
#第三方登陆
social:
oauth:
GITHUB:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/github
GITEE:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/gitee
WECHAT_OPEN:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/wechat
QQ:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/qq
DINGTALK:
client-id: 233************
client-secret: 233************************************
redirect-uri: ${social.domain}/oauth/redirect/dingtalk

View File

@@ -0,0 +1,47 @@
package org.springblade.test;
import org.springblade.core.tool.utils.AesUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.StringPool;
/**
* Key生成器
*
* @author Chill
*/
public class CryptoKeyGenerator {
public static void main(String[] args) {
String cryptoKey = AesUtil.genAesKey();
String aesKey = AesUtil.genAesKey();
String desKey = DesUtil.genDesKey();
System.out.println("=========== blade.token.crypto-key 配置如下 ============");
System.out.println("#blade配置\n" +
"blade:\n" +
" token:\n" +
" crypto-key: " + cryptoKey);
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== blade.api.crypto 配置如下 ===============");
System.out.println("#blade配置\n" +
"blade:\n" +
" api:\n" +
" crypto:\n" +
" enabled: true\n" +
" aes-key: " + aesKey + "\n" +
" des-key: " + desKey);
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== saber crypto.js 配置如下 ===============");
System.out.println("export default class crypto {\n" +
" static cryptoKey = '" + cryptoKey + "';\n" +
" static aesKey = '" + aesKey + "';\n" +
" static desKey = '" + desKey + "';\n" +
"}");
System.out.println("=======================================================");
}
}

View File

@@ -0,0 +1,45 @@
package org.springblade.test;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.springblade.core.tool.utils.AesUtil;
import org.springblade.core.tool.utils.DesUtil;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.SM4Util;
/**
* 加密密钥生成器
*
* @author BladeX
*/
public class EncryptKeyGenerator {
public static void main(String[] args) {
// 生成对称加密密钥
String aesKey = AesUtil.genAesKey();
String desKey = DesUtil.genDesKey();
String sm4Key = SM4Util.generateKey();
// 生成SM2非对称加密密钥对
AsymmetricCipherKeyPair sm2KeyPair = SM2Util.generateKeyPair();
String sm2PublicKey = SM2Util.getPublicKeyString(sm2KeyPair);
String sm2PrivateKey = SM2Util.getPrivateKeyString(sm2KeyPair);
System.out.println("\n================== Encryption Keys Generated ==================\n");
System.out.println("[Symmetric Keys / 对称加密密钥]");
System.out.println();
System.out.println("AES-256-Key: " + aesKey);
System.out.println("DES-Key: " + desKey);
System.out.println("SM4-Key(国密): " + sm4Key);
System.out.println("\n----------------------------------------------------------------\n");
System.out.println("[Asymmetric Keys / 非对称加密密钥]");
System.out.println();
System.out.println("SM2-PublicKey(国密公钥): " + sm2PublicKey);
System.out.println("SM2-PrivateKey(国密私钥): " + sm2PrivateKey);
System.out.println("\n================================================================\n");
}
}

View File

@@ -0,0 +1,22 @@
package org.springblade.test;
import org.springblade.core.tool.utils.RandomType;
import org.springblade.core.tool.utils.StringUtil;
/**
* signKey生成器
*
* @author Chill
*/
public class SignKeyGenerator {
public static void main(String[] args) {
System.out.println("=========== blade.token.sign-key 配置如下 ==============");
System.out.println("#blade配置\n" +
"blade:\n" +
" token:\n" +
" sign-key: " + StringUtil.random(32, RandomType.ALL) );
System.out.println("=======================================================");
}
}

View File

@@ -0,0 +1,46 @@
package org.springblade.test;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.util.encoders.Hex;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringPool;
/**
* 国密算法生成器
*
* @author Chill
*/
public class Sm2KeyGenerator {
public static void main(String[] args) {
System.out.println("================ blade.oauth2 配置如下 =================");
AsymmetricCipherKeyPair keyPair = SM2Util.generateKeyPair();
String publicKey = SM2Util.getPublicKeyString(keyPair);
String privateKey = SM2Util.getPrivateKeyString(keyPair);
System.out.println("#blade配置 \n" +
"blade:\n" +
" oauth2:\n" +
" enabled: true\n" +
" public-key: " + publicKey + "\n" +
" private-key: " + privateKey);
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== saber website.js 配置如下 ===============");
System.out.println("//saber配置\n" +
"oauth2: {\n" +
" publicKey: '" + publicKey + "',\n" +
"}");
System.out.println("=======================================================");
System.out.println(StringPool.EMPTY);
System.out.println("============== 密码:[admin] 加密流程如下 ================");
String password = "admin";
byte[] encryptedData = SM2Util.encrypt(password, publicKey);
String decryptedText = SM2Util.decrypt(encryptedData, privateKey);
System.out.println("加密前: " + password);
System.out.println("加密后: " + Hex.toHexString(encryptedData));
System.out.println("解密后: " + decryptedText);
System.out.println("请注意: 此密文为前端加密后调用token接口的密码参数");
System.out.println("=======================================================");
}
}

View File

@@ -0,0 +1,120 @@
package org.springblade.test;
import lombok.SneakyThrows;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.util.encoders.Hex;
import org.springblade.core.tool.utils.SM2Util;
import org.springblade.core.tool.utils.StringUtil;
/**
* SM2Test
*
* @author Chill
*/
public class Sm2Test {
@SneakyThrows
public static void main(String[] args) {
System.out.println("================== SM2 加解密与签名测试 ==================");
// 1. 生成SM2密钥对
AsymmetricCipherKeyPair keyPair = SM2Util.generateKeyPair();
String publicKeyString = SM2Util.getPublicKeyString(keyPair);
String privateKeyString = SM2Util.getPrivateKeyString(keyPair);
System.out.println("1. 生成SM2密钥对:");
System.out.println(" 公钥 (16进制): " + publicKeyString);
System.out.println(" 公钥长度: " + publicKeyString.length());
System.out.println(" 私钥 (16进制): " + privateKeyString);
System.out.println(" 私钥长度: " + privateKeyString.length());
System.out.println("--------------------------------------------------");
// 2. 从字符串恢复密钥
System.out.println("2. 从字符串恢复密钥...");
ECPublicKeyParameters publicKey = SM2Util.stringToPublicKey(publicKeyString);
ECPrivateKeyParameters privateKey = SM2Util.stringToPrivateKey(privateKeyString);
System.out.println(" 密钥恢复成功!");
System.out.println("--------------------------------------------------");
// 3. 准备待加密的原文
String originalText = "Hello BladeX! 这是一段需要被国密SM2算法加密和签名的敏感信息。1234567890";
System.out.println("3. 待加密的原文: " + originalText);
System.out.println("--------------------------------------------------");
// 4. 执行加密
System.out.println("4. 正在执行加密操作...");
byte[] encryptedData = SM2Util.encrypt(originalText, publicKey);
if (encryptedData != null && encryptedData.length > 0) {
System.out.println(" 加密成功!");
System.out.println(" 加密后的密文 (16进制): " + Hex.toHexString(encryptedData));
System.out.println(" 密文长度: " + encryptedData.length + " 字节");
} else {
System.out.println(" 加密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 5. 执行解密
System.out.println("5. 正在执行解密操作...");
String decryptedText = SM2Util.decrypt(encryptedData, privateKey);
if (StringUtil.isNotBlank(decryptedText)) {
System.out.println(" 解密成功!");
System.out.println(" 解密后的明文: " + decryptedText);
} else {
System.out.println(" 解密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 6. 执行数字签名
System.out.println("6. 正在执行数字签名...");
byte[] signature = SM2Util.sign(originalText, privateKey);
if (signature != null && signature.length > 0) {
System.out.println(" 签名成功!");
System.out.println(" 数字签名 (16进制): " + Hex.toHexString(signature));
System.out.println(" 签名长度: " + signature.length + " 字节");
} else {
System.out.println(" 签名失败!");
return;
}
System.out.println("--------------------------------------------------");
// 7. 验证签名
System.out.println("7. 正在验证数字签名...");
boolean isVerified = SM2Util.verify(originalText, signature, publicKey);
System.out.println(" 签名验证结果: " + (isVerified ? "有效" : "无效"));
System.out.println("--------------------------------------------------");
// 8. 校验加解密结果
System.out.println("8. 正在校验原文与解密后的明文是否一致...");
boolean isDecryptMatch = originalText.equals(decryptedText);
System.out.println(" 加解密校验结果: " + (isDecryptMatch ? "一致" : "不一致"));
System.out.println("==================================================");
// 9. 最终结果汇总
if (isDecryptMatch && isVerified) {
System.out.println("\n[成功] SM2加解密与签名流程验证通过");
System.out.println(" ✓ 密钥对生成成功");
System.out.println(" ✓ 加解密功能正常");
System.out.println(" ✓ 数字签名验证通过");
} else {
System.out.println("\n[失败] SM2加解密与签名流程验证失败");
if (!isDecryptMatch) {
System.out.println(" ✗ 加解密校验失败");
}
if (!isVerified) {
System.out.println(" ✗ 数字签名验证失败");
}
}
}
}

View File

@@ -0,0 +1,71 @@
package org.springblade.test;
import lombok.SneakyThrows;
import org.springblade.core.tool.utils.SM4Util;
import org.springblade.core.tool.utils.StringUtil;
/**
* Sm4Test
*
* @author BladeX
*/
public class Sm4Test {
@SneakyThrows
public static void main(String[] args) {
System.out.println("================== SM4 加解密测试 ==================");
// 1. 生成SM4密钥
String sm4Key = SM4Util.generateKey();
System.out.println("1. 生成SM4密钥 (16进制): " + sm4Key);
System.out.println(" 密钥长度 (Hex): " + sm4Key.length());
System.out.println("--------------------------------------------------");
// 2. 准备待加密的原文
String originalText = "Hello BladeX! 这是一段需要被国密SM4算法加密的敏感信息。1234567890";
System.out.println("2. 待加密的原文: " + originalText);
System.out.println("--------------------------------------------------");
// 3. 执行加密
System.out.println("3. 正在执行加密操作...");
String encryptedText = SM4Util.encrypt(originalText, sm4Key);
if (StringUtil.isNotBlank(encryptedText)) {
System.out.println(" 加密成功!");
System.out.println(" 加密后的密文 (16进制): " + encryptedText);
} else {
System.out.println(" 加密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 4. 执行解密
System.out.println("4. 正在执行解密操作...");
String decryptedText = SM4Util.decrypt(encryptedText, sm4Key);
if (StringUtil.isNotBlank(decryptedText)) {
System.out.println(" 解密成功!");
System.out.println(" 解密后的明文: " + decryptedText);
} else {
System.out.println(" 解密失败!");
return;
}
System.out.println("--------------------------------------------------");
// 5. 校验结果
System.out.println("5. 正在校验原文与解密后的明文是否一致...");
boolean isMatch = originalText.equals(decryptedText);
System.out.println(" 校验结果: " + (isMatch ? "一致" : "不一致"));
System.out.println("==================================================");
if (isMatch) {
System.out.println("\n[成功] SM4加解密流程验证通过");
} else {
System.out.println("\n[失败] SM4加解密流程验证失败");
}
}
}