diff --git a/pom.xml b/pom.xml
index df841d52..e129b784 100644
--- a/pom.xml
+++ b/pom.xml
@@ -99,6 +99,10 @@
org.springframework.boot
spring-boot-starter-data-redis
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
org.springframework.boot
spring-boot-starter-web
diff --git a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
index be573166..262910b0 100644
--- a/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
+++ b/src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
@@ -9,6 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -24,6 +25,7 @@ import java.util.Collections;
@ServletComponentScan("com.genersoft.iot.vmp.conf")
@SpringBootApplication
@EnableScheduling
+@EnableCaching
public class VManageBootstrap extends SpringBootServletInitializer {
private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);
diff --git a/src/main/java/com/genersoft/iot/vmp/conf/MybatisConfig.java b/src/main/java/com/genersoft/iot/vmp/conf/MybatisConfig.java
index 03ef0994..7f25a362 100644
--- a/src/main/java/com/genersoft/iot/vmp/conf/MybatisConfig.java
+++ b/src/main/java/com/genersoft/iot/vmp/conf/MybatisConfig.java
@@ -1,6 +1,8 @@
package com.genersoft.iot.vmp.conf;
import org.apache.ibatis.logging.stdout.StdOutImpl;
+import org.apache.ibatis.mapping.DatabaseIdProvider;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,6 +11,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import javax.sql.DataSource;
+import java.util.Properties;
/**
* 配置mybatis
@@ -21,7 +24,29 @@ public class MybatisConfig {
private UserSetting userSetting;
@Bean
- public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
+ public DatabaseIdProvider databaseIdProvider() {
+ VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
+ Properties properties = new Properties();
+ properties.setProperty("Oracle", "oracle");
+ properties.setProperty("MySQL", "mysql");
+ properties.setProperty("DB2", "db2");
+ properties.setProperty("Derby", "derby");
+ properties.setProperty("H2", "h2");
+ properties.setProperty("HSQL", "hsql");
+ properties.setProperty("Informix", "informix");
+ properties.setProperty("MS-SQL", "ms-sql");
+ properties.setProperty("PostgreSQL", "postgresql");
+ properties.setProperty("Sybase", "sybase");
+ properties.setProperty("Hana", "hana");
+ properties.setProperty("DM", "dm");
+ properties.setProperty("KingbaseES", "kingbase");
+ properties.setProperty("KingBase8", "kingbase");
+ databaseIdProvider.setProperties(properties);
+ return databaseIdProvider;
+ }
+
+ @Bean
+ public SqlSessionFactory sqlSessionFactory(DataSource dataSource, DatabaseIdProvider databaseIdProvider) throws Exception {
final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource);
org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration();
@@ -30,6 +55,7 @@ public class MybatisConfig {
}
config.setMapUnderscoreToCamelCase(true);
sqlSessionFactory.setConfiguration(config);
+ sqlSessionFactory.setDatabaseIdProvider(databaseIdProvider);
return sqlSessionFactory.getObject();
}
diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
index f45f89a1..274a19f8 100644
--- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
+++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
@@ -51,8 +51,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
if (StringUtils.isBlank(jwt)) {
jwt = request.getParameter(JwtUtils.getHeader());
if (StringUtils.isBlank(jwt)) {
- chain.doFilter(request, response);
- return;
+ jwt = request.getHeader(JwtUtils.getApiKeyHeader());
+ if (StringUtils.isBlank(jwt)) {
+ chain.doFilter(request, response);
+ return;
+ }
}
}
diff --git a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
index fcd19461..eacff188 100644
--- a/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
+++ b/src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
@@ -1,8 +1,12 @@
package com.genersoft.iot.vmp.conf.security;
import com.genersoft.iot.vmp.conf.security.dto.JwtUser;
+import com.genersoft.iot.vmp.service.IUserApiKeyService;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.User;
+import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
+import org.jose4j.jwk.JsonWebKey;
+import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
@@ -20,8 +24,13 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
+import java.util.List;
+import java.util.Map;
@Component
public class JwtUtils implements InitializingBean {
@@ -30,6 +39,8 @@ public class JwtUtils implements InitializingBean {
public static final String HEADER = "access-token";
+ public static final String API_KEY_HEADER = "api-key";
+
private static final String AUDIENCE = "Audience";
private static final String keyId = "3e79646c4dbc408383a9eed09f2b85ae";
@@ -37,17 +48,28 @@ public class JwtUtils implements InitializingBean {
/**
* token过期时间(分钟)
*/
- public static final long expirationTime = 30 * 24 * 60;
+ public static final long EXPIRATION_TIME = 30 * 24 * 60;
private static RsaJsonWebKey rsaJsonWebKey;
private static IUserService userService;
+ private static IUserApiKeyService userApiKeyService;
+
+ public static String getApiKeyHeader() {
+ return API_KEY_HEADER;
+ }
+
@Resource
public void setUserService(IUserService userService) {
JwtUtils.userService = userService;
}
+ @Resource
+ public void setUserApiKeyService(IUserApiKeyService userApiKeyService) {
+ JwtUtils.userApiKeyService = userApiKeyService;
+ }
+
@Override
public void afterPropertiesSet() {
try {
@@ -59,17 +81,34 @@ public class JwtUtils implements InitializingBean {
/**
* 创建密钥对
+ *
* @throws JoseException JoseException
*/
private RsaJsonWebKey generateRsaJsonWebKey() throws JoseException {
- // 生成一个RSA密钥对,该密钥对将用于JWT的签名和验证,包装在JWK中
- RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
- // 给JWK一个密钥ID
- rsaJsonWebKey.setKeyId(keyId);
+ RsaJsonWebKey rsaJsonWebKey = null;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("/jwk.json"), StandardCharsets.UTF_8))) {
+ String jwkJson = reader.readLine();
+ JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson);
+ List jsonWebKeys = jsonWebKeySet.getJsonWebKeys();
+ if (!jsonWebKeys.isEmpty()) {
+ JsonWebKey jsonWebKey = jsonWebKeys.get(0);
+ if (jsonWebKey instanceof RsaJsonWebKey) {
+ rsaJsonWebKey = (RsaJsonWebKey) jsonWebKey;
+ }
+ }
+ } catch (Exception e) {
+ // ignored
+ }
+ if (rsaJsonWebKey == null) {
+ // 生成一个RSA密钥对,该密钥对将用于JWT的签名和验证,包装在JWK中
+ rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
+ // 给JWK一个密钥ID
+ rsaJsonWebKey.setKeyId(keyId);
+ }
return rsaJsonWebKey;
}
- public static String createToken(String username) {
+ public static String createToken(String username, Long expirationTime, Map extra) {
try {
/*
* “iss” (issuer) 发行人
@@ -83,13 +122,17 @@ public class JwtUtils implements InitializingBean {
claims.setGeneratedJwtId();
claims.setIssuedAtToNow();
// 令牌将过期的时间 分钟
- claims.setExpirationTimeMinutesInTheFuture(expirationTime);
+ if (expirationTime != null) {
+ claims.setExpirationTimeMinutesInTheFuture(expirationTime);
+ }
claims.setNotBeforeMinutesInThePast(0);
claims.setSubject("login");
claims.setAudience(AUDIENCE);
//添加自定义参数,必须是字符串类型
claims.setClaim("userName", username);
-
+ if (extra != null) {
+ extra.forEach(claims::setClaim);
+ }
//jws
JsonWebSignature jws = new JsonWebSignature();
//签名算法RS256
@@ -104,10 +147,17 @@ public class JwtUtils implements InitializingBean {
} catch (JoseException e) {
logger.error("[Token生成失败]: {}", e.getMessage());
}
-
return null;
}
+ public static String createToken(String username, Long expirationTime) {
+ return createToken(username, expirationTime, null);
+ }
+
+ public static String createToken(String username) {
+ return createToken(username, EXPIRATION_TIME);
+ }
+
public static String getHeader() {
return HEADER;
}
@@ -118,8 +168,8 @@ public class JwtUtils implements InitializingBean {
try {
JwtConsumer consumer = new JwtConsumerBuilder()
- .setRequireExpirationTime()
- .setMaxFutureValidityInMinutes(5256000)
+ //.setRequireExpirationTime()
+ //.setMaxFutureValidityInMinutes(5256000)
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
//.setExpectedIssuer("")
@@ -129,15 +179,27 @@ public class JwtUtils implements InitializingBean {
JwtClaims claims = consumer.processToClaims(token);
NumericDate expirationTime = claims.getExpirationTime();
- // 判断是否即将过期, 默认剩余时间小于5分钟未即将过期
- // 剩余时间 (秒)
- long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue();
- if (timeRemaining < 5 * 60) {
- jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
+ if (expirationTime != null) {
+ // 判断是否即将过期, 默认剩余时间小于5分钟未即将过期
+ // 剩余时间 (秒)
+ long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue();
+ if (timeRemaining < 5 * 60) {
+ jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
+ } else {
+ jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
+ }
} else {
jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
}
+ Long apiKeyId = claims.getClaimValue("apiKeyId", Long.class);
+ if (apiKeyId != null) {
+ UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(apiKeyId.intValue());
+ if (userApiKey == null || !userApiKey.isEnable()) {
+ jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED);
+ }
+ }
+
String username = (String) claims.getClaimValue("userName");
User user = userService.getUserByUsername(username);
diff --git a/src/main/java/com/genersoft/iot/vmp/service/IUserApiKeyService.java b/src/main/java/com/genersoft/iot/vmp/service/IUserApiKeyService.java
new file mode 100644
index 00000000..b3cc5805
--- /dev/null
+++ b/src/main/java/com/genersoft/iot/vmp/service/IUserApiKeyService.java
@@ -0,0 +1,25 @@
+package com.genersoft.iot.vmp.service;
+
+import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
+import com.github.pagehelper.PageInfo;
+
+public interface IUserApiKeyService {
+ int addApiKey(UserApiKey userApiKey);
+
+ boolean isApiKeyExists(String apiKey);
+
+ PageInfo getUserApiKeys(int page, int count);
+
+ int enable(Integer id);
+
+ int disable(Integer id);
+
+ int remark(Integer id, String remark);
+
+ int delete(Integer id);
+
+ UserApiKey getUserApiKeyById(Integer id);
+
+ int reset(Integer id, String apiKey);
+
+}
diff --git a/src/main/java/com/genersoft/iot/vmp/service/IUserService.java b/src/main/java/com/genersoft/iot/vmp/service/IUserService.java
index 7e2a8395..1e9b7247 100755
--- a/src/main/java/com/genersoft/iot/vmp/service/IUserService.java
+++ b/src/main/java/com/genersoft/iot/vmp/service/IUserService.java
@@ -11,6 +11,8 @@ public interface IUserService {
boolean changePassword(int id, String password);
+ User getUserById(int id);
+
User getUserByUsername(String username);
int addUser(User user);
diff --git a/src/main/java/com/genersoft/iot/vmp/service/impl/UserApiKeyServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/service/impl/UserApiKeyServiceImpl.java
new file mode 100644
index 00000000..85ee4f0f
--- /dev/null
+++ b/src/main/java/com/genersoft/iot/vmp/service/impl/UserApiKeyServiceImpl.java
@@ -0,0 +1,80 @@
+package com.genersoft.iot.vmp.service.impl;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.genersoft.iot.vmp.service.IUserApiKeyService;
+import com.genersoft.iot.vmp.storager.dao.UserApiKeyMapper;
+import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+@DS("master")
+public class UserApiKeyServiceImpl implements IUserApiKeyService {
+
+ @Autowired
+ UserApiKeyMapper userApiKeyMapper;
+
+ @Autowired
+ private RedisTemplate