You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

260 lines
26 KiB

diff --git a/README.md b/README.md
index 91ef8ea..66632ef 100644
--- a/README.md
+++ b/README.md
@@ -135,7 +135,7 @@ crm:
| 405xx | 璇锋眰鏂瑰紡閿欒 | base |
| 500xx | 绯荤粺/澶栭儴璋冪敤閿欒 | base |
| 60001 | 閫氱敤涓氬姟閿欒 | base |
-| 61001~61999 | 璁よ瘉妯″潡锛?1001 涓嶆敮鎸佺殑鐧诲綍鏂瑰紡 / 61002 涓夋柟鎺堟潈澶辫触 / 61003 璐﹀彿绂佺敤 / 61004 閮ㄩ棬鍒嗛厤鍙傛暟闈炴硶 / 61005 閮ㄩ棬涓嶅瓨鍦?/ 61006 閮ㄩ棬涓嬩粛鏈夋垚鍛樻嫆鍒?/ 61007 閮ㄩ棬绉诲姩闈炴硶闃茬幆锛?| crm-auth |
+| 61001~61999 | 璁よ瘉妯″潡锛?1001 涓嶆敮鎸佺殑鐧诲綍鏂瑰紡 / 61002 涓夋柟鎺堟潈澶辫触 / 61003 璐﹀彿绂佺敤 / 61004 閮ㄩ棬鍒嗛厤鍙傛暟闈炴硶 / 61005 閮ㄩ棬涓嶅瓨鍦?/ 61006 閮ㄩ棬涓嬩粛鏈夋垚鍛樻嫆鍒?/ 61007 閮ㄩ棬绉诲姩闈炴硶闃茬幆 / 61008 闈炴湰缁勭粐鎴愬憳 / 61010 瑙掕壊鍙傛暟闈炴硶 / 61011 瑙掕壊缂栫爜宸插瓨鍦?/ 61012 鍐呯疆瑙掕壊淇濇姢 / 61013 璇ヨ处鍙峰凡鍦ㄥ叾浠栬澶囧湪绾匡紙甯?data 鍥炰紶 preLoginToken锛?/ 61014 鐧诲綍纭宸插け鏁?/ 61015 琚《涓嬬嚎 / 61016 涓嶆敮鎸佺殑绔被鍨嬶級 | crm-auth |
| 62001~62999 | 鏂囦欢妯″潡锛?2001 澶у皬瓒呴檺 / 62002 绫诲瀷绂佹 / 62003 涓婁紶浼氳瘽涓嶅瓨鍦?/ 62004 鍒嗙墖涓嶅畬鏁?/ 62005 棰勮鏈嶅姟涓嶅彲鐢?/ 62006 鍒嗙墖澶у皬閰嶇疆閿欒锛?| crm-file |
| 63001~63999 | 鏁版嵁瀛楀吀锛?3001 鍙傛暟闈炴硶 / 63002 鍒嗙粍缂栫爜宸插瓨鍦?/ 63003 鍐呯疆瀛楀吀淇濇姢 / 63004 鍒嗙粍涓嬩粛鏈夊瓧鍏搁」 / 63005 鍒嗙粍涓嶅瓨鍦ㄦ垨宸插仠鐢?/ 63006 瀛楀吀缂栫爜缁勫唴閲嶅 / 63007 瀛楀吀鍊煎悓缁勯噸澶?/ 63008 韬唤瀛楁鍙 / 63009 瀛楀吀椤硅寮曠敤 / 63010 瀛楀吀椤逛笉瀛樺湪 / 63011 鍒嗙粍涓嶅瓨鍦?/ 63012 浠呭惎鐢ㄩ」鍙榛樿锛?| crm-dict |
| 64001~64999 | 鍏捣姹犻厤缃紙64001 鍙傛暟闈炴硶 / 64002 姹犲悕閲嶅 / 64003 閮ㄩ棬宸叉湁鍏捣姹?/ 64004 姹犱笉瀛樺湪 / 64005 姹犲唴浠嶆湁娲昏穬绾跨储鎷掑垹 / 64006 姹犳垚鍛橀潪娉?/ 64007 鍖哄煙涓嶅瓨鍦級 | crm-rule |
diff --git a/crm-app/src/main/resources/application.yml b/crm-app/src/main/resources/application.yml
index d2a8d7f..1cedbda 100644
--- a/crm-app/src/main/resources/application.yml
+++ b/crm-app/src/main/resources/application.yml
@@ -49,6 +49,7 @@ crm:
dingtalk:
client-id: "dingikqv3al2koirnku7"
client-secret: "VVlS2GaBdibboTi3l80wvXufTaYxlCHWaLDtJKd9APT37td2II-cosFE_-WkVGIU"
+ prelogin-ttl-seconds: 120 # 椤跺彿纭绁?preLoginToken 鏈夋晥鏈燂紙绉掞級锛岄粯璁?2 鍒嗛挓
file:
minio:
endpoint: ${CRM_MINIO_ENDPOINT:http://127.0.0.1:9000} # 鍐呯綉鍦板潃锛屾祻瑙堝櫒姘镐笉鐩存帴鎺ヨЕ MinIO锛坈ompose 鎸囧悜瀹瑰櫒鏈嶅姟鍚?minio:9000锛?diff --git a/crm-auth/src/main/java/com/crm/auth/config/AuthProperties.java b/crm-auth/src/main/java/com/crm/auth/config/AuthProperties.java
index 11e03c7..734fa36 100644
--- a/crm-auth/src/main/java/com/crm/auth/config/AuthProperties.java
+++ b/crm-auth/src/main/java/com/crm/auth/config/AuthProperties.java
@@ -20,6 +20,7 @@ import java.util.List;
* dingtalk:
* client-id: dingxxxxxx # 閽夐拤寮€鏀惧钩鍙板簲鐢ㄧ殑 Client ID (鍘?AppKey)
* client-secret: xxxxxx # Client Secret (鍘?AppSecret)
+ * prelogin-ttl-seconds: 120 # 椤跺彿纭绁?preLoginToken 鏈夋晥鏈燂紙绉掞級
* </pre>
*/
@Data
@@ -32,6 +33,9 @@ public class AuthProperties {
/** 棰濆鏀捐鐨?URL锛堝湪 SecurityConfig 榛樿鐧藉悕鍗曞熀纭€涓婅拷鍔狅級 */
private List<String> ignoreUrls = new ArrayList<>();
+ /** 棰勭櫥褰曠エ鏈夋晥鏈燂紙绉掞級锛氫富鐧诲綍妫€娴嬪埌璁惧鍦ㄧ嚎鏃剁鍙戯紝榛樿 2 鍒嗛挓 */
+ private int preloginTtlSeconds = 120;
+
private DingTalk dingtalk = new DingTalk();
@Data
diff --git a/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java b/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java
index 4f4f17c..b57d42e 100644
--- a/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java
+++ b/crm-auth/src/main/java/com/crm/auth/constant/AuthConstants.java
@@ -14,6 +14,18 @@ public interface AuthConstants {
/** 鐧诲綍鎬佸湪 Redis 涓殑 key 鍓嶇紑锛屽畬鏁?key锛歝rm:auth:token:{jti} */
String LOGIN_TOKEN_KEY = "crm:auth:token:";
+ /** 浼氳瘽鍙嶅悜绱㈠紩 key 鍓嶇紑锛屽畬鏁?key锛歝rm:auth:session:{userId}:{clientType}锛坴alue=褰撳墠璇ョ jti锛?*/
+ String SESSION_INDEX_KEY = "crm:auth:session:";
+
+ /** 棰勭櫥褰曠エ key 鍓嶇紑锛屽畬鏁?key锛歝rm:auth:prelogin:{preLoginToken}锛堢‘璁ら《鍙风敤锛孏ETDEL 涓€娆℃€ф秷璐癸級 */
+ String PRELOGIN_TOKEN_KEY = "crm:auth:prelogin:";
+
+ /** 琚《涓嬬嚎鏍囪 key 鍓嶇紑锛屽畬鏁?key锛歝rm:auth:kicked:{jti}锛堟棫璁惧涓嬫璇锋眰鎹璇嗗埆琚《锛?*/
+ String KICKED_MARK_KEY = "crm:auth:kicked:";
+
+ /** request attribute锛氳椤朵笅绾挎爣璁帮紙JwtAuthenticationFilter 鍩嬨€丼ecurityExceptionHandlers 璇伙紝绂佹榄旀硶瀛楃涓诧級 */
+ String ATTR_KICKED_OFFLINE = "crm.auth.kickedOffline";
+
/** JWT payload锛氱櫥褰曟€佸敮涓€鏍囪瘑 */
String JWT_KEY_JTI = "jti";
@@ -60,4 +72,16 @@ public interface AuthConstants {
/** 鍐呯疆瑙掕壊淇濇姢锛堢姝㈠垹闄ゆ垨鏀圭紪鐮侊級 */
int CODE_BUILTIN_ROLE_PROTECTED = 61012;
+
+ /** 璇ヨ处鍙峰凡鍦ㄥ叾浠栬澶囧湪绾匡紙甯?data=LoginResultDTO 鍥炰紶 preLoginToken锛岀敱鍓嶇鍐崇瓥鏄惁椤跺彿锛?*/
+ int CODE_DEVICE_ONLINE = 61013;
+
+ /** 棰勭櫥褰曠エ鏃犳晥锛堣繃鏈?宸叉秷璐?浼€狅級锛岄渶閲嶆柊鐧诲綍 */
+ int CODE_PRELOGIN_INVALID = 61014;
+
+ /** 琚《涓嬬嚎锛堣处鍙峰凡鍦ㄥ叾浠栬澶囧畬鎴愰《鍙风櫥褰曪級 */
+ int CODE_KICKED_OFFLINE = 61015;
+
+ /** 涓嶆敮鎸佺殑绔被鍨?*/
+ int CODE_UNSUPPORTED_CLIENT_TYPE = 61016;
}
diff --git a/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java b/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java
index 34a50e7..33b375f 100644
--- a/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java
+++ b/crm-auth/src/main/java/com/crm/auth/controller/AuthController.java
@@ -29,8 +29,19 @@ public class AuthController {
@Operation(summary = "閽夐拤鐧诲綍锛堟壂鐮?鍏嶇櫥閫氱敤锛? , tags = "鐧诲綍")
@PostMapping("/login/dingtalk")
- public Result<LoginResultDTO> loginByDingTalk(@RequestParam String authCode) {
- return Result.success(authService.login(IdentityTypeEnum.DINGTALK, authCode));
+ public Result<LoginResultDTO> loginByDingTalk(@RequestParam String authCode,
+ @RequestParam(required = false) String clientType) {
+ LoginResultDTO dto = authService.login(IdentityTypeEnum.DINGTALK, authCode, clientType);
+ // 璁惧鍐茬獊涓嶆姏寮傚父锛氬甫 data 鍥炰紶 preLoginToken锛岀敱鍓嶇寮广€屾槸鍚﹂《鍙枫€嶅喅绛栵紙spec 3.1锛?+ return dto.getStatus() == LoginResultDTO.LoginStatus.SUCCESS
+ ? Result.success(dto)
+ : Result.fail(AuthConstants.CODE_DEVICE_ONLINE, "璇ヨ处鍙峰凡鍦ㄥ叾浠栬澶囧湪绾匡紝鏄惁椤跺彿鐧诲綍", dto);
+ }
+
+ @Operation(summary = "纭椤跺彿鐧诲綍锛堝嚟 preLoginToken 韪㈡帀鏃ц澶囧苟瀹屾垚鐧诲綍锛?, tags = "鐧诲綍")
+ @PostMapping("/login/dingtalk/confirm")
+ public Result<LoginResultDTO> confirmLogin(@RequestParam String preLoginToken) {
+ return Result.success(authService.confirmLogin(preLoginToken));
}
@Operation(summary = "娉ㄩ攢" , tags = "鐧诲綍")
diff --git a/crm-auth/src/main/java/com/crm/auth/domain/dto/LoginResultDTO.java b/crm-auth/src/main/java/com/crm/auth/domain/dto/LoginResultDTO.java
index 432ada4..46b331c 100644
--- a/crm-auth/src/main/java/com/crm/auth/domain/dto/LoginResultDTO.java
+++ b/crm-auth/src/main/java/com/crm/auth/domain/dto/LoginResultDTO.java
@@ -15,9 +15,40 @@ public class LoginResultDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
- @Schema(description = "JWT token锛屽悗缁姹傛斁鍏ヨ姹傚ご锛欰uthorization: Bearer {token}")
+ @Schema(description = "鐧诲綍鐘舵€侊細SUCCESS=鐧诲綍鎴愬姛锛汥EVICE_ONLINE=璇ヨ处鍙峰凡鍦ㄥ叾浠栬澶囧湪绾匡紝寰呭墠绔‘璁ゆ槸鍚﹂《鍙?)
+ private LoginStatus status;
+
+ @Schema(description = "JWT token锛屽悗缁姹傛斁鍏ヨ姹傚ご锛欰uthorization: Bearer {token}锛泂tatus=DEVICE_ONLINE 鏃朵负 null")
private String token;
- @Schema(description = "鐢ㄦ埛淇℃伅")
+ @Schema(description = "鐢ㄦ埛淇℃伅锛泂tatus=DEVICE_ONLINE 鏃朵负 null")
private UserInfoDTO userInfo;
+
+ @Schema(description = "棰勭櫥褰曠エ锛歴tatus=DEVICE_ONLINE 鏃惰繑鍥烇紝鍓嶇纭椤跺彿鍚庡嚟瀹冭皟 confirm 鎺ュ彛锛涙湁鏁堟湡榛樿 2 鍒嗛挓")
+ private String preLoginToken;
+
+ public static LoginResultDTO success(String token, UserInfoDTO userInfo) {
+ LoginResultDTO dto = new LoginResultDTO();
+ dto.setStatus(LoginStatus.SUCCESS);
+ dto.setToken(token);
+ dto.setUserInfo(userInfo);
+ return dto;
+ }
+
+ public static LoginResultDTO deviceOnline(String preLoginToken) {
+ LoginResultDTO dto = new LoginResultDTO();
+ dto.setStatus(LoginStatus.DEVICE_ONLINE);
+ dto.setPreLoginToken(preLoginToken);
+ return dto;
+ }
+
+ /** 鐧诲綍缁撴灉鐘舵€?*/
+ public enum LoginStatus {
+
+ /** 鐧诲綍鎴愬姛锛宼oken + userInfo 鏈夊€?*/
+ SUCCESS,
+
+ /** 璇ヨ处鍙峰凡鍦ㄥ叾浠栬澶囧湪绾匡紝preLoginToken 鏈夊€硷紝寰呭墠绔‘璁ら《鍙?*/
+ DEVICE_ONLINE
+ }
}
diff --git a/crm-auth/src/main/java/com/crm/auth/security/AuthLoginUser.java b/crm-auth/src/main/java/com/crm/auth/security/AuthLoginUser.java
index 822ec55..eb22405 100644
--- a/crm-auth/src/main/java/com/crm/auth/security/AuthLoginUser.java
+++ b/crm-auth/src/main/java/com/crm/auth/security/AuthLoginUser.java
@@ -1,5 +1,6 @@
package com.crm.auth.security;
+import com.crm.auth.domain.enums.ClientTypeEnum;
import com.crm.base.security.LoginUser;
import lombok.Data;
@@ -27,4 +28,7 @@ public class AuthLoginUser implements LoginUser, Serializable {
/** 澶村儚 */
private String avatar;
+
+ /** 绔被鍨嬶細鍗曠鍦ㄧ嚎鍞竴鎬у垽瀹氱淮搴︺€傚彧瀛?Redis 渚э紙token 閿?JSON锛夛紝JWT payload 涓嶅甫锛涘瓨閲忎細璇濆彲鑳戒负 null */
+ private ClientTypeEnum clientType;
}
diff --git a/crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java b/crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java
index e7e6e6e..da699a9 100644
--- a/crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java
+++ b/crm-auth/src/main/java/com/crm/auth/security/JwtAuthenticationFilter.java
@@ -46,6 +46,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
+ // 琚《涓嬬嚎璇嗗埆锛歵oken 宸插け鏁堜絾 kicked 鏍囪浠嶅湪 -> 鍩?attribute锛?+ // 鐢?SecurityExceptionHandlers 杈撳嚭涓撻棬鐨勩€岃椤朵笅绾裤€嶉敊璇爜锛堝尯鍒簬鏅€氳繃鏈燂級
+ if (tokenService.isKickedOffline(token)) {
+ request.setAttribute(AuthConstants.ATTR_KICKED_OFFLINE, Boolean.TRUE);
+ }
log.debug("token 鏍¢獙鏈€氳繃锛岃姹傝矾寰? {}", request.getRequestURI());
}
}
diff --git a/crm-auth/src/main/java/com/crm/auth/security/SecurityExceptionHandlers.java b/crm-auth/src/main/java/com/crm/auth/security/SecurityExceptionHandlers.java
index ee5d883..69b5da3 100644
--- a/crm-auth/src/main/java/com/crm/auth/security/SecurityExceptionHandlers.java
+++ b/crm-auth/src/main/java/com/crm/auth/security/SecurityExceptionHandlers.java
@@ -1,6 +1,7 @@
package com.crm.auth.security;
import cn.hutool.json.JSONUtil;
+import com.crm.auth.constant.AuthConstants;
import com.crm.base.domain.result.Result;
import com.crm.base.domain.result.ResultCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
@@ -28,6 +29,13 @@ public class SecurityExceptionHandlers implements AuthenticationEntryPoint, Acce
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
+ // 琚《涓嬬嚎锛坮equest attribute 鐢?JwtAuthenticationFilter 鍩嬶級锛氫笓闂ㄩ敊璇爜锛屽墠绔脊銆屽凡鍦ㄥ叾浠栬澶囩櫥褰曘€嶏紱
+ // HTTP 浠?401锛屽鐢ㄥ墠绔幇鏈夌殑 401 璺崇櫥褰曢€昏緫
+ if (Boolean.TRUE.equals(request.getAttribute(AuthConstants.ATTR_KICKED_OFFLINE))) {
+ write(response, HttpServletResponse.SC_UNAUTHORIZED,
+ Result.fail(AuthConstants.CODE_KICKED_OFFLINE, "鎮ㄧ殑璐﹀彿宸插湪鍏朵粬璁惧鐧诲綍锛屾偍宸蹭笅绾?));
+ return;
+ }
write(response, HttpServletResponse.SC_UNAUTHORIZED,
Result.fail(ResultCodeEnum.USER_NOT_FIND, "鏈櫥褰曟垨鐧诲綍宸茶繃鏈燂紝璇烽噸鏂扮櫥褰?));
}
diff --git a/crm-auth/src/main/java/com/crm/auth/service/IAuthService.java b/crm-auth/src/main/java/com/crm/auth/service/IAuthService.java
index 1a49370..97792d1 100644
--- a/crm-auth/src/main/java/com/crm/auth/service/IAuthService.java
+++ b/crm-auth/src/main/java/com/crm/auth/service/IAuthService.java
@@ -10,9 +10,18 @@ import com.crm.auth.domain.enums.IdentityTypeEnum;
public interface IAuthService {
/**
- * 涓夋柟鎵爜鐧诲綍锛歛uthCode 鎹笁鏂圭敤鎴?-> 鍖归厤/娉ㄥ唽鏈湴鐢ㄦ埛 -> 缁戝畾韬唤 -> 绛惧彂 token
+ * 涓夋柟鎵爜鐧诲綍锛歛uthCode 鎹笁鏂圭敤鎴?-> 鍖归厤/娉ㄥ唽鏈湴鐢ㄦ埛 -> 缁戝畾韬唤 -> 鍗曠鍦ㄧ嚎妫€娴?-> 绛惧彂 token銆?+ * <p>璇?(鐢ㄦ埛, 绔? 宸叉湁娲讳細璇濇椂涓嶇姝e紡 token锛岃繑鍥?status=DEVICE_ONLINE + preLoginToken锛岀敱鍓嶇鍐崇瓥鏄惁椤跺彿</p>
+ *
+ * @param clientType 绔被鍨嬶紙pc/mobile锛屽ぇ灏忓啓涓嶆晱鎰燂級锛涗笉浼?绌轰覆榛樿 PC锛岄潪娉曞€兼姏 61016
*/
- LoginResultDTO login(IdentityTypeEnum type, String authCode);
+ LoginResultDTO login(IdentityTypeEnum type, String authCode, String clientType);
+
+ /**
+ * 纭椤跺彿鐧诲綍锛欸ETDEL 娑堣垂涓€娆℃€?preLoginToken -> 韪㈡帀璇?(鐢ㄦ埛, 绔? 鏃т細璇?-> 绛惧彂鏂?token 瑕嗗啓绱㈠紩銆?+ * <p>鍙湁 SUCCESS 涓€绉嶆甯稿嚭鍙o紱preLoginToken 杩囨湡/宸叉秷璐?浼€犵粺涓€鎶?61014</p>
+ */
+ LoginResultDTO confirmLogin(String preLoginToken);
/**
* 娉ㄩ攢褰撳墠 token
diff --git a/crm-base/src/main/java/com/crm/base/domain/result/Result.java b/crm-base/src/main/java/com/crm/base/domain/result/Result.java
index c72215d..8c78f85 100644
--- a/crm-base/src/main/java/com/crm/base/domain/result/Result.java
+++ b/crm-base/src/main/java/com/crm/base/domain/result/Result.java
@@ -62,6 +62,15 @@ public class Result<T> implements Serializable {
return res;
}
+ /**
+ * 澶辫触浣嗕粛闇€鍥炰紶涓氬姟鏁版嵁锛堝鐧诲綍鍐茬獊鏃跺洖浼?preLoginToken锛岀敱鍓嶇鍐崇瓥鍚庣画浜や簰锛?+ */
+ public static <T> Result<T> fail(Integer code, String msg, T data) {
+ Result<T> res = fail(code, msg);
+ res.setData(data);
+ return res;
+ }
+
/**
* MyBatis-Plus 鍒嗛〉瀵硅薄鐩存帴鍖呰杩斿洖
*/