Browse Source

支持手动生成token

EightMonth 1 year ago
parent
commit
c88f9d95d4

+ 18 - 9
jeecg-boot-base-core/src/main/java/org/jeecg/common/system/util/JwtUtil.java

@@ -12,10 +12,7 @@ import com.google.common.base.Joiner;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.Stream;
 
 
@@ -37,12 +34,16 @@ import org.jeecg.common.system.vo.SysUserCacheInfo;
 import org.jeecg.common.util.DateUtils;
 import org.jeecg.common.util.DateUtils;
 import org.jeecg.common.util.SpringContextUtils;
 import org.jeecg.common.util.SpringContextUtils;
 import org.jeecg.common.util.oConvertUtils;
 import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.security.self.SelfAuthenticationProvider;
+import org.jeecg.config.security.self.SelfAuthenticationToken;
 import org.jeecg.config.security.utils.SecureUtil;
 import org.jeecg.config.security.utils.SecureUtil;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.oauth2.core.*;
 import org.springframework.security.oauth2.core.*;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
 import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
@@ -124,17 +125,25 @@ public class JwtUtil {
 	}
 	}
 
 
 	/**
 	/**
-	 * 生成签名,5min后过期(暂未实现)
+	 * 生成token
 	 *
 	 *
 	 * @param username 用户名
 	 * @param username 用户名
 	 * @param secret   用户的密码
 	 * @param secret   用户的密码
 	 * @return 加密的token
 	 * @return 加密的token
 	 */
 	 */
 	public static String sign(String username, String secret) {
 	public static String sign(String username, String secret) {
-		Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
-		Algorithm algorithm = Algorithm.HMAC256(secret);
-		// 附带username信息
-		return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
+		Map<String, Object> additionalParameter = new HashMap<>();
+		additionalParameter.put("username", username);
+
+		RegisteredClientRepository registeredClientRepository = SpringContextUtils.getBean(RegisteredClientRepository.class);
+		SelfAuthenticationProvider selfAuthenticationProvider = SpringContextUtils.getBean(SelfAuthenticationProvider.class);
+
+		OAuth2ClientAuthenticationToken client = new OAuth2ClientAuthenticationToken(Objects.requireNonNull(registeredClientRepository.findByClientId("jeecg-client")), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
+		client.setAuthenticated(true);
+		SelfAuthenticationToken selfAuthenticationToken = new SelfAuthenticationToken(client, additionalParameter);
+		selfAuthenticationToken.setAuthenticated(true);
+		OAuth2AccessTokenAuthenticationToken accessToken = (OAuth2AccessTokenAuthenticationToken) selfAuthenticationProvider.authenticate(selfAuthenticationToken);
+		return accessToken.getAccessToken().getTokenValue();
 
 
 	}
 	}
 
 

+ 2 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/security/LoginType.java

@@ -33,4 +33,6 @@ public class LoginType {
      * 所有联合登录,比如github\钉钉\企业微信\微信
      * 所有联合登录,比如github\钉钉\企业微信\微信
      */
      */
     public static final String SOCIAL = "social";
     public static final String SOCIAL = "social";
+
+    public static final String SELF = "self";
 }
 }

+ 10 - 4
jeecg-boot-base-core/src/main/java/org/jeecg/config/security/SecurityConfig.java

@@ -37,17 +37,16 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
 import org.springframework.security.oauth2.server.authorization.token.*;
 import org.springframework.security.oauth2.server.authorization.token.*;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
 import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.CorsConfiguration;
 
 
 import java.security.KeyPair;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPrivateKey;
 import java.security.interfaces.RSAPublicKey;
 import java.security.interfaces.RSAPublicKey;
 import java.util.Arrays;
 import java.util.Arrays;
