|
|
@@ -4,75 +4,134 @@ import com.alibaba.fastjson.JSON;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.skyversation.xjcy.util.HttpUtil;
|
|
|
import com.skyversation.xjcy.util.MessageManage;
|
|
|
+import lombok.Getter;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.util.LinkedMultiValueMap;
|
|
|
import org.springframework.util.MultiValueMap;
|
|
|
import org.springframework.util.StringUtils;
|
|
|
|
|
|
+import javax.annotation.PostConstruct;
|
|
|
import java.security.MessageDigest;
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Map;
|
|
|
-import java.util.Objects;
|
|
|
|
|
|
+/**
|
|
|
+ * OAuth认证服务类
|
|
|
+ * 处理用户登录、注册、token管理等认证相关操作
|
|
|
+ */
|
|
|
@Service
|
|
|
public class AuthService {
|
|
|
|
|
|
+ // ============================ 常量定义 ============================
|
|
|
+
|
|
|
+ /** OAuth客户端ID */
|
|
|
+ private static final String CLIENT_ID = "2";
|
|
|
+ /** 成功状态码 */
|
|
|
+ private static final Integer SUCCESS_CODE_INT = 200;
|
|
|
+ /** 密码错误消息 */
|
|
|
+ private static final String MESSAGE_PASSWORD_ERROR = "密码错误";
|
|
|
+ /** 用户不存在消息 */
|
|
|
+ private static final String MESSAGE_USER_NOT_EXIST = "用户不存在";
|
|
|
+
|
|
|
+ /** 用户登录接口路径 */
|
|
|
+ private static final String API_USER_LOGIN = "/api/user/login";
|
|
|
+ /** 用户注册接口路径 */
|
|
|
+ private static final String USER_REGISTER = "/user/register";
|
|
|
+ /** Token验证接口路径 */
|
|
|
+ private static final String USER_VALIDATE_TOKEN = "/user/validateToken";
|
|
|
+
|
|
|
+ /** 微信账号用户名前缀 */
|
|
|
+ private static final String WX_NAME_PREFIX = "#wx";
|
|
|
+ /** 微信账号密码前缀 */
|
|
|
+ private static final String WX_PASSWORD_PREFIX = "Wx@";
|
|
|
+ /** 微信账号加密盐值 */
|
|
|
+ private static final String WX_SALT = "chatWe";
|
|
|
+ /** 手机账号用户名前缀 */
|
|
|
+ private static final String PHONE_NAME_PREFIX = "#phone";
|
|
|
+ /** 手机账号密码前缀 */
|
|
|
+ private static final String PHONE_PASSWORD_PREFIX = "Phone@";
|
|
|
+ /** 手机账号加密盐值 */
|
|
|
+ private static final String PHONE_SALT = "enohp";
|
|
|
+
|
|
|
+ /** 响应字段:状态码 */
|
|
|
+ private static final String RESPONSE_FIELD_CODE = "code";
|
|
|
+ /** 响应字段:消息 */
|
|
|
+ private static final String RESPONSE_FIELD_MESSAGE = "message";
|
|
|
+ /** 响应字段:内容数据 */
|
|
|
+ private static final String RESPONSE_FIELD_CONTENT = "content";
|
|
|
+ /** 响应字段:用户ID */
|
|
|
+ private static final String RESPONSE_FIELD_ID = "id";
|
|
|
+
|
|
|
+
|
|
|
+ // ============================ 内部类定义 ============================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户账户信息类
|
|
|
+ */
|
|
|
+ @Getter
|
|
|
+ public final static class Account {
|
|
|
+ private final String username;
|
|
|
+ private final String password;
|
|
|
+
|
|
|
+ public Account(String username, String password) {
|
|
|
+ if (username == null || password == null || username.isEmpty() || password.isEmpty()) {
|
|
|
+ throw new IllegalArgumentException("缺失必要参数");
|
|
|
+ }
|
|
|
+ this.username = username;
|
|
|
+ this.password = password;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================ 实例变量 ============================
|
|
|
+
|
|
|
+ /** 微信认证服务 */
|
|
|
private final WxAuthService wxAuthService;
|
|
|
+
|
|
|
+ private final PhoneService phoneService;
|
|
|
+ /** 服务账户用户名 */
|
|
|
@Value("${app.oauth.login-name}")
|
|
|
private String loginName;
|
|
|
+
|
|
|
+ /** 服务账户密码 */
|
|
|
@Value("${app.oauth.password}")
|
|
|
private String password;
|
|
|
+
|
|
|
+ /** 服务账户信息 */
|
|
|
+ private Account account;
|
|
|
+
|
|
|
+ /** OAuth服务路径 */
|
|
|
@Value("${app.oauth.path}")
|
|
|
private String oauthPath = null;
|
|
|
|
|
|
- private String cacheToken;
|
|
|
+ /** Token缓存,使用volatile确保多线程可见性 */
|
|
|
+ private volatile String cacheToken;
|
|
|
|
|
|
+ // ============================ 构造方法 ============================
|
|
|
|
|
|
- public AuthService(WxAuthService wxAuthService) {
|
|
|
+ public AuthService(WxAuthService wxAuthService, PhoneService phoneService) {
|
|
|
this.wxAuthService = wxAuthService;
|
|
|
+ this.phoneService = phoneService;
|
|
|
}
|
|
|
|
|
|
- private String loginForToken(String loginName, String password) throws RuntimeException {
|
|
|
- JSONObject jsonObject = login(loginName, password);
|
|
|
- if ("密码错误".equals(jsonObject.getString("message"))) {
|
|
|
- throw new RuntimeException("请检查使用的账户和密码是否正确");
|
|
|
- }
|
|
|
- if (!Objects.equals(jsonObject.getString("code"), "200")) {
|
|
|
- throw new RuntimeException(jsonObject.getString("message"));
|
|
|
- }
|
|
|
- return jsonObject.getString("message");
|
|
|
- }
|
|
|
+ // ============================ 初始化方法 ============================
|
|
|
|
|
|
- private JSONObject login(String loginName, String password) {
|
|
|
+ /**
|
|
|
+ * 初始化方法 - 验证配置并创建服务账户
|
|
|
+ */
|
|
|
+ @PostConstruct
|
|
|
+ private void init() {
|
|
|
if (!StringUtils.hasText(loginName) || !StringUtils.hasText(password)) {
|
|
|
- throw new RuntimeException("请提供用于访问DMS的账号密码");
|
|
|
+ throw new IllegalStateException("OAuth配置不完整: login-name和password必须配置");
|
|
|
}
|
|
|
- MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
|
|
|
- params.add("clientId", "2");
|
|
|
- params.add("userName", loginName);
|
|
|
- params.add("password", password);
|
|
|
- String response = HttpUtil.requestPost(oauthPath + "/api/user/login", params, new HashMap<>());
|
|
|
- return JSON.parseObject(response);
|
|
|
- }
|
|
|
-
|
|
|
- private JSONObject register(String loginName, String password) {
|
|
|
- if (!StringUtils.hasText(loginName) || !StringUtils.hasText(password)) {
|
|
|
- throw new RuntimeException("请提供用于访问DMS的账号密码");
|
|
|
+ if (!StringUtils.hasText(oauthPath)) {
|
|
|
+ throw new IllegalStateException("OAuth配置不完整: oauth.path必须配置");
|
|
|
}
|
|
|
- MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
|
|
|
- params.add("username", loginName);
|
|
|
- params.add("password", password);
|
|
|
- Map<String, String> header = new HashMap<>();
|
|
|
- header.put("token", getTokenOfServiceAccount());
|
|
|
- String response = HttpUtil.requestPost(oauthPath + "/user/register", params, header);
|
|
|
- return JSON.parseObject(response);
|
|
|
+ account = new Account(loginName, password);
|
|
|
}
|
|
|
|
|
|
- private void initUser(int userId) {
|
|
|
- //TODO 初始化用户
|
|
|
- }
|
|
|
+ // ============================ 公共方法 ============================
|
|
|
|
|
|
/**
|
|
|
* 检查token有效性
|
|
|
@@ -84,29 +143,34 @@ public class AuthService {
|
|
|
public boolean checkToken(String token) {
|
|
|
Map<String, String> header = new HashMap<>();
|
|
|
header.put("token", token);
|
|
|
- String response = HttpUtil.requestGet(oauthPath + "/user/validateToken", null, header);
|
|
|
+ String response = HttpUtil.requestGet(oauthPath + USER_VALIDATE_TOKEN, null, header);
|
|
|
JSONObject jsonObject = JSON.parseObject(response);
|
|
|
- Integer code = jsonObject.getInteger("code");
|
|
|
- return Integer.valueOf(200).equals(code);
|
|
|
+ return isSuccess(jsonObject);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取服务器内置的账户token
|
|
|
+ * 使用双重检查锁定确保线程安全
|
|
|
*
|
|
|
* @return 服务器内置账户的token
|
|
|
* @throws RuntimeException 内置账户信息异常
|
|
|
*/
|
|
|
public String getTokenOfServiceAccount() throws RuntimeException {
|
|
|
+ // 双重检查锁定,确保线程安全
|
|
|
if (!StringUtils.hasText(cacheToken) || !checkToken(cacheToken)) {
|
|
|
- cacheToken = loginForToken(this.loginName, this.password);
|
|
|
+ synchronized (this) {
|
|
|
+ if (!StringUtils.hasText(cacheToken) || !checkToken(cacheToken)) {
|
|
|
+ cacheToken = loginForToken(account);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
return cacheToken;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 通过wx的一键登录登录到oauth。
|
|
|
- * 实际上每个wx用户都会被分配一个oauth账户,此账户以用户名与wx的openid建立连接。
|
|
|
- * 逻辑上这个方法会尝试登录,无法登录时尝试建立oauth账户
|
|
|
+ * 通过微信一键登录登录到OAuth系统
|
|
|
+ * 每个微信用户都会被分配一个OAuth账户,用户名与微信openid关联
|
|
|
+ * 逻辑:尝试登录,无法登录时尝试建立OAuth账户
|
|
|
*
|
|
|
* @param wxCode wx.login()方法提供的一键登录用code
|
|
|
* @return 完整的response.body
|
|
|
@@ -114,47 +178,158 @@ public class AuthService {
|
|
|
public String logOrRegWxAccount(String wxCode) {
|
|
|
WxAuthService.Result wxResult = wxAuthService.wxLogin(wxCode);
|
|
|
if (wxResult.isSuccess()) {
|
|
|
- //生成期望的绑定账户和密码
|
|
|
- String oauthAccount = "#wx" + wxResult.getWxOpenId();
|
|
|
- String oauthPassword = "Wx@" + encrypt(wxResult.getWxOpenId() + "chatWe");
|
|
|
-
|
|
|
- //尝试登录
|
|
|
- JSONObject result = login(oauthAccount, oauthPassword);
|
|
|
- String message = result.getString("message");
|
|
|
- Integer code = result.getInteger("code");
|
|
|
- if (Integer.valueOf(200).equals(code)) {
|
|
|
- return message;
|
|
|
- } else {
|
|
|
- //登录失败
|
|
|
- if ("用户不存在".equals(message)) {
|
|
|
- //尝试注册
|
|
|
- JSONObject regResult = register(oauthAccount, oauthPassword);
|
|
|
- if (!Integer.valueOf(200).equals(regResult.getInteger("code"))) {
|
|
|
- return MessageManage.getInstance().getResultContent(-1, "注册失败,未知异常", "注册失败,未知异常");
|
|
|
- }
|
|
|
- JSONObject userUnInitResult = login(oauthAccount, oauthPassword);
|
|
|
- if (!Integer.valueOf(200).equals(userUnInitResult.getInteger("code"))) {
|
|
|
- return MessageManage.getInstance().getResultContent(-1, "注册失败,未知异常", "注册失败,未知异常");
|
|
|
- }
|
|
|
- Integer userId = userUnInitResult.getJSONObject("content").getInteger("id");
|
|
|
- if (userId != null) {
|
|
|
- initUser(userId);
|
|
|
- }
|
|
|
- return login(oauthAccount, oauthPassword).toJSONString();
|
|
|
- } else {
|
|
|
- return MessageManage.getInstance().getResultContent(-1, "未知异常", "未知异常");
|
|
|
- }
|
|
|
- }
|
|
|
+ return logOrReg(generateAccount(wxResult.getWxOpenId(), WX_NAME_PREFIX, WX_PASSWORD_PREFIX, WX_SALT));
|
|
|
} else {
|
|
|
return MessageManage.getInstance().getResultContent(-1, wxResult.getError(), wxResult.getError());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 通过验证码策略一键登录登录到OAuth系统
|
|
|
+ * 每个用户都会被分配一个OAuth账户,用户名与手机号关联
|
|
|
+ * 逻辑:尝试登录,无法登录时尝试建立OAuth账户
|
|
|
+ *
|
|
|
+ * @param code phoneService提供的验证码
|
|
|
+ * @return 完整的response.body
|
|
|
+ */
|
|
|
+ public String logOrRegPhoneAccount(String phone,String code) {
|
|
|
+ PhoneService.Result result = phoneService.login(phone,code);
|
|
|
+ if (result.isSuccess()) {
|
|
|
+ return logOrReg(generateAccount(result.getPhone(), PHONE_NAME_PREFIX, PHONE_PASSWORD_PREFIX, PHONE_SALT));
|
|
|
+ } else {
|
|
|
+ return MessageManage.getInstance().getResultContent(-1, result.getError(), result.getError());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public boolean sendPhoneCode(String phone) {
|
|
|
+ return phoneService.sendCode(phone);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ============================ 私有方法 ============================
|
|
|
|
|
|
/**
|
|
|
- * 简单sha-256加密,不加盐
|
|
|
+ * 登录并获取token
|
|
|
*/
|
|
|
- public static String encrypt(String input) {
|
|
|
+ private String loginForToken(Account account) throws RuntimeException {
|
|
|
+ JSONObject jsonObject = login(account);
|
|
|
+ if (MESSAGE_PASSWORD_ERROR.equals(jsonObject.getString(RESPONSE_FIELD_MESSAGE))) {
|
|
|
+ throw new RuntimeException("请检查使用的账户和密码是否正确");
|
|
|
+ }
|
|
|
+ if (!isSuccess(jsonObject)) {
|
|
|
+ throw new RuntimeException(jsonObject.getString(RESPONSE_FIELD_MESSAGE));
|
|
|
+ }
|
|
|
+ return jsonObject.getString(RESPONSE_FIELD_MESSAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户登录
|
|
|
+ */
|
|
|
+ private JSONObject login(Account account) {
|
|
|
+ MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
|
|
|
+ params.add("clientId", CLIENT_ID);
|
|
|
+ params.add("userName", account.getUsername());
|
|
|
+ params.add("password", account.getPassword());
|
|
|
+ String response = HttpUtil.requestPost(oauthPath + API_USER_LOGIN, params, new HashMap<>());
|
|
|
+ return JSON.parseObject(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 用户注册
|
|
|
+ */
|
|
|
+ private JSONObject register(Account account) throws RuntimeException {
|
|
|
+ MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
|
|
|
+ params.add("username", account.getUsername());
|
|
|
+ params.add("password", account.getPassword());
|
|
|
+ Map<String, String> header = new HashMap<>();
|
|
|
+ header.put("token", getTokenOfServiceAccount());
|
|
|
+ String response = HttpUtil.requestPost(oauthPath + USER_REGISTER, params, header);
|
|
|
+ return JSON.parseObject(response);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化用户数据
|
|
|
+ */
|
|
|
+ private boolean initUser(int userId) {
|
|
|
+ //TODO 初始化用户
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 登录或注册流程
|
|
|
+ */
|
|
|
+ private String logOrReg(Account account) {
|
|
|
+ // 尝试登录
|
|
|
+ JSONObject result = login(account);
|
|
|
+ String message = result.getString(RESPONSE_FIELD_MESSAGE);
|
|
|
+ boolean success = isSuccess(result);
|
|
|
+
|
|
|
+ if (success) {
|
|
|
+ return result.toJSONString();
|
|
|
+ } else {
|
|
|
+ // 登录失败处理
|
|
|
+ if (MESSAGE_USER_NOT_EXIST.equals(message)) {
|
|
|
+ boolean regSuccess = tryRegAndInit(account);
|
|
|
+ if (!regSuccess) {
|
|
|
+ return MessageManage.getInstance().getResultContent(-1, "注册失败,未知异常", "注册失败,未知异常");
|
|
|
+ }
|
|
|
+ return login(account).toJSONString();
|
|
|
+ } else {
|
|
|
+ return MessageManage.getInstance().getResultContent(-1, "未知异常", "未知异常");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 尝试注册并初始化用户
|
|
|
+ */
|
|
|
+ private boolean tryRegAndInit(Account account) {
|
|
|
+ // 尝试注册
|
|
|
+ JSONObject regResult = register(account);
|
|
|
+ if (!isSuccess(regResult)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONObject userUnInitResult = login(account);
|
|
|
+ if (!isSuccess(userUnInitResult)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer userId = userUnInitResult.getJSONObject(RESPONSE_FIELD_CONTENT).getInteger(RESPONSE_FIELD_ID);
|
|
|
+ if (userId != null) {
|
|
|
+ return initUser(userId);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查响应是否成功
|
|
|
+ */
|
|
|
+ private static boolean isSuccess(JSONObject result) {
|
|
|
+ if (result == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ Integer code = result.getInteger(RESPONSE_FIELD_CODE);
|
|
|
+ return SUCCESS_CODE_INT.equals(code);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成账户信息
|
|
|
+ */
|
|
|
+ private static Account generateAccount(String uniCode, String namePrefix, String passwordPrefix, String salt) {
|
|
|
+ String oauthAccount = namePrefix + uniCode;
|
|
|
+ String oauthPassword = passwordPrefix + encrypt(uniCode + salt);
|
|
|
+ return new Account(oauthAccount, oauthPassword);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * SHA-256加密方法
|
|
|
+ *
|
|
|
+ * @param input 待加密字符串
|
|
|
+ * @return 加密后的十六进制字符串
|
|
|
+ */
|
|
|
+ private static String encrypt(String input) {
|
|
|
try {
|
|
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
|
byte[] hash = digest.digest(input.getBytes());
|
|
|
@@ -174,5 +349,4 @@ public class AuthService {
|
|
|
throw new RuntimeException(e);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-}
|
|
|
+}
|