diff --git a/jeecg-boot-base-core/pom.xml b/jeecg-boot-base-core/pom.xml
index b8bb6cbd7..018a026c0 100644
--- a/jeecg-boot-base-core/pom.xml
+++ b/jeecg-boot-base-core/pom.xml
@@ -230,6 +230,20 @@
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-authorization-server
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+
+ org.springframework.security
+ spring-security-cas
+
org.apache.shiro
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/CommonAPI.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/CommonAPI.java
index 97823987c..0013f4ef4 100644
--- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/CommonAPI.java
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/api/CommonAPI.java
@@ -127,4 +127,24 @@ public interface CommonAPI {
*/
List translateDictFromTableByKeys(String table, String text, String code, String keys);
+ /**
+ * 登录加载系统字典
+ * @return
+ */
+ Map> queryAllDictItems();
+
+ /**
+ * 查询SysDepart集合
+ * @param userId
+ * @return
+ */
+ List queryUserDeparts(String userId);
+
+ /**
+ * 根据用户名设置部门ID
+ * @param username
+ * @param orgCode
+ */
+ void updateUserDepart(String username,String orgCode,Integer loginTenantId);
+
}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java
index 71f97672c..44443e0cb 100644
--- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/aspect/AutoLogAspect.java
@@ -1,5 +1,6 @@
package org.jeecg.common.aspect;
+import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import org.apache.shiro.SecurityUtils;
@@ -21,6 +22,7 @@ import org.jeecg.common.util.IpUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
@@ -100,7 +102,7 @@ public class AutoLogAspect {
//设置IP地址
dto.setIp(IpUtils.getIpAddr(request));
//获取登录用户信息
- LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+ LoginUser sysUser = JSON.parseObject(SecurityContextHolder.getContext().getAuthentication().getName(), LoginUser.class);;
if(sysUser!=null){
dto.setUserid(sysUser.getUsername());
dto.setUsername(sysUser.getRealname());
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
index 82e19cf9f..6e92c2c64 100644
--- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgBootExceptionHandler.java
@@ -17,6 +17,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;
+import javax.naming.AuthenticationException;
+
/**
* 异常处理器
*
@@ -27,6 +29,23 @@ import org.springframework.web.servlet.NoHandlerFoundException;
@Slf4j
public class JeecgBootExceptionHandler {
+ /**
+ * 验证码错误异常
+ */
+
+ @ExceptionHandler(JeecgCaptchaException.class)
+ public Result> handleJeecgCaptchaException(JeecgCaptchaException e) {
+ log.error(e.getMessage(), e);
+ return Result.error(e.getCode(), e.getMessage());
+ }
+
+ @ExceptionHandler(AuthenticationException.class)
+ @ResponseStatus(HttpStatus.UNAUTHORIZED)
+ public Result> handleJeecgCaptchaException(AuthenticationException e) {
+ log.error(e.getMessage(), e);
+ return Result.error(401, e.getMessage());
+ }
+
/**
* 处理自定义异常
*/
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgCaptchaException.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgCaptchaException.java
new file mode 100644
index 000000000..cf2777a39
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/exception/JeecgCaptchaException.java
@@ -0,0 +1,28 @@
+package org.jeecg.common.exception;
+
+import lombok.Data;
+
+/**
+ * @author kezhijie@wuhandsj.com
+ * @date 2024/1/2 11:38
+ */
+@Data
+public class JeecgCaptchaException extends RuntimeException{
+
+ private Integer code;
+
+ private static final long serialVersionUID = -9093410345065209053L;
+
+ public JeecgCaptchaException(Integer code, String message) {
+ super(message);
+ this.code = code;
+ }
+
+ public JeecgCaptchaException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JeecgCaptchaException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
index b4505d78b..e9f577276 100644
--- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/base/controller/JeecgController.java
@@ -1,5 +1,6 @@
package org.jeecg.common.system.base.controller;
+import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -19,6 +20,7 @@ import org.jeecgframework.poi.excel.entity.ImportParams;
import org.jeecgframework.poi.excel.entity.enmus.ExcelType;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;
@@ -51,7 +53,7 @@ public class JeecgController> {
protected ModelAndView exportXls(HttpServletRequest request, T object, Class clazz, String title) {
// Step.1 组装查询条件
QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
- LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+ LoginUser sysUser = JSON.parseObject(SecurityContextHolder.getContext().getAuthentication().getName(), LoginUser.class);;
// 过滤选中数据
String selections = request.getParameter("selections");
@@ -89,7 +91,7 @@ public class JeecgController> {
protected ModelAndView exportXlsSheet(HttpServletRequest request, T object, Class clazz, String title,String exportFields,Integer pageNum) {
// Step.1 组装查询条件
QueryWrapper queryWrapper = QueryGenerator.initQueryWrapper(object, request.getParameterMap());
- LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+ LoginUser sysUser = JSON.parseObject(SecurityContextHolder.getContext().getAuthentication().getName(), LoginUser.class);;
// Step.2 计算分页sheet数据
double total = service.count();
int count = (int)Math.ceil(total/pageNum);
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
index 7365ece60..a059f857d 100644
--- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java
@@ -1,5 +1,7 @@
package org.jeecg.common.system.util;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson2.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
@@ -29,6 +31,7 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
/**
* @Author Scott
@@ -95,7 +98,8 @@ public class JwtUtil {
public static String getUsername(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
- return jwt.getClaim("username").asString();
+ LoginUser loginUser = JSONObject.parseObject(jwt.getClaim("sub").asString(), LoginUser.class);
+ return loginUser.getUsername();
} catch (JWTDecodeException e) {
return null;
}
@@ -177,7 +181,7 @@ public class JwtUtil {
//2.通过shiro获取登录用户信息
LoginUser sysUser = null;
try {
- sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+ sysUser = JSON.parseObject(SecurityContextHolder.getContext().getAuthentication().getName(), LoginUser.class);;
} catch (Exception e) {
log.warn("SecurityUtils.getSubject() 获取用户信息异常:" + e.getMessage());
}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
index 44656a7f8..c0a83364c 100644
--- a/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/common/system/vo/LoginUser.java
@@ -7,6 +7,8 @@ import lombok.experimental.Accessors;
import org.jeecg.common.desensitization.annotation.SensitiveField;
import org.springframework.format.annotation.DateTimeFormat;
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
import java.util.Date;
/**
@@ -20,8 +22,10 @@ import java.util.Date;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
-public class LoginUser {
+public class LoginUser implements Serializable {
+
+ private static final long serialVersionUID = -7143159031677245866L;
/**
* 登录人id
*/
@@ -127,4 +131,32 @@ public class LoginUser {
/**设备id uniapp推送用*/
private String clientId;
+ @SensitiveField
+ private String salt;
+
+ @Override
+ public String toString() {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ return "{" +
+ "\"id\":\"" + id + '"' +
+ ", \"username\":\"" + username + '"' +
+ ", \"realname\":\"" + realname + '"' +
+ ", \"password\":\"'" + password + '"' +
+ ", \"orgCode\":\"" + orgCode + '"' +
+ ", \"avatar\":\"" + avatar + '"' +
+ ", \"sex\":" + sex +
+ ", \"email\":\"" + email + '"' +
+ ", \"phone\":\"" + phone + '"' +
+ ", \"status\":" + status +
+ ", \"delFlag\":" + delFlag +
+ ", \"activitiSync\":" + activitiSync +
+ ", \"userIdentity\":" + userIdentity +
+ ", \"departIds\":\"" + departIds + '"' +
+ ", \"post\":\"" + post + '"' +
+ ", \"telephone\":\"" + telephone + '"' +
+ ", \"relTenantIds\":\"" + relTenantIds + '"' +
+ ", \"clientId\":\"" + clientId + '"' +
+ ", \"salt\":\"" + salt + '"' +
+ '}';
+ }
}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/config/security/JeecgRedisOAuth2AuthorizationConsentService.java b/jeecg-boot-base-core/src/main/java/org/jeecg/config/security/JeecgRedisOAuth2AuthorizationConsentService.java
new file mode 100644
index 000000000..5ca2113ae
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/config/security/JeecgRedisOAuth2AuthorizationConsentService.java
@@ -0,0 +1,51 @@
+package org.jeecg.config.security;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+@RequiredArgsConstructor
+public class JeecgRedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService {
+
+ private final RedisTemplate redisTemplate;
+
+ private final static Long TIMEOUT = 10L;
+
+ @Override
+ public void save(OAuth2AuthorizationConsent authorizationConsent) {
+ Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
+
+ redisTemplate.opsForValue().set(buildKey(authorizationConsent), authorizationConsent, TIMEOUT,
+ TimeUnit.MINUTES);
+
+ }
+
+ @Override
+ public void remove(OAuth2AuthorizationConsent authorizationConsent) {
+ Assert.notNull(authorizationConsent, "authorizationConsent cannot be null");
+ redisTemplate.delete(buildKey(authorizationConsent));
+ }
+
+ @Override
+ public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) {
+ Assert.hasText(registeredClientId, "registeredClientId cannot be empty");
+ Assert.hasText(principalName, "principalName cannot be empty");
+ return (OAuth2AuthorizationConsent) redisTemplate.opsForValue()
+ .get(buildKey(registeredClientId, principalName));
+ }
+
+ private static String buildKey(String registeredClientId, String principalName) {
+ return "token:consent:" + registeredClientId + ":" + principalName;
+ }
+
+ private static String buildKey(OAuth2AuthorizationConsent authorizationConsent) {
+ return buildKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
+ }
+
+}
diff --git a/jeecg-boot-base-core/src/main/java/org/jeecg/config/security/JeecgRedisOAuth2AuthorizationService.java b/jeecg-boot-base-core/src/main/java/org/jeecg/config/security/JeecgRedisOAuth2AuthorizationService.java
new file mode 100644
index 000000000..cdbb7dc53
--- /dev/null
+++ b/jeecg-boot-base-core/src/main/java/org/jeecg/config/security/JeecgRedisOAuth2AuthorizationService.java
@@ -0,0 +1,180 @@
+package org.jeecg.config.security;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.lang.Nullable;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author EightMonth
+ */
+@Component
+@RequiredArgsConstructor
+public class JeecgRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
+
+ private final static Long TIMEOUT = 10L;
+
+ private static final String AUTHORIZATION = "token";
+
+ private final RedisTemplate redisTemplate;
+
+ @Override
+ public void save(OAuth2Authorization authorization) {
+ Assert.notNull(authorization, "authorization cannot be null");
+
+ if (isState(authorization)) {
+ String token = authorization.getAttribute("state");
+ redisTemplate.setValueSerializer(RedisSerializer.java());
+ redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.STATE, token), authorization, TIMEOUT,
+ TimeUnit.MINUTES);
+ }
+
+ if (isCode(authorization)) {
+ OAuth2Authorization.Token authorizationCode = authorization
+ .getToken(OAuth2AuthorizationCode.class);
+ OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
+ long between = ChronoUnit.MINUTES.between(authorizationCodeToken.getIssuedAt(),
+ authorizationCodeToken.getExpiresAt());
+ redisTemplate.setValueSerializer(RedisSerializer.java());
+ redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()),
+ authorization, between, TimeUnit.MINUTES);
+ }
+
+ if (isRefreshToken(authorization)) {
+ OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
+ long between = ChronoUnit.SECONDS.between(refreshToken.getIssuedAt(), refreshToken.getExpiresAt());
+ redisTemplate.setValueSerializer(RedisSerializer.java());
+ redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()),
+ authorization, between, TimeUnit.SECONDS);
+ }
+
+ if (isAccessToken(authorization)) {
+ OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
+ long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
+ redisTemplate.setValueSerializer(RedisSerializer.java());
+ redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()),
+ authorization, between, TimeUnit.SECONDS);
+
+ // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
+ String tokenUsername = String.format("%s::%s::%s", AUTHORIZATION, authorization.getPrincipalName(), accessToken.getTokenValue());
+ redisTemplate.opsForValue().set(tokenUsername, accessToken.getTokenValue(), between, TimeUnit.SECONDS);
+ }
+ }
+
+ @Override
+ public void remove(OAuth2Authorization authorization) {
+ Assert.notNull(authorization, "authorization cannot be null");
+
+ List keys = new ArrayList<>();
+ if (isState(authorization)) {
+ String token = authorization.getAttribute("state");
+ keys.add(buildKey(OAuth2ParameterNames.STATE, token));
+ }
+
+ if (isCode(authorization)) {
+ OAuth2Authorization.Token authorizationCode = authorization
+ .getToken(OAuth2AuthorizationCode.class);
+ OAuth2AuthorizationCode authorizationCodeToken = authorizationCode.getToken();
+ keys.add(buildKey(OAuth2ParameterNames.CODE, authorizationCodeToken.getTokenValue()));
+ }
+
+ if (isRefreshToken(authorization)) {
+ OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
+ keys.add(buildKey(OAuth2ParameterNames.REFRESH_TOKEN, refreshToken.getTokenValue()));
+ }
+
+ if (isAccessToken(authorization)) {
+ OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
+ keys.add(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()));
+
+ // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
+ String key = String.format("%s::%s::%s", AUTHORIZATION, authorization.getPrincipalName(), accessToken.getTokenValue());
+ keys.add(key);
+ }
+
+ redisTemplate.delete(keys);
+ }
+
+ @Override
+ @Nullable
+ public OAuth2Authorization findById(String id) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ @Nullable
+ public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) {
+ Assert.hasText(token, "token cannot be empty");
+ Assert.notNull(tokenType, "tokenType cannot be empty");
+ redisTemplate.setValueSerializer(RedisSerializer.java());
+ return (OAuth2Authorization) redisTemplate.opsForValue().get(buildKey(tokenType.getValue(), token));
+ }
+
+ private String buildKey(String type, String id) {
+ return String.format("%s::%s::%s", AUTHORIZATION, type, id);
+ }
+
+ private static boolean isState(OAuth2Authorization authorization) {
+ return Objects.nonNull(authorization.getAttribute("state"));
+ }
+
+ private static boolean isCode(OAuth2Authorization authorization) {
+ OAuth2Authorization.Token authorizationCode = authorization
+ .getToken(OAuth2AuthorizationCode.class);
+ return Objects.nonNull(authorizationCode);
+ }
+
+ private static boolean isRefreshToken(OAuth2Authorization authorization) {
+ return Objects.nonNull(authorization.getRefreshToken());
+ }
+
+ private static boolean isAccessToken(OAuth2Authorization authorization) {
+ return Objects.nonNull(authorization.getAccessToken());
+ }
+
+ /**
+ * 扩展方法根据 username 查询是否存在存储的
+ * @param authentication
+ * @return
+ */
+ public void removeByUsername(Authentication authentication) {
+ // 根据 username查询对应access-token
+ String authenticationName = authentication.getName();
+
+ // 扩展记录 access-token 、username 的关系 1::token::username::admin::xxx
+ String tokenUsernameKey = String.format("%s::%s::*", AUTHORIZATION, authenticationName);
+ Set keys = redisTemplate.keys(tokenUsernameKey);
+ if (CollUtil.isEmpty(keys)) {
+ return;
+ }
+
+ List