-import java.util.UUID;
 
 
 /**
 /**
  * spring authorization server核心配置
  * spring authorization server核心配置
@@ -193,7 +192,8 @@ public class SecurityConfig {
         RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
         RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
         RSAKey rsaKey = new RSAKey.Builder(publicKey)
         RSAKey rsaKey = new RSAKey.Builder(publicKey)
                 .privateKey(privateKey)
                 .privateKey(privateKey)
-                .keyID(UUID.randomUUID().toString())
+                // 重要!生产环境需要修改!
+                .keyID("jeecg")
                 .build();
                 .build();
         JWKSet jwkSet = new JWKSet(rsaKey);
         JWKSet jwkSet = new JWKSet(rsaKey);
         return new ImmutableJWKSet<>(jwkSet);
         return new ImmutableJWKSet<>(jwkSet);
@@ -211,7 +211,13 @@ public class SecurityConfig {
         KeyPair keyPair;
         KeyPair keyPair;
         try {
         try {
             KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
             KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
-            keyPairGenerator.initialize(2048);
+
+            // 生产环境不应该设置secureRandom,seed如果被泄露,jwt容易被伪造
+            // 如果不设置secureRandom,会存在一个问题,当应用重启后,原有的token将会全部失效,因为重启的keyPair与之前已经不同
+            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
+            // 重要!生产环境需要修改!
+            secureRandom.setSeed("jeecg".getBytes());
+            keyPairGenerator.initialize(2048, secureRandom);
             keyPair = keyPairGenerator.generateKeyPair();
             keyPair = keyPairGenerator.generateKeyPair();
         }
         }
         catch (Exception ex) {
         catch (Exception ex) {

+ 217 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/security/self/SelfAuthenticationProvider.java

@@ -0,0 +1,217 @@
+package org.jeecg.config.security.self;
+
+import com.alibaba.fastjson.JSONObject;
+import org.jeecg.common.api.CommonAPI;
+import org.jeecg.common.constant.CommonConstant;
+import org.jeecg.common.exception.JeecgBootException;
+import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.common.system.vo.SysDepartModel;
+import org.jeecg.common.util.RedisUtil;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.config.JeecgBaseConfig;
+import org.jeecg.modules.base.service.BaseCommonService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.core.*;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import java.security.Principal;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 自用生成token处理器,不对外开放,外部请求无法通过该方式生成token
+ * @author eightmonth@qq.com
+ * @date 2024/3/19 11:40
+ */
+@Component
+public class SelfAuthenticationProvider implements AuthenticationProvider {
+
+    private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
+
+    private final OAuth2AuthorizationService authorizationService;
+    private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
+    @Autowired
+    private CommonAPI commonAPI;
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private JeecgBaseConfig jeecgBaseConfig;
+    @Autowired
+    private BaseCommonService baseCommonService;
+
+    public SelfAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+        Assert.notNull(authorizationService, "authorizationService cannot be null");
+        Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
+        this.authorizationService = authorizationService;
+        this.tokenGenerator = tokenGenerator;
+    }
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        SelfAuthenticationToken passwordGrantAuthenticationToken =  (SelfAuthenticationToken) authentication;
+        Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
+
+        // 授权类型
+        AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
+        // 用户名
+        String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
+        //请求参数权限范围
+        String requestScopesStr = "*";
+        //请求参数权限范围专场集合
+        Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
+
+        OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
+        RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
+
+        if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
+            throw new JeecgBootException("非法登录");
+        }
+
+        // 通过用户名获取用户信息
+        LoginUser loginUser = commonAPI.getUserByName(username);
+        // 检查用户可行性
+        checkUserIsEffective(loginUser);
+
+        //由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
+        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
+
+        DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
+                .registeredClient(registeredClient)
+                .principal(usernamePasswordAuthenticationToken)
+                .authorizationGrantType(authorizationGrantType)
+                .authorizedScopes(requestScopeSet)
+                .authorizationGrant(passwordGrantAuthenticationToken);
+
+        OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
+                .principalName(clientPrincipal.getName())
+                .authorizedScopes(requestScopeSet)
+                .attribute(Principal.class.getName(), username)
+                .authorizationGrantType(authorizationGrantType);
+
+
+        // ----- Access token -----
+        OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
+        OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
+        if (generatedAccessToken == null) {
+            OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+                    "无法生成访问token,请联系管理系。", ERROR_URI);
+            throw new OAuth2AuthenticationException(error);
+        }
+        OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+                generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
+                generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
+        if (generatedAccessToken instanceof ClaimAccessor) {
+            authorizationBuilder.token(accessToken, (metadata) -> {
+                metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
+            });
+        } else {
+            authorizationBuilder.accessToken(accessToken);
+        }
+
+        // ----- Refresh token -----
+        OAuth2RefreshToken refreshToken = null;
+        if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
+                // 不向公共客户端颁发刷新令牌
+                !clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
+
+            tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
+            OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
+            if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
+                OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+                        "无法生成刷新token,请联系管理员。", ERROR_URI);
+                throw new OAuth2AuthenticationException(error);
+            }
+
+            refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
+            authorizationBuilder.refreshToken(refreshToken);
+        }
+
+        OAuth2Authorization authorization = authorizationBuilder.build();
+
+        // 保存认证信息至redis
+        authorizationService.save(authorization);
+
+        JSONObject addition = new JSONObject(new LinkedHashMap<>());
+
+        // 设置租户
+        JSONObject jsonObject = commonAPI.setLoginTenant(username);
+        addition.putAll(jsonObject.getInnerMap());
+
+        // 设置登录用户信息
+        addition.put("userInfo", loginUser);
+        addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
+
+        List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
+        addition.put("departs", departs);
+        if (departs == null || departs.size() == 0) {
+            addition.put("multi_depart", 0);
+        } else if (departs.size() == 1) {
+            commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
+            addition.put("multi_depart", 1);
+        } else {
+            //查询当前是否有登录部门
+            if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
+                commonAPI.updateUserDepart(username, departs.get(0).getOrgCode(),null);
+            }
+            addition.put("multi_depart", 2);
+        }
+
+        // 返回access_token、refresh_token以及其它信息给到前端
+        return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, addition);
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return SelfAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+
+    private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
+        OAuth2ClientAuthenticationToken clientPrincipal = null;
+        if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
+            clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
+        }
+        if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
+            return clientPrincipal;
+        }
+        throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+    }
+
+    /**
+     * 校验用户是否有效
+     */
+    private void checkUserIsEffective(LoginUser loginUser) {
+        //情况1:根据用户信息查询,该用户不存在
+        if (Objects.isNull(loginUser)) {
+            baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
+            throw new JeecgBootException("该用户不存在,请注册");
+        }
+        //情况2:根据用户信息查询,该用户已注销
+        //update-begin---author:王帅   Date:20200601  for:if条件永远为falsebug------------
+        if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
+            //update-end---author:王帅   Date:20200601  for:if条件永远为falsebug------------
+            baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
+            throw new JeecgBootException("该用户已注销");
+        }
+        //情况3:根据用户信息查询,该用户已冻结
+        if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
+            baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
+            throw new JeecgBootException("该用户已冻结");
+        }
+    }
+}

