feat(auth): 新增MK OAuth认证模式支持

This commit is contained in:
黄斌峰
2026-08-07 17:55:41 +08:00
parent deb4029f01
commit 6a965e1618
12 changed files with 288 additions and 3 deletions
@@ -77,4 +77,8 @@ public interface OAuth2GranterConstant {
*/
String REGISTER = "register";
/**
* mk oauth模式
*/
String MK = "mk";
}
@@ -0,0 +1,134 @@
package org.springblade.core.oauth2.endpoint;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.oauth2.constant.OAuth2TokenConstant;
import org.springblade.core.oauth2.exception.ExceptionCode;
import org.springblade.core.oauth2.granter.TokenGranter;
import org.springblade.core.oauth2.granter.TokenGranterFactory;
import org.springblade.core.oauth2.handler.TokenHandler;
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.oauth2.utils.OAuth2ExceptionUtil;
import org.springblade.core.tool.support.Kv;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import static org.springblade.core.oauth2.constant.OAuth2GranterConstant.MK;
import static org.springblade.core.oauth2.constant.OAuth2ParameterConstant.USERNAME;
/**
* mk oauth认证端点
* @author bfhuange
* @date 2024/9/13
*/
@Slf4j
@RequiredArgsConstructor
@Tag(name = "跳转mk认证", description = "跳转mk认证端点")
public abstract class AbstractOAuth2MKEndpoint {
private final TokenGranterFactory granterFactory;
private final TokenHandler tokenHandler;
/**
* 返回登录页面
* @param request
* @return
*/
@GetMapping("/oauth/mk/login")
public ResponseEntity<String> login(HttpServletRequest request, HttpServletResponse response) {
String referer = request.getHeader("referer");
if (!checkRefererUrl(referer)) {
// 校验来源地址
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body("非法来源地址");
}
// 返回登录页面
return ResponseEntity.ok(generateLoginUrl(referer));
}
/**
* 通过mk授权码换取本系统token
* @param mkCode
* @return
*/
@GetMapping("/oauth/mk/token")
public ResponseEntity<Kv> token(String mkCode) {
log.info("mk 登录回调 mkCode{}", mkCode);
if (!StringUtils.hasText(mkCode)) {
// 根据无效的mk code的错误代码抛出异常
OAuth2ExceptionUtil.throwFromCode(ExceptionCode.INVALID_MK_CODE.getCode());
}
// 将mk code 转换成erp的账号/手机号
String account = getAccountByMkCode(mkCode);
if (account == null) {
OAuth2ExceptionUtil.throwFromCode(ExceptionCode.MK_ACCOUNT_NOT_FIND.getCode());
}
// 创建 OAuth2 请求对象并构建参数
OAuth2Request request = OAuth2Request.create();
// 默认管理组
request.setTenantId(OAuth2TokenConstant.DEFAULT_TENANT_ID);
// 只设置账号
request.getParameterArgs().set(USERNAME, account);
// 根据MK oauth的授权类型创建对应的 TokenGranter
TokenGranter tokenGranter = granterFactory.create(MK);
// 使用 TokenGranter 获取用户信息
OAuth2User user = tokenGranter.user(request);
// 创建令牌
OAuth2Token token = tokenGranter.token(user, request);
// 对令牌进行增强处理
OAuth2Token enhanceToken = tokenHandler.enhance(user, token, request);
// 返回增强后的令牌
return ResponseEntity.ok(enhanceToken.getArgs());
}
/**
* 输出消息
* @param response
* @param message
*/
protected void writeMessage(HttpServletResponse response, String message) {
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
try(PrintWriter writer = response.getWriter()) {
log.warn(message);
writer.println(message);
} catch (IOException e) {
log.error(message + "异常", e);
}
}
/**
* 生成登录地址
* @param refererUrl
* @return
*/
protected abstract String generateLoginUrl(String refererUrl);
/**
* 根据mk授权码获取账号
* @param mkCode
* @return
*/
protected abstract String getAccountByMkCode(String mkCode);
/**
* 校验来源url,返回false,不跳转登录页面
* @param refererUrl
* @return
*/
protected abstract boolean checkRefererUrl(String refererUrl);
}
@@ -74,6 +74,11 @@ public enum ExceptionCode implements Oauth2ExceptionCode {
*/
UNAUTHORIZED_USER(OAuth2ErrorCode.UNAUTHORIZED_USER, "认证信息错误或无效"),
/**
* 用户未授权 - 账号或密码不正确。
*/
ACCOUNT_OR_PASSWORD_ERROR(OAuth2ErrorCode.ACCOUNT_OR_PASSWORD_ERROR, "账号或密码不正确"),
/**
* 用户租户未授权 - 指定的用户租户未授权。
*/
@@ -84,6 +89,21 @@ public enum ExceptionCode implements Oauth2ExceptionCode {
*/
INVALID_REFRESH_TOKEN(OAuth2ErrorCode.INVALID_REFRESH_TOKEN, "令牌刷新错误或无效"),
/**
* 用户未授权 - 无效的mk code
*/
INVALID_MK_CODE(OAuth2ErrorCode.INVALID_MK_CODE, "无效的mk code"),
/**
* 用户未授权 - 获取mk账号错误
*/
MK_ACCOUNT_NOT_FIND(OAuth2ErrorCode.MK_ACCOUNT_FETCH_ERROR, "获取MK账号错误"),
/**
* 用户未授权 - mk账号未同步本系统
*/
MK_ACCOUNT_NOT_SYNC_SYSTEM(OAuth2ErrorCode.MK_ACCOUNT_NOT_SYNC_SYSTEM, "MK账号【%s】未同步到本系统"),
/**
* 客户端不存在 - 指定的客户端ID不存在或无效。
*/
@@ -59,10 +59,26 @@ public interface OAuth2ErrorCode {
* 用户租户未授权 - 指定的用户租户未授权。
*/
int UNAUTHORIZED_USER_TENANT = 2006;
/**
* 账号或密码不正确
*/
int ACCOUNT_OR_PASSWORD_ERROR = 2007;
/**
* 令牌刷新错误或无效 - 刷新令牌认证信息错误或无效。
*/
int INVALID_REFRESH_TOKEN = 2010;
/**
* 无效的mk code
*/
int INVALID_MK_CODE = 2020;
/**
* 获取mk账号错误
*/
int MK_ACCOUNT_FETCH_ERROR = 2021;
/**
* mk账号未同步本系统
*/
int MK_ACCOUNT_NOT_SYNC_SYSTEM = 2022;
/**
* 客户端不存在 - 指定的客户端ID不存在或无效。
*/
@@ -25,6 +25,7 @@
*/
package org.springblade.core.oauth2.granter;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.launch.constant.TokenConstant;
@@ -66,8 +67,9 @@ public abstract class AbstractTokenGranter implements TokenGranter {
* 认证失败原因:密码匹配失败
*/
private static final String REASON_PASSWORD_MISMATCH = "密码匹配失败";
@Getter
private final OAuth2ClientService clientService;
@Getter
private final OAuth2UserService userService;
private final PasswordHandler passwordHandler;
@@ -0,0 +1,81 @@
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.OAuth2Exception;
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.OAuth2ExceptionUtil;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.Optional;
/**
* PasswordTokenGranter
*
* @author BladeX
*/
@Component
public class MKTokenGranter extends AbstractTokenGranter {
/**
* MK client id
*/
private static final String MK_CLIENT_ID = "mk-oauth";
public MKTokenGranter(OAuth2ClientService clientService, OAuth2UserService userService, PasswordHandler passwordHandler) {
super(clientService, userService, passwordHandler);
}
@Override
public String type() {
return MK;
}
@Override
public OAuth2User user(OAuth2Request request) {
OAuth2User user = this.getUser(request);
return Optional.ofNullable(this.enhancer)
.map(enhancer -> enhancer.enhance(user, request))
.orElse(user);
}
@Override
public OAuth2Client client(OAuth2Request request) {
// 直接使用固定的client id
return getClientService().loadByClientId(MK_CLIENT_ID);
}
private OAuth2User getUser(OAuth2Request request) {
// 获取用户信息
OAuth2User user = getUserService().loadByUsername(request.getUsername(), request);
// 用户不存在
if (user == null) {
String message = String.format(ExceptionCode.MK_ACCOUNT_NOT_SYNC_SYSTEM.getMessage(), request.getUsername());
throw new OAuth2Exception(ExceptionCode.MK_ACCOUNT_NOT_SYNC_SYSTEM, message);
}
// 校验用户信息
if (!getUserService().validateUser(Objects.requireNonNull(user))) {
OAuth2ExceptionUtil.throwFromCode(OAuth2ErrorCode.INVALID_USER);
}
// 无需校验用户密码
//MK登进来检查是否是默认密码
//MK登录跳过强制修改密码
// user.setDefaultPwdFlag(false);
// 设置客户端信息
user.setClient(client(request));
// 返回用户信息
return user;
}
}
@@ -115,6 +115,7 @@ public class TokenGranterFactory {
case WECHAT_APPLET -> properties.getGranter().getWechatApplet();
case SOCIAL -> properties.getGranter().getSocial();
case REGISTER -> properties.getGranter().getRegister();
case MK -> properties.getGranter().getMk();
default -> true;
};
}
@@ -76,7 +76,7 @@ public abstract class AbstractAuthorizationHandler implements AuthorizationHandl
*/
@Override
public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
if (request.isClientCredentials() || request.isImplicit() || request.isSocial()) {
if (request.isClientCredentials() || request.isImplicit() || request.isSocial() || request.isMk()) {
return new OAuth2Validation();
}
if (Func.hasEmpty(user, user.getUserId())) {
@@ -61,7 +61,7 @@ public class OAuth2AuthorizationHandler extends AbstractAuthorizationHandler {
*/
@Override
public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
log.error("用户:{},认证失败,失败原因:{}", user.getAccount(), validation.getMessage());
}
}
@@ -65,6 +65,11 @@ public class OAuth2Properties {
*/
private String privateKey;
/**
* sm4密钥
*/
private String sm4SecretKey;
/**
* 授权模式
*/
@@ -117,6 +122,10 @@ public class OAuth2Properties {
* 是否开启注册模式
*/
private Boolean register = true;
/**
* 是否开启 mk oauth模式
*/
private Boolean mk = true;
}
}
@@ -410,6 +410,14 @@ public class OAuth2Request {
return SOCIAL.equals(getGrantType());
}
/**
* 是否mk oauth模式
* @return
*/
public Boolean isMk() {
return MK.equals(getGrantType());
}
/**
* 设置租户ID
*
@@ -56,6 +56,9 @@ public class OAuth2ExceptionUtil {
OAUTH2_EXCEPTION.put(
ExceptionCode.INVALID_USER, () -> new UserInvalidException(ExceptionCode.INVALID_USER.getMessage(), new Throwable())
);
OAUTH2_EXCEPTION.put(
ExceptionCode.ACCOUNT_OR_PASSWORD_ERROR, () -> new UserInvalidException(ExceptionCode.ACCOUNT_OR_PASSWORD_ERROR.getMessage())
);
OAUTH2_EXCEPTION.put(
ExceptionCode.UNAUTHORIZED_USER, () -> new UserUnauthorizedException(ExceptionCode.UNAUTHORIZED_USER.getMessage(), new Throwable())
);
@@ -95,6 +98,13 @@ public class OAuth2ExceptionUtil {
OAUTH2_EXCEPTION.put(
ExceptionCode.TEMPORARILY_UNAVAILABLE, () -> new OAuth2Exception(ExceptionCode.TEMPORARILY_UNAVAILABLE, ExceptionCode.TEMPORARILY_UNAVAILABLE.getMessage(), new Throwable())
);
put(ExceptionCode.INVALID_MK_CODE);
put(ExceptionCode.MK_ACCOUNT_NOT_FIND);
put(ExceptionCode.MK_ACCOUNT_NOT_SYNC_SYSTEM);
}
private static void put(ExceptionCode exceptionCode) {
OAUTH2_EXCEPTION.put(exceptionCode, () -> new OAuth2Exception(exceptionCode, exceptionCode.getMessage()));
}
/**