+ 19 - 0
jeecg-boot-base-core/src/main/java/org/jeecg/config/security/self/SelfAuthenticationToken.java

@@ -0,0 +1,19 @@
+package org.jeecg.config.security.self;
+
+import org.jeecg.config.security.LoginType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
+
+import java.util.Map;
+
+/**
+ * 自用生成token,不支持对外请求,仅为程序内部生成token
+ * @author eightmonth
+ * @date 2024/3/19 11:37
+ */
+public class SelfAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
+    public SelfAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
+        super(new AuthorizationGrantType(LoginType.SELF), clientPrincipal, additionalParameters);
+    }
+}

+ 16 - 0
jeecg-module-system/jeecg-system-start/src/test/java/org/jeecg/modules/system/test/InsertDemoTest.java

@@ -6,6 +6,9 @@ import org.jeecg.common.constant.CommonConstant;
 import org.jeecg.common.system.util.JwtUtil;
 import org.jeecg.common.system.util.JwtUtil;
 import org.jeecg.common.util.RedisUtil;
 import org.jeecg.common.util.RedisUtil;
 import org.jeecg.common.util.RestUtil;
 import org.jeecg.common.util.RestUtil;
+import org.jeecg.common.util.SpringContextUtils;
+import org.jeecg.config.security.self.SelfAuthenticationProvider;
+import org.jeecg.config.security.self.SelfAuthenticationToken;
 import org.junit.Test;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -14,8 +17,21 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.test.context.junit4.SpringRunner;
 
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
 /**
 /**
  * 系统用户单元测试
  * 系统用户单元测试
  */
  */