diff --git a/README.md b/README.md index 8ed015f8..7c37dc6c 100755 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 此仓库为奇特物联(iotkit)物联网平台开源项目。 奇特物联是一个开源的物联网基础开发平台,提供了物联网及相关业务开发的常见基础功能, 能帮助你快速搭建自己的物联网相关业务平台。 -系统包含了品类、物模型、消息转换、通讯组件(mqtt通讯组件、小度音箱接入组件、onenet Studio接入组件)、云端低代码设备开发、设备管理、规则引擎、第三方平台接入、数据流转、数据可视化、报警中心等模块和智能家居APP(小程序)。 +系统包含了品类、物模型、消息转换、通讯组件(mqtt通讯组件、小度音箱接入组件、onenet Studio接入组件)、云端低代码设备开发、设备管理、规则引擎、第三方平台接入、数据流转、数据可视化、报警中心等模块和智能家居APP(小程序),集成了[Sa-Token](https://gitee.com/dromara/sa-token) 认证框架。 **前端项目见:** https://gitee.com/iotkit-open-source/iot-console-web @@ -15,7 +15,7 @@ #### 软件架构 软件架构说明 -本系统采用springboot、mongodb、redis、elasticsearch、pulsar、keycloak等框架和第三方软件 +本系统采用springboot、mongodb、redis、elasticsearch、pulsar、sa-token等框架和第三方软件 #### 安装教程 diff --git a/common/pom.xml b/common/pom.xml index 39306785..728d3c9b 100755 --- a/common/pom.xml +++ b/common/pom.xml @@ -5,7 +5,7 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/common/src/main/java/cc/iotkit/common/ComponentClassLoader.java b/common/src/main/java/cc/iotkit/common/ComponentClassLoader.java index caf6d999..4e557a1b 100755 --- a/common/src/main/java/cc/iotkit/common/ComponentClassLoader.java +++ b/common/src/main/java/cc/iotkit/common/ComponentClassLoader.java @@ -32,7 +32,7 @@ public class ComponentClassLoader { classLoaders.put(name, classLoader); Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - if (!method.isAccessible()) { + if (!method.canAccess(classLoader)) { method.setAccessible(true); } diff --git a/common/src/main/java/cc/iotkit/common/Constants.java b/common/src/main/java/cc/iotkit/common/Constants.java index 95aa4584..1008ed9c 100755 --- a/common/src/main/java/cc/iotkit/common/Constants.java +++ b/common/src/main/java/cc/iotkit/common/Constants.java @@ -1,7 +1,5 @@ package cc.iotkit.common; -import lombok.Data; - public interface Constants { String PRODUCT_SECRET = "xdkKUymrEGSCYWswqCvSPyRSFvH5j7CU"; @@ -18,6 +16,10 @@ public interface Constants { String THING_MODEL_CACHE = "thing_model_cache"; + String USER_CACHE = "user_info_cache"; + + String OAUTH_CLIENT_CACHE = "oauth_client_cache"; + String WECHAT_APP_ID = "wx791cb7bf75950e0c"; String WECHAT_APP_SECRET = "eeef73ce71f1a722ad6298985d859844"; @@ -66,6 +68,11 @@ public interface Constants { */ String HTTP_CONSUMER_DEVICE_INFO_TOPIC = "device_info:"; + /** + * 写权限 + */ + String PERMISSION_WRITE = "write"; + /** * 三方平台类型 */ diff --git a/common/src/main/java/cc/iotkit/common/utils/CodecUtil.java b/common/src/main/java/cc/iotkit/common/utils/CodecUtil.java index 9888379a..44f2f575 100755 --- a/common/src/main/java/cc/iotkit/common/utils/CodecUtil.java +++ b/common/src/main/java/cc/iotkit/common/utils/CodecUtil.java @@ -96,5 +96,4 @@ public class CodecUtil { encryptStr = new String(HexUtil.parseHex(encryptStr)); return StringUtils.isEmpty(encryptStr) ? "" : aesDecryptByBytes(base64Decode(encryptStr), decryptKey); } - } diff --git a/dao/pom.xml b/dao/pom.xml index 24fe4e4f..36b738bc 100755 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -5,7 +5,7 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/dao/src/main/java/cc/iotkit/dao/OauthClientCache.java b/dao/src/main/java/cc/iotkit/dao/OauthClientCache.java new file mode 100755 index 00000000..b444509e --- /dev/null +++ b/dao/src/main/java/cc/iotkit/dao/OauthClientCache.java @@ -0,0 +1,33 @@ +package cc.iotkit.dao; + +import cc.iotkit.common.Constants; +import cc.iotkit.model.OauthClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; + +@Repository +public class OauthClientCache { + + @Autowired + private OauthClientRepository oauthClientRepository; + + private static OauthClientCache INSTANCE; + + @PostConstruct + public void init() { + INSTANCE = this; + } + + public static OauthClientCache getInstance() { + return INSTANCE; + } + + @Cacheable(value = Constants.OAUTH_CLIENT_CACHE, key = "#clientId") + public OauthClient getClient(String clientId) { + return oauthClientRepository.findById(clientId).orElse(null); + } + +} diff --git a/dao/src/main/java/cc/iotkit/dao/OauthClientRepository.java b/dao/src/main/java/cc/iotkit/dao/OauthClientRepository.java new file mode 100755 index 00000000..788fffaa --- /dev/null +++ b/dao/src/main/java/cc/iotkit/dao/OauthClientRepository.java @@ -0,0 +1,11 @@ +package cc.iotkit.dao; + +import cc.iotkit.model.OauthClient; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface OauthClientRepository extends MongoRepository { + + +} diff --git a/dao/src/main/java/cc/iotkit/dao/UserInfoCache.java b/dao/src/main/java/cc/iotkit/dao/UserInfoCache.java new file mode 100755 index 00000000..7a6be9f5 --- /dev/null +++ b/dao/src/main/java/cc/iotkit/dao/UserInfoCache.java @@ -0,0 +1,33 @@ +package cc.iotkit.dao; + +import cc.iotkit.common.Constants; +import cc.iotkit.model.UserInfo; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; + +@Repository +public class UserInfoCache { + + @Autowired + private UserInfoRepository userInfoRepository; + + private static UserInfoCache INSTANCE; + + @PostConstruct + public void init() { + INSTANCE = this; + } + + public static UserInfoCache getInstance() { + return INSTANCE; + } + + @Cacheable(value = Constants.USER_CACHE, key = "#uid") + public UserInfo getUserInfo(String uid) { + return userInfoRepository.findById(uid).orElse(null); + } + +} diff --git a/dao/src/main/java/cc/iotkit/dao/UserInfoRepository.java b/dao/src/main/java/cc/iotkit/dao/UserInfoRepository.java index afeece71..dd1769dd 100755 --- a/dao/src/main/java/cc/iotkit/dao/UserInfoRepository.java +++ b/dao/src/main/java/cc/iotkit/dao/UserInfoRepository.java @@ -9,6 +9,8 @@ import java.util.List; @Repository public interface UserInfoRepository extends MongoRepository { + UserInfo findByUid(String uid); + List findByType(int type); List findByTypeAndOwnerId(int type, String ownerId); diff --git a/manager/pom.xml b/manager/pom.xml index d06e34ee..aeed57d0 100755 --- a/manager/pom.xml +++ b/manager/pom.xml @@ -5,7 +5,7 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 @@ -28,39 +28,20 @@ spring-boot-starter-cache - - org.springframework.cloud - spring-cloud-starter-openfeign - - org.springframework.boot - spring-boot-starter-security + spring-boot-starter-logging + + + org.apache.logging.log4j + log4j-to-slf4j + + - org.springframework.boot - spring-boot-starter-validation - - - - org.keycloak - keycloak-spring-boot-starter - - - - org.keycloak - keycloak-spring-security-adapter - - - - org.keycloak - keycloak-admin-client - - - - javax.ws.rs - javax.ws.rs-api + org.apache.logging.log4j + log4j-core @@ -68,11 +49,6 @@ caffeine - - com.squareup.okhttp3 - okhttp - - org.projectlombok lombok @@ -114,6 +90,11 @@ joda-time + + it.ozimov + embedded-redis + + cc.iotkit model @@ -144,69 +125,74 @@ converter + + cc.iotkit + oauth2-server + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - - - org.apache.maven.plugins - maven-compiler-plugin - 3.0 - - 11 - 11 - UTF-8 - - + + + org.apache.maven.plugins + maven-compiler-plugin + 3.0 + + 11 + 11 + UTF-8 + + - - org.apache.maven.plugins - maven-assembly-plugin - - - cc.iotkit - standalone-package - ${project.version} - - - - false - - standalone-package - - - - - make-assembly - package - - single - - - - + + org.apache.maven.plugins + maven-assembly-plugin + + + cc.iotkit + standalone-package + ${project.version} + + + + false + + standalone-package + + + + + make-assembly + package + + single + + + + - - + + \ No newline at end of file diff --git a/manager/src/main/java/cc/iotkit/manager/Application.java b/manager/src/main/java/cc/iotkit/manager/Application.java index f296ebd0..363a9995 100755 --- a/manager/src/main/java/cc/iotkit/manager/Application.java +++ b/manager/src/main/java/cc/iotkit/manager/Application.java @@ -1,16 +1,25 @@ package cc.iotkit.manager; +import cc.iotkit.manager.config.EmbeddedElasticSearchConfig; +import cc.iotkit.manager.config.EmbeddedRedisConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; @Slf4j -@EnableFeignClients(basePackages = {"cc.iotkit.deviceapi"}) @SpringBootApplication(scanBasePackages = {"cc.iotkit"}) +@EnableWebMvc public class Application { public static void main(String[] args) { + if (EmbeddedElasticSearchConfig.embeddedEnable()) { + EmbeddedElasticSearchConfig.startEmbeddedElasticSearch(); + } + if (EmbeddedRedisConfig.embeddedEnable()) { + EmbeddedRedisConfig.startEmbeddedRedisServer(); + } + SpringApplication.run(Application.class, args); } diff --git a/manager/src/main/java/cc/iotkit/manager/config/AutoBeanConfig.java b/manager/src/main/java/cc/iotkit/manager/config/AutoBeanConfig.java1 similarity index 100% rename from manager/src/main/java/cc/iotkit/manager/config/AutoBeanConfig.java rename to manager/src/main/java/cc/iotkit/manager/config/AutoBeanConfig.java1 diff --git a/manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java b/manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java index bc3720fb..d302f6f5 100755 --- a/manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java +++ b/manager/src/main/java/cc/iotkit/manager/config/CacheConfig.java @@ -61,6 +61,16 @@ public class CacheConfig { Caffeine.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) .build() + ), new CaffeineCache( + Constants.USER_CACHE, + Caffeine.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build() + ), new CaffeineCache( + Constants.OAUTH_CLIENT_CACHE, + Caffeine.newBuilder() + .expireAfterWrite(5, TimeUnit.MINUTES) + .build() ) )); return manager; diff --git a/manager/src/main/java/cc/iotkit/manager/config/ElasticSearchConfig.java b/manager/src/main/java/cc/iotkit/manager/config/EmbeddedElasticSearchConfig.java similarity index 69% rename from manager/src/main/java/cc/iotkit/manager/config/ElasticSearchConfig.java rename to manager/src/main/java/cc/iotkit/manager/config/EmbeddedElasticSearchConfig.java index c1a9c370..955ac1e9 100755 --- a/manager/src/main/java/cc/iotkit/manager/config/ElasticSearchConfig.java +++ b/manager/src/main/java/cc/iotkit/manager/config/EmbeddedElasticSearchConfig.java @@ -1,42 +1,30 @@ package cc.iotkit.manager.config; import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.node.InternalSettingsPreparer; import org.elasticsearch.node.Node; import org.elasticsearch.transport.Netty4Plugin; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Component; import java.util.Collections; -@Slf4j -@Configuration -public class ElasticSearchConfig { +public class EmbeddedElasticSearchConfig { static { System.setProperty("es.set.netty.runtime.available.processors", "false"); } - @SneakyThrows - @Bean - public EmbeddedElasticSearch getEmbeddedElasticSearch(ConfigProperty configProperty) { - if (configProperty.enabled) { - EmbeddedElasticSearch embeddedElasticSearch = new EmbeddedElasticSearch(configProperty); - embeddedElasticSearch.start(); - return embeddedElasticSearch; - } - return null; + public static boolean embeddedEnable() { + return "true".equals(System.getProperty("embeddedElasticSearch")); } - @Component - @ConfigurationProperties(prefix = "elasticsearch.embedded") - public static class ConfigProperty { + @SneakyThrows + public static void startEmbeddedElasticSearch() { + EmbeddedElasticSearch embeddedElasticSearch = new EmbeddedElasticSearch(new ConfigProperty()); + embeddedElasticSearch.start(); + } - private boolean enabled; + public static class ConfigProperty { private String dataPath = "./data/elasticsearch"; diff --git a/manager/src/main/java/cc/iotkit/manager/config/EmbeddedRedisConfig.java b/manager/src/main/java/cc/iotkit/manager/config/EmbeddedRedisConfig.java new file mode 100755 index 00000000..837c2891 --- /dev/null +++ b/manager/src/main/java/cc/iotkit/manager/config/EmbeddedRedisConfig.java @@ -0,0 +1,16 @@ +package cc.iotkit.manager.config; + +import redis.embedded.RedisServer; + +public class EmbeddedRedisConfig { + + public static boolean embeddedEnable() { + return "true".equals(System.getProperty("embeddedRedisServer")); + } + + public static void startEmbeddedRedisServer() { + RedisServer redisServer = new RedisServer(); + redisServer.start(); + } + +} diff --git a/manager/src/main/java/cc/iotkit/manager/config/GlobalExceptionHandler.java b/manager/src/main/java/cc/iotkit/manager/config/GlobalExceptionHandler.java index 777c066a..f38a0bae 100755 --- a/manager/src/main/java/cc/iotkit/manager/config/GlobalExceptionHandler.java +++ b/manager/src/main/java/cc/iotkit/manager/config/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package cc.iotkit.manager.config; +import cn.dev33.satoken.exception.NotLoginException; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -18,7 +19,12 @@ public class GlobalExceptionHandler { @ResponseBody public RequestResult handleException(Exception e, HttpServletResponse response) { log.error("handler exception", e); - if(e.getMessage().contains("Unauthorized")){ + if (e instanceof NotLoginException) { + response.setStatus(401); + return new RequestResult("401", "未授权的请求"); + } + + if (e.getMessage().contains("Unauthorized")) { response.setStatus(403); return new RequestResult("403", "没有权限"); } diff --git a/manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java b/manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java1 similarity index 100% rename from manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java rename to manager/src/main/java/cc/iotkit/manager/config/KeycloakSecurityConfig.java1 diff --git a/manager/src/main/java/cc/iotkit/manager/config/ResponseResultHandler.java b/manager/src/main/java/cc/iotkit/manager/config/ResponseResultHandler.java index 5108cb33..49daa079 100755 --- a/manager/src/main/java/cc/iotkit/manager/config/ResponseResultHandler.java +++ b/manager/src/main/java/cc/iotkit/manager/config/ResponseResultHandler.java @@ -1,5 +1,6 @@ package cc.iotkit.manager.config; +import cn.dev33.satoken.util.SaResult; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -34,6 +35,9 @@ public class ResponseResultHandler implements ResponseBodyAdvice { GlobalExceptionHandler.RequestResult requestResult = (GlobalExceptionHandler.RequestResult) body; return new ApiResponse(Integer.parseInt(requestResult.getCode()), requestResult.getMessage(), "", System.currentTimeMillis()); + } else if (body instanceof SaResult) { + SaResult result = (SaResult) body; + return new ApiResponse(result.getCode(), result.getMsg(), result.getData(), System.currentTimeMillis()); } else if (body instanceof Map) { Map map = (Map) body; //spring mvc内部异常 diff --git a/manager/src/main/java/cc/iotkit/manager/config/SaTokenConfigure.java b/manager/src/main/java/cc/iotkit/manager/config/SaTokenConfigure.java new file mode 100755 index 00000000..856ec79d --- /dev/null +++ b/manager/src/main/java/cc/iotkit/manager/config/SaTokenConfigure.java @@ -0,0 +1,66 @@ +package cc.iotkit.manager.config; + +import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; +import cn.dev33.satoken.interceptor.SaRouteInterceptor; +import cn.dev33.satoken.router.SaRouter; +import cn.dev33.satoken.stp.StpUtil; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class SaTokenConfigure implements WebMvcConfigurer { + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 注册注解拦截器 + registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**"); + // 注册路由拦截器,自定义认证规则 + registry.addInterceptor(new SaRouteInterceptor((req, res, handler) -> { + System.out.println(req.getRequestPath()); + // 根据路由划分模块,不同模块不同鉴权 + SaRouter + //管理员、系统用户角色能使用的功能 + .match("/**") + .notMatch("/oauth2/**","/*.png").check(c -> StpUtil.checkRoleOr("iot_admin", "iot_system")) + //需要有可写权限的功能 + .match( + "/**/save*/**", + "/**/remove*/**", + "/**/del*/**", + "/**/add*/**", + "/**/clear*/**", + "/**/set*/**", + "/**/set", + "/**/invoke" + ).check(c -> StpUtil.checkPermission("write")) + //管理员、系统、客户端用户角色能使用的功能 + .match("/space/addSpace/**", + "/space/saveSpace/**", + "/space/delSpace/**", + "/space/saveHome/**", + "/space/currentHome/**", + "/space/myRecentDevices/**", + "/space/spaces/**", + "/space/myDevices/**", + "/space/findDevice/**", + "/space/addDevice/**", + "/space/saveDevice", + "/space/removeDevice", + "/space/device/*", + "/device/*/consumer/*", + "/device/*/service/property/set", + "/device/*/service/*/invoke" + ) + .check(c -> StpUtil.checkRoleOr("iot_admin", "iot_system", "iot_client")) + ; + })).addPathPatterns("/**") + .excludePathPatterns( + "/*.png", + "/oauth2/**", "/*.html", + "/favicon.ico", "/v2/api-docs", + "/webjars/**", "/swagger-resources/**", + "/*.js"); + } + +} diff --git a/manager/src/main/java/cc/iotkit/manager/config/SecurityConfig.java1 b/manager/src/main/java/cc/iotkit/manager/config/SecurityConfig.java1 new file mode 100755 index 00000000..2c6bf25f --- /dev/null +++ b/manager/src/main/java/cc/iotkit/manager/config/SecurityConfig.java1 @@ -0,0 +1,55 @@ +package cc.iotkit.manager.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; + +@Configuration +public class SecurityConfig extends WebSecurityConfigurerAdapter { + + @Value("${app.systemRole}") + private String systemRole; + + @Override + protected void configure(HttpSecurity http) throws Exception { +// super.configure(http); + http + .authorizeRequests() + .antMatchers("/oauth2/**", "/*.html", "/favicon.ico", "/v2/api-docs", "/webjars/**", "/swagger-resources/**", "/*.js").permitAll() + .antMatchers("/api/**").hasRole("iot_client_user") + .antMatchers("/aligenieDevice/invoke/**").hasRole("iot_client_user") + //客户端用户写权限 + .antMatchers("/space/addSpace/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/saveSpace/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/delSpace/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/saveHome/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/currentHome/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/myRecentDevices/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/spaces/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/myDevices/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/findDevice/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/addDevice/**").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/saveDevice").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/removeDevice").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/space/device/*").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/device/*/consumer/*").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/device/*/service/property/set").hasAnyRole("iot_write", "iot_client_user") + .antMatchers("/device/*/service/*/invoke").hasAnyRole("iot_write", "iot_client_user") + + + .antMatchers(HttpMethod.DELETE).hasRole("iot_write") + .antMatchers(HttpMethod.PUT).hasRole("iot_write") + .antMatchers("/**/save*/**").hasRole("iot_write") + .antMatchers("/**/remove*/**").hasRole("iot_write") + .antMatchers("/**/del*/**").hasRole("iot_write") + .antMatchers("/**/add*/**").hasRole("iot_write") + .antMatchers("/**/clear*/**").hasRole("iot_write") + .antMatchers("/**/set*/**").hasRole("iot_write") + .antMatchers("/**/set").hasRole("iot_write") + .antMatchers("/**/invoke").hasRole("iot_write") + .antMatchers("/**").hasAnyRole(systemRole) + .and().csrf().disable(); + } +} diff --git a/manager/src/main/java/cc/iotkit/manager/controller/ProductController.java b/manager/src/main/java/cc/iotkit/manager/controller/ProductController.java index ffcc79c6..6e2f6bfc 100755 --- a/manager/src/main/java/cc/iotkit/manager/controller/ProductController.java +++ b/manager/src/main/java/cc/iotkit/manager/controller/ProductController.java @@ -13,6 +13,7 @@ import cc.iotkit.model.product.Category; import cc.iotkit.model.product.Product; import cc.iotkit.model.product.ProductModel; import cc.iotkit.model.product.ThingModel; +import cn.dev33.satoken.annotation.SaCheckRole; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.PutObjectResult; @@ -23,7 +24,6 @@ import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -101,14 +101,14 @@ public class ProductController { return categoryRepository.findAll(); } - @PreAuthorize("hasRole('iot_admin')") + @SaCheckRole("iot_admin") @PostMapping("/saveCategory") public void saveCategory(Category cate) { cate.setCreateAt(System.currentTimeMillis()); categoryRepository.save(cate); } - @PreAuthorize("hasRole('iot_admin')") + @SaCheckRole("iot_admin") @PostMapping("/delCategory") public void delCategory(String id) { categoryRepository.deleteById(id); diff --git a/manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java b/manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java index 0c918fbf..1a0e4597 100755 --- a/manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java +++ b/manager/src/main/java/cc/iotkit/manager/controller/SpaceDeviceController.java @@ -16,7 +16,6 @@ import cc.iotkit.model.space.SpaceDevice; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; @@ -103,7 +102,6 @@ public class SpaceDeviceController { .build(); } - @PreAuthorize("hasRole('iot_system_user')") @GetMapping("/{userId}/devices") public List getDevices(@PathVariable("userId") String userId) { List spaceDevices = spaceDeviceRepository.findAll(Example.of(SpaceDevice.builder().uid(userId).build())); diff --git a/manager/src/main/java/cc/iotkit/manager/controller/ThirdAuthController.java b/manager/src/main/java/cc/iotkit/manager/controller/ThirdAuthController.java old mode 100644 new mode 100755 diff --git a/manager/src/main/java/cc/iotkit/manager/controller/UserInfoController.java b/manager/src/main/java/cc/iotkit/manager/controller/UserInfoController.java index 6b7d6737..c8e9cba5 100755 --- a/manager/src/main/java/cc/iotkit/manager/controller/UserInfoController.java +++ b/manager/src/main/java/cc/iotkit/manager/controller/UserInfoController.java @@ -2,17 +2,17 @@ package cc.iotkit.manager.controller; import cc.iotkit.common.Constants; import cc.iotkit.common.exception.BizException; +import cc.iotkit.common.utils.CodecUtil; import cc.iotkit.common.utils.ReflectUtil; import cc.iotkit.dao.AligenieDeviceRepository; import cc.iotkit.dao.UserInfoRepository; import cc.iotkit.manager.service.DataOwnerService; -import cc.iotkit.manager.service.KeycloakAdminService; import cc.iotkit.manager.service.PulsarAdminService; import cc.iotkit.manager.utils.AuthUtil; import cc.iotkit.model.UserInfo; +import cn.dev33.satoken.annotation.SaCheckRole; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.util.*; @@ -24,8 +24,6 @@ public class UserInfoController { @Value("${app.systemRole}") private String systemRole; - @Autowired - private KeycloakAdminService keycloakAdminService; @Autowired private UserInfoRepository userInfoRepository; @Autowired @@ -39,7 +37,7 @@ public class UserInfoController { /** * 平台用户列表 */ - @PreAuthorize("hasRole('iot_admin')") + @SaCheckRole("iot_admin") @GetMapping("/platform/users") public List getPlatformUsers() { return userInfoRepository.findByType(UserInfo.USER_TYPE_PLATFORM); @@ -54,18 +52,10 @@ public class UserInfoController { user.setId(UUID.randomUUID().toString()); user.setType(UserInfo.USER_TYPE_PLATFORM); user.setOwnerId(AuthUtil.getUserId()); - user.setRoles(Arrays.asList(Constants.ROLE_SYSTEM)); + user.setRoles(Collections.singletonList(Constants.ROLE_SYSTEM)); + user.setPermissions(Collections.singletonList(Constants.PERMISSION_WRITE)); user.setCreateAt(System.currentTimeMillis()); - UserInfo keycloakUser = keycloakAdminService.getUser(user.getUid()); - if (keycloakUser != null) { - user.setId(keycloakUser.getId()); - keycloakAdminService.updateUser(user); - } else { - keycloakAdminService.createUser(user, Constants.PWD_SYSTEM_USER); - } - if (!pulsarAdminService.tenantExists(user.getUid())) { - pulsarAdminService.createTenant(user.getUid()); - } + user.setSecret(CodecUtil.aesEncrypt(Constants.PWD_SYSTEM_USER, Constants.PWD_SYSTEM_USER)); userInfoRepository.save(user); } catch (Throwable e) { throw new BizException("add platform user error", e); @@ -84,25 +74,23 @@ public class UserInfoController { * 添加C端用户 */ @PostMapping("/client/user/add") - public void addClientUser(@RequestBody UserInfo user) { + public void addClientUser(@RequestBody UserInfo user) throws Exception { user.setType(UserInfo.USER_TYPE_CLIENT); user.setOwnerId(AuthUtil.getUserId()); user.setRoles(Collections.singletonList(Constants.ROLE_CLIENT)); user.setCreateAt(System.currentTimeMillis()); - String uid = keycloakAdminService.createUser(user, Constants.PWD_CLIENT_USER); - user.setId(uid); + user.setSecret(CodecUtil.aesEncrypt(Constants.PWD_CLIENT_USER, Constants.ACCOUNT_SECRET)); userInfoRepository.save(user); } @PostMapping("/client/user/{id}/delete") public void deleteClientUser(@PathVariable("id") String id) { Optional optUser = userInfoRepository.findById(id); - if (!optUser.isPresent()) { + if (optUser.isEmpty()) { throw new BizException("user does not exist"); } UserInfo user = optUser.get(); ownerService.checkOwner(user); - keycloakAdminService.deleteUser(id); userInfoRepository.deleteById(id); aligenieDeviceRepository.deleteByUid(user.getId()); } @@ -110,7 +98,7 @@ public class UserInfoController { @PostMapping("/client/user/save") public void saveClientUser(@RequestBody UserInfo user) { Optional userOpt = userInfoRepository.findById(user.getId()); - if (!userOpt.isPresent()) { + if (userOpt.isEmpty()) { return; } UserInfo oldUser = userOpt.get(); diff --git a/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieDeviceController.java b/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieDeviceController.java index 8039f0ba..257edeb7 100755 --- a/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieDeviceController.java +++ b/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieDeviceController.java @@ -1,8 +1,6 @@ package cc.iotkit.manager.controller.aligenie; -import cc.iotkit.common.Constants; import cc.iotkit.common.exception.BizException; -import cc.iotkit.common.utils.UniqueIdUtil; import cc.iotkit.dao.*; import cc.iotkit.manager.service.DataOwnerService; import cc.iotkit.manager.service.DeviceService; @@ -10,21 +8,13 @@ import cc.iotkit.model.UserInfo; import cc.iotkit.model.aligenie.AligenieDevice; import cc.iotkit.model.aligenie.AligenieProduct; import cc.iotkit.model.device.DeviceInfo; -import cc.iotkit.model.device.message.ThingModelMessage; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.pulsar.client.api.Producer; -import org.apache.pulsar.client.api.PulsarClient; -import org.apache.pulsar.client.api.PulsarClientException; -import org.apache.pulsar.client.impl.schema.JSONSchema; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; -import javax.annotation.PostConstruct; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; @Slf4j @@ -46,24 +36,9 @@ public class AligenieDeviceController { private DeviceService deviceService; @Autowired private DeviceDao deviceDao; - @Value("${app.aligenie.push.device}") - private String pushDevice; @Value("${pulsar.broker}") private String pulsarBrokerUrl; - private Producer deviceMessageProducer; - - @PostConstruct - public void init() throws PulsarClientException { - //初始化pulsar客户端 - PulsarClient client = PulsarClient.builder() - .serviceUrl(pulsarBrokerUrl) - .build(); - deviceMessageProducer = client.newProducer(JSONSchema.of(ThingModelMessage.class)) - .topic("persistent://iotkit/default/" + Constants.THING_MODEL_MESSAGE_TOPIC) - .create(); - } - @GetMapping("/list/{uid}") public List getDevices(@PathVariable("uid") String uid) { UserInfo user = userInfoRepository.findById(uid).get(); @@ -73,9 +48,9 @@ public class AligenieDeviceController { @PostMapping("/bind/{uid}") public void bind(@PathVariable("uid") String uid, - @RequestBody List devices) throws PulsarClientException { + @RequestBody List devices) { Optional optUser = userInfoRepository.findById(uid); - if (!optUser.isPresent()) { + if (optUser.isEmpty()) { throw new BizException("user does not exist"); } UserInfo user = optUser.get(); @@ -106,23 +81,6 @@ public class AligenieDeviceController { deviceDao.updateTag(device.getDeviceId(), new DeviceInfo.Tag("aligenie", "天猫精灵接入", "是")); } - - DeviceInfo deviceInfo = deviceRepository.findByDeviceId(pushDevice); - if (deviceInfo == null) { - return; - } - - Map uidData = new HashMap<>(); - uidData.put("uid", uid); - deviceMessageProducer.send(ThingModelMessage.builder() - .deviceId(pushDevice) - .productKey(deviceInfo.getProductKey()) - .deviceName(deviceInfo.getDeviceName()) - .type(ThingModelMessage.TYPE_EVENT) - .identifier("userDevicesChange") - .mid(UniqueIdUtil.newRequestId()) - .data(uidData) - .build()); } @Data diff --git a/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieProductController.java b/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieProductController.java index 5bce08a9..10f91330 100755 --- a/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieProductController.java +++ b/manager/src/main/java/cc/iotkit/manager/controller/aligenie/AligenieProductController.java @@ -1,19 +1,15 @@ package cc.iotkit.manager.controller.aligenie; import cc.iotkit.dao.AligenieProductRepository; -import cc.iotkit.manager.model.aligenie.AligenieProductVo; import cc.iotkit.manager.service.DataOwnerService; import cc.iotkit.manager.utils.AuthUtil; import cc.iotkit.model.aligenie.AligenieProduct; -import cc.iotkit.model.product.Product; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.domain.Example; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; import java.util.List; @RestController diff --git a/manager/src/main/java/cc/iotkit/manager/service/KeycloakAdminService.java b/manager/src/main/java/cc/iotkit/manager/service/KeycloakAdminService.java1 similarity index 100% rename from manager/src/main/java/cc/iotkit/manager/service/KeycloakAdminService.java rename to manager/src/main/java/cc/iotkit/manager/service/KeycloakAdminService.java1 diff --git a/manager/src/main/java/cc/iotkit/manager/utils/AuthUtil.java b/manager/src/main/java/cc/iotkit/manager/utils/AuthUtil.java index 9023263c..535d4074 100755 --- a/manager/src/main/java/cc/iotkit/manager/utils/AuthUtil.java +++ b/manager/src/main/java/cc/iotkit/manager/utils/AuthUtil.java @@ -1,33 +1,19 @@ package cc.iotkit.manager.utils; import cc.iotkit.common.Constants; -import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; +import cn.dev33.satoken.stp.StpUtil; +import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; public class AuthUtil { public static String getUserId() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - return ""; - } - if (authentication instanceof KeycloakAuthenticationToken) { - KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication; - return token.getName(); - } - return ""; + return StpUtil.getLoginId().toString(); } public static List getUserRoles() { - return SecurityContextHolder.getContext() - .getAuthentication() - .getAuthorities() - .stream().map((role) -> role.getAuthority().replace("ROLE_", "")) - .collect(Collectors.toList()); + return StpUtil.getRoleList(); } public static boolean isAdmin() { diff --git a/manager/src/main/resources/application-dev.yml b/manager/src/main/resources/application-dev.yml index 4b5a94ae..8f76d8d8 100755 --- a/manager/src/main/resources/application-dev.yml +++ b/manager/src/main/resources/application-dev.yml @@ -1,3 +1,6 @@ +server: + port: 8086 + spring: servlet: multipart: @@ -36,28 +39,21 @@ aliyun: accessKeyId: 填写阿里云accessKeyId accessKeySecret: 填写阿里云accessKeySecret -keycloak: - realm : 填写keycloak中定义的realm - resource : iotkit - auth-server-url : 填写keycloak认证地址 - ssl-required : external - use-resource-role-mappings : false - cors-max-age : 1000 - cors-allowed-methods : POST PUT DELETE GET - cors-exposed-headers : WWW-Authenticate - bearer-only : false - enable-basic-auth : false - expose-token : true - verify-token-audience : false - connection-pool-size : 20 - disable-trust-manager: true - allow-any-hostname : false - token-minimum-time-to-live : 10 - min-time-between-jwks-requests : 10 - -keycloak-admin-clientid : 填写keycloak中定义的clientId -keycloak-admin-user : 填写keycloak中添加的管理员用户名 -keycloak-admin-password : 填写keycloak中添加的管理员密码 +sa-token: + # token名称 (同时也是cookie名称) + token-name: satoken + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false pulsar: broker: pulsar://pulsar broker地址:6650 diff --git a/manager/src/main/resources/application.yml b/manager/src/main/resources/application.yml index 1188c402..3bcb8105 100755 --- a/manager/src/main/resources/application.yml +++ b/manager/src/main/resources/application.yml @@ -1,3 +1,6 @@ +server: + port: 8086 + spring: servlet: multipart: @@ -34,29 +37,22 @@ aliyun: accessKeyId: 填写阿里云accessKeyId accessKeySecret: 填写阿里云accessKeySecret -keycloak: - realm : 填写keycloak中定义的realm - resource : iotkit - auth-server-url : 填写keycloak认证地址 - ssl-required : external - use-resource-role-mappings : false - cors-max-age : 1000 - cors-allowed-methods : POST PUT DELETE GET - cors-exposed-headers : WWW-Authenticate - bearer-only : false - enable-basic-auth : false - expose-token : true - verify-token-audience : false - connection-pool-size : 20 - disable-trust-manager: true - allow-any-hostname : false - token-minimum-time-to-live : 10 - min-time-between-jwks-requests : 10 - -keycloak-admin-clientid : 填写keycloak中定义的clientId -keycloak-admin-user : 填写keycloak中添加的管理员用户名 -keycloak-admin-password : 填写keycloak中添加的管理员密码 - +sa-token: + # token名称 (同时也是cookie名称) + token-name: satoken + # token有效期,单位s 默认30天, -1代表永不过期 + timeout: 2592000 + # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 + activity-timeout: -1 + # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) + is-concurrent: true + # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) + is-share: false + # token风格 + token-style: uuid + # 是否输出操作日志 + is-log: false + pulsar: broker: pulsar://pulsar broker地址:6650 service: http://pulsar 服务地址:8080 diff --git a/model/pom.xml b/model/pom.xml index d5abe3ad..891c115f 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -5,9 +5,9 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 model diff --git a/model/src/main/java/cc/iotkit/model/OauthClient.java b/model/src/main/java/cc/iotkit/model/OauthClient.java new file mode 100755 index 00000000..2b892a3f --- /dev/null +++ b/model/src/main/java/cc/iotkit/model/OauthClient.java @@ -0,0 +1,25 @@ +package cc.iotkit.model; + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +/** + * oauth2的client + */ +@Data +@Document +public class OauthClient { + + @Id + private String clientId; + + private String name; + + private String clientSecret; + + private String allowUrl; + + private Long createAt; + +} diff --git a/model/src/main/java/cc/iotkit/model/Role.java b/model/src/main/java/cc/iotkit/model/Role.java new file mode 100755 index 00000000..0d4bdf7f --- /dev/null +++ b/model/src/main/java/cc/iotkit/model/Role.java @@ -0,0 +1,14 @@ +package cc.iotkit.model; + +import lombok.Data; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@Document +public class Role { + + private String id; + + private String name; + +} diff --git a/model/src/main/java/cc/iotkit/model/ThirdUserSession.java b/model/src/main/java/cc/iotkit/model/ThirdUserSession.java old mode 100644 new mode 100755 diff --git a/model/src/main/java/cc/iotkit/model/UserInfo.java b/model/src/main/java/cc/iotkit/model/UserInfo.java index 8e4e69d0..5fb22e59 100755 --- a/model/src/main/java/cc/iotkit/model/UserInfo.java +++ b/model/src/main/java/cc/iotkit/model/UserInfo.java @@ -35,6 +35,11 @@ public class UserInfo implements Owned { */ private String ownerId; + /** + * 密钥(密码加密后的内容) + */ + private String secret; + /** * 用户昵称 */ @@ -69,7 +74,12 @@ public class UserInfo implements Owned { /** * 角色 */ - private List roles; + private List roles = new ArrayList<>(); + + /** + * 权限 + */ + private List permissions = new ArrayList<>(); /** * 用户使用的平台 diff --git a/oauth2-server/pom.xml b/oauth2-server/pom.xml new file mode 100755 index 00000000..777ddcc4 --- /dev/null +++ b/oauth2-server/pom.xml @@ -0,0 +1,53 @@ + + + + iotkit-parent + cc.iotkit + 0.2.0-SNAPSHOT + + 4.0.0 + + oauth2-server + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + cn.dev33 + sa-token-oauth2 + + + + cn.dev33 + sa-token-spring-boot-starter + + + + cn.dev33 + sa-token-dao-redis-jackson + + + + com.ejlchina + okhttps + + + + org.projectlombok + lombok + + + + cc.iotkit + dao + + + + + \ No newline at end of file diff --git a/oauth2-server/src/main/java/cc/iotkit/oauth/controller/AuthClientController.java b/oauth2-server/src/main/java/cc/iotkit/oauth/controller/AuthClientController.java new file mode 100755 index 00000000..4fca1576 --- /dev/null +++ b/oauth2-server/src/main/java/cc/iotkit/oauth/controller/AuthClientController.java @@ -0,0 +1,131 @@ +package cc.iotkit.oauth.controller; + +import cc.iotkit.common.Constants; +import cc.iotkit.common.utils.CodecUtil; +import cc.iotkit.dao.OauthClientCache; +import cc.iotkit.dao.UserInfoCache; +import cc.iotkit.model.OauthClient; +import cc.iotkit.model.UserInfo; +import cc.iotkit.utils.SoMap; +import cn.dev33.satoken.stp.SaLoginConfig; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import com.ejlchina.okhttps.OkHttps; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +import javax.servlet.http.HttpServletRequest; + +@Slf4j +@RestController +@RequestMapping("/oauth2") +public class AuthClientController { + + @Value("${oauth2.auth-server-url}") + private String serverUrl; + + @Autowired + private OauthClientCache oauthClientCache; + @Autowired + private UserInfoCache userInfoCache; + + // 进入首页 + @RequestMapping("/") + public Object index(HttpServletRequest request) { + request.setAttribute("uid", StpUtil.getLoginIdDefaultNull()); + return new ModelAndView("index.html"); + } + + // 根据Code码进行登录,获取 Access-Token 和 openid + @RequestMapping("/codeLogin") + public SaResult codeLogin(String code, String clientId) { + OauthClient oauthClient = oauthClientCache.getClient(clientId); + if (oauthClient == null) { + return SaResult.error("clientId does not exist"); + } + String clientSecret = oauthClient.getClientSecret(); + + // 调用Server端接口,获取 Access-Token 以及其他信息 + String str = OkHttps.sync(serverUrl + "/oauth2/token") + .addBodyPara("grant_type", "authorization_code") + .addBodyPara("code", code) + .addBodyPara("client_id", clientId) + .addBodyPara("client_secret", clientSecret) + .post() + .getBody() + .toString(); + SoMap so = SoMap.getSoMap().setJsonString(str); + log.info("get token by code result:{}", so); + // code不等于200 代表请求失败 + if (so.getInt("code") != 200) { + return SaResult.error(so.getString("msg")); + } + + // 根据openid获取其对应的userId + SoMap data = so.getMap("data"); + String uid = getUserIdByOpenid(data.getString("openid")); + String access_token = data.getString("access_token"); + UserInfo userInfo = userInfoCache.getUserInfo(uid); + data.put("name", userInfo.getNickName()); + data.put("uid", uid); + + // 返回相关参数 + StpUtil.login(uid, SaLoginConfig.setToken(access_token)); + return SaResult.data(data); + } + + // 注销登录 + @RequestMapping("/logout") + public RedirectView logout(String accessToken, String redirect_uri) { + //先注销client中cookie的token + StpUtil.logout(); + //再注销web页面使用的token + StpUtil.logoutByTokenValue(accessToken); + + return new RedirectView(redirect_uri); + } + + // 根据 Access-Token 置换相关的资源: 获取账号昵称、头像、性别等信息 + @RequestMapping("/getUserinfo") + public SaResult getUserinfo(String accessToken) { + // 调用Server端接口,查询开放的资源 + String str = OkHttps.sync(serverUrl + "/oauth2/userinfo") + .addBodyPara("access_token", accessToken) + .post() + .getBody() + .toString(); + SoMap so = SoMap.getSoMap().setJsonString(str); + // code不等于200 代表请求失败 + if (so.getInt("code") != 200) { + return SaResult.error(so.getString("msg")); + } + + // 返回相关参数 (data=获取到的资源 ) + SoMap data = so.getMap("data"); + return SaResult.data(data); + } + + @GetMapping("/checkLogin") + public SaResult checkLogin() { + try { + StpUtil.checkLogin(); + } catch (Throwable e) { + return SaResult.error("no login"); + } + return SaResult.ok(); + } + + @SneakyThrows + private String getUserIdByOpenid(String openid) { + String clientIdLoginId = CodecUtil.aesDecrypt(openid, Constants.ACCOUNT_SECRET); + return clientIdLoginId.split(":")[1]; + } + +} diff --git a/oauth2-server/src/main/java/cc/iotkit/oauth/controller/AuthServerController.java b/oauth2-server/src/main/java/cc/iotkit/oauth/controller/AuthServerController.java new file mode 100755 index 00000000..ba1c6242 --- /dev/null +++ b/oauth2-server/src/main/java/cc/iotkit/oauth/controller/AuthServerController.java @@ -0,0 +1,97 @@ +package cc.iotkit.oauth.controller; + +import cc.iotkit.common.Constants; +import cc.iotkit.common.utils.CodecUtil; +import cc.iotkit.dao.UserInfoRepository; +import cc.iotkit.model.UserInfo; +import cc.iotkit.oauth.service.TokenRequestHandler; +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.logic.SaOAuth2Util; +import cn.dev33.satoken.stp.StpUtil; +import cn.dev33.satoken.util.SaResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +@RestController +public class AuthServerController { + + @Autowired + private UserInfoRepository userInfoRepository; + + // 处理所有OAuth相关请求 + @RequestMapping("/oauth2/*") + public Object request() { + return TokenRequestHandler.serverRequest(); + } + + // Sa-OAuth2 定制化配置 + @Autowired + public void setSaOAuth2Config(SaOAuth2Config cfg) { + cfg. + // 未登录的视图 + setNotLoginView(() -> new ModelAndView("login.html")). + // 登录处理函数 + setDoLoginHandle((name, pwd) -> { + try { + UserInfo userInfo = userInfoRepository.findByUid(name); + if (userInfo != null) { + String secret = userInfo.getSecret(); + String encodePwd = CodecUtil.aesEncrypt(pwd, Constants.ACCOUNT_SECRET); + if (encodePwd.equals(secret)) { + StpUtil.login(userInfo.getId(), "PC"); + return SaResult.ok(); + } + } + } catch (Throwable e) { + return SaResult.error("账号名或密码错误"); + } + return SaResult.error("账号名或密码错误"); + }). + // 授权确认视图 + setConfirmView((clientId, scope) -> { + Map map = new HashMap<>(); + map.put("clientId", clientId); + map.put("scope", scope); + return new ModelAndView("confirm.html", map); + }) + ; + } + + // 全局异常拦截 + @ExceptionHandler + public SaResult handlerException(Exception e) { + e.printStackTrace(); + return SaResult.error(e.getMessage()); + } + + // ---------- 开放相关资源接口: Client端根据 Access-Token ,置换相关资源 ------------ + + // 获取Userinfo信息:昵称、头像、性别等等 + @RequestMapping("/oauth2/userinfo") + public SaResult userinfo() { + // 获取 Access-Token 对应的账号id + String accessToken = SaHolder.getRequest().getParamNotNull("access_token"); + Object loginId = SaOAuth2Util.getLoginIdByAccessToken(accessToken); + System.out.println("-------- 此Access-Token对应的账号id: " + loginId); + + // 校验 Access-Token 是否具有权限: userinfo + SaOAuth2Util.checkScope(accessToken, "userinfo"); + + // 模拟账号信息 (真实环境需要查询数据库获取信息) + Map map = new LinkedHashMap<>(); + map.put("nickname", "shengzhang_"); + map.put("avatar", "http://xxx.com/1.jpg"); + map.put("age", "18"); + map.put("sex", "男"); + map.put("address", "山东省 青岛市 城阳区"); + return SaResult.data(map); + } +} diff --git a/oauth2-server/src/main/java/cc/iotkit/oauth/service/SaOAuth2TemplateImpl.java b/oauth2-server/src/main/java/cc/iotkit/oauth/service/SaOAuth2TemplateImpl.java new file mode 100755 index 00000000..1a266abb --- /dev/null +++ b/oauth2-server/src/main/java/cc/iotkit/oauth/service/SaOAuth2TemplateImpl.java @@ -0,0 +1,51 @@ +package cc.iotkit.oauth.service; + +import cc.iotkit.common.Constants; +import cc.iotkit.common.utils.CodecUtil; +import cc.iotkit.dao.OauthClientCache; +import cc.iotkit.model.OauthClient; +import cn.dev33.satoken.oauth2.logic.SaOAuth2Template; +import cn.dev33.satoken.oauth2.model.SaClientModel; +import cn.dev33.satoken.stp.StpUtil; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class SaOAuth2TemplateImpl extends SaOAuth2Template { + + @Autowired + private OauthClientCache oauthClientCache; + + // 根据 id 获取 Client 信息 + @Override + public SaClientModel getClientModel(String clientId) { + OauthClient client = oauthClientCache.getClient(clientId); + if (client == null) { + return null; + } + + + return new SaClientModel() + .setClientId(client.getClientId()) + .setClientSecret(client.getClientSecret()) + .setAllowUrl(client.getAllowUrl()) + .setContractScope("userinfo") + .setIsAutoMode(true); + } + + // 根据ClientId 和 LoginId 获取openid + @SneakyThrows + @Override + public String getOpenid(String clientId, Object loginId) { + // 此为模拟数据,真实环境需要从数据库查询 + return CodecUtil.aesEncrypt(clientId + ":" + loginId, Constants.ACCOUNT_SECRET); + } + + @Override + public String randomAccessToken(String clientId, Object loginId, String scope) { + return StpUtil.createLoginSession(loginId); + } + + +} diff --git a/oauth2-server/src/main/java/cc/iotkit/oauth/service/StpInterfaceImpl.java b/oauth2-server/src/main/java/cc/iotkit/oauth/service/StpInterfaceImpl.java new file mode 100755 index 00000000..b4909649 --- /dev/null +++ b/oauth2-server/src/main/java/cc/iotkit/oauth/service/StpInterfaceImpl.java @@ -0,0 +1,38 @@ +package cc.iotkit.oauth.service; + +import cc.iotkit.dao.UserInfoCache; +import cc.iotkit.model.UserInfo; +import cn.dev33.satoken.stp.StpInterface; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class StpInterfaceImpl implements StpInterface { + + @Autowired + private UserInfoCache userInfoCache; + + /** + * 返回一个账号所拥有的权限码集合 + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + // 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限 + List list = new ArrayList(); + list.add("write"); + return list; + } + + /** + * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验) + */ + @Override + public List getRoleList(Object loginId, String loginType) { + UserInfo userInfo = userInfoCache.getUserInfo(loginId.toString()); + return userInfo.getRoles(); + } + +} \ No newline at end of file diff --git a/oauth2-server/src/main/java/cc/iotkit/oauth/service/TokenRequestHandler.java b/oauth2-server/src/main/java/cc/iotkit/oauth/service/TokenRequestHandler.java new file mode 100755 index 00000000..3d747281 --- /dev/null +++ b/oauth2-server/src/main/java/cc/iotkit/oauth/service/TokenRequestHandler.java @@ -0,0 +1,62 @@ +package cc.iotkit.oauth.service; + +import cn.dev33.satoken.context.SaHolder; +import cn.dev33.satoken.context.model.SaRequest; +import cn.dev33.satoken.context.model.SaResponse; +import cn.dev33.satoken.oauth2.SaOAuth2Manager; +import cn.dev33.satoken.oauth2.config.SaOAuth2Config; +import cn.dev33.satoken.oauth2.exception.SaOAuth2Exception; +import cn.dev33.satoken.oauth2.logic.SaOAuth2Consts; +import cn.dev33.satoken.oauth2.logic.SaOAuth2Handle; +import cn.dev33.satoken.oauth2.model.SaClientModel; + +public class TokenRequestHandler { + + public static Object serverRequest() { + SaRequest req = SaHolder.getRequest(); + SaResponse res = SaHolder.getResponse(); + SaOAuth2Config cfg = SaOAuth2Manager.getConfig(); + SaClientModel cm; + if (req.isPath(SaOAuth2Consts.Api.authorize) && req.isParam(SaOAuth2Consts.Param.response_type, SaOAuth2Consts.ResponseType.code)) { + cm = SaOAuth2Handle.currClientModel(); + if (!cfg.getIsCode() || !cm.isCode && !cm.isAutoMode) { + throw new SaOAuth2Exception("暂未开放的授权模式"); + } else { + return SaOAuth2Handle.authorize(req, res, cfg); + } + } else if (req.isPath(SaOAuth2Consts.Api.token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.authorization_code)) { + return SaOAuth2Handle.token(req, res, cfg); + } else if (req.isPath(SaOAuth2Consts.Api.token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.refresh_token)) { + return SaOAuth2Handle.refreshToken(req); + } else if (req.isPath(SaOAuth2Consts.Api.revoke)) { + return SaOAuth2Handle.revokeToken(req); + } else if (req.isPath(SaOAuth2Consts.Api.doLogin)) { + return SaOAuth2Handle.doLogin(req, res, cfg); + } else if (req.isPath(SaOAuth2Consts.Api.doConfirm)) { + return SaOAuth2Handle.doConfirm(req); + } else if (req.isPath(SaOAuth2Consts.Api.authorize) && req.isParam(SaOAuth2Consts.Param.response_type, SaOAuth2Consts.ResponseType.token)) { + cm = SaOAuth2Handle.currClientModel(); + if (!cfg.getIsImplicit() || !cm.isImplicit && !cm.isAutoMode) { + throw new SaOAuth2Exception("暂未开放的授权模式"); + } else { + return SaOAuth2Handle.authorize(req, res, cfg); + } + } else if (req.isPath(SaOAuth2Consts.Api.token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.password)) { + cm = SaOAuth2Handle.currClientModel(); + if (!cfg.getIsPassword() || !cm.isPassword && !cm.isAutoMode) { + throw new SaOAuth2Exception("暂未开放的授权模式"); + } else { + return SaOAuth2Handle.password(req, res, cfg); + } + } else if (req.isPath(SaOAuth2Consts.Api.token) && req.isParam(SaOAuth2Consts.Param.grant_type, SaOAuth2Consts.GrantType.client_credentials)) { + cm = SaOAuth2Handle.currClientModel(); + if (!cfg.getIsClient() || !cm.isClient && !cm.isAutoMode) { + throw new SaOAuth2Exception("暂未开放的授权模式"); + } else { + return SaOAuth2Handle.clientToken(req, res, cfg); + } + } else { + return "{\"msg\": \"not handle\"}"; + } + } +} diff --git a/oauth2-server/src/main/java/cc/iotkit/utils/SoMap.java b/oauth2-server/src/main/java/cc/iotkit/utils/SoMap.java new file mode 100755 index 00000000..06ed14bd --- /dev/null +++ b/oauth2-server/src/main/java/cc/iotkit/utils/SoMap.java @@ -0,0 +1,737 @@ +package cc.iotkit.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Map< String, Object> 是最常用的一种Map类型,但是它写着麻烦 + *

所以特封装此类,继承Map,进行一些扩展,可以让Map更灵活使用 + *

最新:2020-12-10 新增部分构造方法 + * @author kong + */ +public class SoMap extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + + public SoMap() { + } + + /** 以下元素会在isNull函数中被判定为Null, */ + public static final Object[] NULL_ELEMENT_ARRAY = {null, ""}; + public static final List NULL_ELEMENT_LIST; + + static { + NULL_ELEMENT_LIST = Arrays.asList(NULL_ELEMENT_ARRAY); + } + + // ============================= 读值 ============================= + + /** 获取一个值 */ + @Override + public Object get(Object key) { + if("this".equals(key)) { + return this; + } + return super.get(key); + } + + /** 如果为空,则返回默认值 */ + public Object get(Object key, Object defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return value; + } + + /** 转为String并返回 */ + public String getString(String key) { + Object value = get(key); + if(value == null) { + return null; + } + return String.valueOf(value); + } + + /** 如果为空,则返回默认值 */ + public String getString(String key, String defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return String.valueOf(value); + } + + /** 转为int并返回 */ + public int getInt(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0; + } + return Integer.valueOf(String.valueOf(value)); + } + /** 转为int并返回,同时指定默认值 */ + public int getInt(String key, int defaultValue) { + Object value = get(key); + if(valueIsNull(value)) { + return defaultValue; + } + return Integer.valueOf(String.valueOf(value)); + } + + /** 转为long并返回 */ + public long getLong(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0; + } + return Long.valueOf(String.valueOf(value)); + } + + /** 转为double并返回 */ + public double getDouble(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return 0.0; + } + return Double.valueOf(String.valueOf(value)); + } + + /** 转为boolean并返回 */ + public boolean getBoolean(String key) { + Object value = get(key); + if(valueIsNull(value)) { + return false; + } + return Boolean.valueOf(String.valueOf(value)); + } + + /** 转为Date并返回,根据自定义格式 */ + public Date getDateByFormat(String key, String format) { + try { + return new SimpleDateFormat(format).parse(getString(key)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** 转为Date并返回,根据格式: yyyy-MM-dd */ + public Date getDate(String key) { + return getDateByFormat(key, "yyyy-MM-dd"); + } + + /** 转为Date并返回,根据格式: yyyy-MM-dd HH:mm:ss */ + public Date getDateTime(String key) { + return getDateByFormat(key, "yyyy-MM-dd HH:mm:ss"); + } + + /** 转为Map并返回 */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public SoMap getMap(String key) { + Object value = get(key); + if(value == null) { + return SoMap.getSoMap(); + } + if(value instanceof Map) { + return SoMap.getSoMap((Map)value); + } + if(value instanceof String) { + return SoMap.getSoMap().setJsonString((String)value); + } + throw new RuntimeException("值无法转化为SoMap: " + value); + } + + /** 获取集合(必须原先就是个集合,否则会创建个新集合并返回) */ + @SuppressWarnings("unchecked") + public List getList(String key) { + Object value = get(key); + List list = null; + if(value == null || value.equals("")) { + list = new ArrayList(); + } + else if(value instanceof List) { + list = (List)value; + } else { + list = new ArrayList(); + list.add(value); + } + return list; + } + + /** 获取集合 (指定泛型类型) */ + public List getList(String key, Class cs) { + List list = getList(key); + List list2 = new ArrayList(); + for (Object obj : list) { + T objC = getValueByClass(obj, cs); + list2.add(objC); + } + return list2; + } + + /** 获取集合(逗号分隔式),(指定类型) */ + public List getListByComma(String key, Class cs) { + String listStr = getString(key); + if(listStr == null || listStr.equals("")) { + return new ArrayList<>(); + } + // 开始转化 + String [] arr = listStr.split(","); + List list = new ArrayList(); + for (String str : arr) { + if(cs == int.class || cs == Integer.class || cs == long.class || cs == Long.class) { + str = str.trim(); + } + T objC = getValueByClass(str, cs); + list.add(objC); + } + return list; + } + + + /** 根据指定类型从map中取值,返回实体对象 */ + public T getModel(Class cs) { + try { + return getModelByObject(cs.newInstance()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** 从map中取值,塞到一个对象中 */ + public T getModelByObject(T obj) { + // 获取类型 + Class cs = obj.getClass(); + // 循环复制 + for (Field field : cs.getDeclaredFields()) { + try { + // 获取对象 + Object value = this.get(field.getName()); + if(value == null) { + continue; + } + field.setAccessible(true); + Object valueConvert = getValueByClass(value, field.getType()); + field.set(obj, valueConvert); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException("属性取值出错:" + field.getName(), e); + } + } + return obj; + } + + + + /** + * 将指定值转化为指定类型并返回 + * @param obj + * @param cs + * @param + * @return + */ + @SuppressWarnings("unchecked") + public static T getValueByClass(Object obj, Class cs) { + String obj2 = String.valueOf(obj); + Object obj3 = null; + if (cs.equals(String.class)) { + obj3 = obj2; + } else if (cs.equals(int.class) || cs.equals(Integer.class)) { + obj3 = Integer.valueOf(obj2); + } else if (cs.equals(long.class) || cs.equals(Long.class)) { + obj3 = Long.valueOf(obj2); + } else if (cs.equals(short.class) || cs.equals(Short.class)) { + obj3 = Short.valueOf(obj2); + } else if (cs.equals(byte.class) || cs.equals(Byte.class)) { + obj3 = Byte.valueOf(obj2); + } else if (cs.equals(float.class) || cs.equals(Float.class)) { + obj3 = Float.valueOf(obj2); + } else if (cs.equals(double.class) || cs.equals(Double.class)) { + obj3 = Double.valueOf(obj2); + } else if (cs.equals(boolean.class) || cs.equals(Boolean.class)) { + obj3 = Boolean.valueOf(obj2); + } else { + obj3 = (T)obj; + } + return (T)obj3; + } + + + // ============================= 写值 ============================= + + /** + * 给指定key添加一个默认值(只有在这个key原来无值的情况先才会set进去) + */ + public void setDefaultValue(String key, Object defaultValue) { + if(isNull(key)) { + set(key, defaultValue); + } + } + + /** set一个值,连缀风格 */ + public SoMap set(String key, Object value) { + // 防止敏感key + if(key.toLowerCase().equals("this")) { + return this; + } + put(key, value); + return this; + } + + /** 将一个Map塞进SoMap */ + public SoMap setMap(Map map) { + if(map != null) { + for (String key : map.keySet()) { + this.set(key, map.get(key)); + } + } + return this; + } + + /** 将一个对象解析塞进SoMap */ + public SoMap setModel(Object model) { + if(model == null) { + return this; + } + Field[] fields = model.getClass().getDeclaredFields(); + for (Field field : fields) { + try{ + field.setAccessible(true); + boolean isStatic = Modifier.isStatic(field.getModifiers()); + if(!isStatic) { + this.set(field.getName(), field.get(model)); + } + }catch (Exception e){ + throw new RuntimeException(e); + } + } + return this; + } + + /** 将json字符串解析后塞进SoMap */ + public SoMap setJsonString(String jsonString) { + try { + @SuppressWarnings("unchecked") + Map map = new ObjectMapper().readValue(jsonString, Map.class); + return this.setMap(map); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + + // ============================= 删值 ============================= + + /** delete一个值,连缀风格 */ + public SoMap delete(String key) { + remove(key); + return this; + } + + /** 清理所有value为null的字段 */ + public SoMap clearNull() { + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(this.isNull(key)) { + iterator.remove(); + this.remove(key); + } + + } + return this; + } + /** 清理指定key */ + public SoMap clearIn(String ...keys) { + List keys2 = Arrays.asList(keys); + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(keys2.contains(key) == true) { + iterator.remove(); + this.remove(key); + } + } + return this; + } + /** 清理掉不在列表中的key */ + public SoMap clearNotIn(String ...keys) { + List keys2 = Arrays.asList(keys); + Iterator iterator = this.keySet().iterator(); + while(iterator.hasNext()) { + String key = iterator.next(); + if(keys2.contains(key) == false) { + iterator.remove(); + this.remove(key); + } + + } + return this; + } + /** 清理掉所有key */ + public SoMap clearAll() { + clear(); + return this; + } + + + // ============================= 快速构建 ============================= + + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap() { + return new SoMap(); + } + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap(String key, Object value) { + return new SoMap().set(key, value); + } + /** 构建一个SoMap并返回 */ + public static SoMap getSoMap(Map map) { + return new SoMap().setMap(map); + } + + /** 将一个对象集合解析成为SoMap */ + public static SoMap getSoMapByModel(Object model) { + return SoMap.getSoMap().setModel(model); + } + + /** 将一个对象集合解析成为SoMap集合 */ + public static List getSoMapByList(List list) { + List listMap = new ArrayList(); + for (Object model : list) { + listMap.add(getSoMapByModel(model)); + } + return listMap; + } + + /** 克隆指定key,返回一个新的SoMap */ + public SoMap cloneKeys(String... keys) { + SoMap so = new SoMap(); + for (String key : keys) { + so.set(key, this.get(key)); + } + return so; + } + /** 克隆所有key,返回一个新的SoMap */ + public SoMap cloneSoMap() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key, this.get(key)); + } + return so; + } + + /** 将所有key转为大写 */ + public SoMap toUpperCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key.toUpperCase(), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key转为小写 */ + public SoMap toLowerCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(key.toLowerCase(), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中下划线转为中划线模式 (kebab-case风格) */ + public SoMap toKebabCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordEachKebabCase(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中下划线转为小驼峰模式 */ + public SoMap toHumpCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordEachBigFs(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + /** 将所有key中小驼峰转为下划线模式 */ + public SoMap humpToLineCase() { + SoMap so = new SoMap(); + for (String key : this.keySet()) { + so.set(wordHumpToLine(key), this.get(key)); + } + this.clearAll().setMap(so); + return this; + } + + + + + // ============================= 辅助方法 ============================= + + + /** 指定key是否为null,判定标准为 NULL_ELEMENT_ARRAY 中的元素 */ + public boolean isNull(String key) { + return valueIsNull(get(key)); + } + + /** 指定key列表中是否包含value为null的元素,只要有一个为null,就会返回true */ + public boolean isContainNull(String ...keys) { + for (String key : keys) { + if(this.isNull(key)) { + return true; + } + } + return false; + } + + /** 与isNull()相反 */ + public boolean isNotNull(String key) { + return !isNull(key); + } + /** 指定key的value是否为null,作用同isNotNull() */ + public boolean has(String key) { + return !isNull(key); + } + + /** 指定value在此SoMap的判断标准中是否为null */ + public boolean valueIsNull(Object value) { + return NULL_ELEMENT_LIST.contains(value); + } + + /** 验证指定key不为空,为空则抛出异常 */ + public SoMap checkNull(String ...keys) { + for (String key : keys) { + if(this.isNull(key)) { + throw new RuntimeException("参数" + key + "不能为空"); + } + } + return this; + } + + static Pattern patternNumber = Pattern.compile("[0-9]*"); + /** 指定key是否为数字 */ + public boolean isNumber(String key) { + String value = getString(key); + if(value == null) { + return false; + } + return patternNumber.matcher(value).matches(); + } + + + + + /** + * 转为JSON字符串 + */ + public String toJsonString() { + try { +// SoMap so = SoMap.getSoMap(this); + return new ObjectMapper().writeValueAsString(this); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +// /** +// * 转为JSON字符串, 带格式的 +// */ +// public String toJsonFormatString() { +// try { +// return JSON.toJSONString(this, true); +// } catch (Exception e) { +// throw new RuntimeException(e); +// } +// } + + // ============================= web辅助 ============================= + + + /** + * 返回当前request请求的的所有参数 + * @return + */ + public static SoMap getRequestSoMap() { + // 大善人SpringMVC提供的封装 + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + if(servletRequestAttributes == null) { + throw new RuntimeException("当前线程非JavaWeb环境"); + } + // 当前request + HttpServletRequest request = servletRequestAttributes.getRequest(); + if (request.getAttribute("currentSoMap") == null || request.getAttribute("currentSoMap") instanceof SoMap == false ) { + initRequestSoMap(request); + } + return (SoMap)request.getAttribute("currentSoMap"); + } + + /** 初始化当前request的 SoMap */ + private static void initRequestSoMap(HttpServletRequest request) { + SoMap soMap = new SoMap(); + Map parameterMap = request.getParameterMap(); // 获取所有参数 + for (String key : parameterMap.keySet()) { + try { + String[] values = parameterMap.get(key); // 获得values + if(values.length == 1) { + soMap.set(key, values[0]); + } else { + List list = new ArrayList(); + for (String v : values) { + list.add(v); + } + soMap.set(key, list); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + request.setAttribute("currentSoMap", soMap); + } + + /** + * 验证返回当前线程是否为JavaWeb环境 + * @return + */ + public static boolean isJavaWeb() { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// 大善人SpringMVC提供的封装 + if(servletRequestAttributes == null) { + return false; + } + return true; + } + + + + // ============================= 常见key (以下key经常用,所以封装以下,方便写代码) ============================= + + /** get 当前页 */ + public int getKeyPageNo() { + int pageNo = getInt("pageNo", 1); + if(pageNo <= 0) { + pageNo = 1; + } + return pageNo; + } + /** get 页大小 */ + public int getKeyPageSize() { + int pageSize = getInt("pageSize", 10); + if(pageSize <= 0 || pageSize > 1000) { + pageSize = 10; + } + return pageSize; + } + + /** get 排序方式 */ + public int getKeySortType() { + return getInt("sortType"); + } + + + + + + + // ============================= 工具方法 ============================= + + + /** + * 将一个一维集合转换为树形集合 + * @param list 集合 + * @param idKey id标识key + * @param parentIdKey 父id标识key + * @param childListKey 子节点标识key + * @return 转换后的tree集合 + */ + public static List listToTree(List list, String idKey, String parentIdKey, String childListKey) { + // 声明新的集合,存储tree形数据 + List newTreeList = new ArrayList(); + // 声明hash-Map,方便查找数据 + SoMap hash = new SoMap(); + // 将数组转为Object的形式,key为数组中的id + for (int i = 0; i < list.size(); i++) { + SoMap json = (SoMap) list.get(i); + hash.put(json.getString(idKey), json); + } + // 遍历结果集 + for (int j = 0; j < list.size(); j++) { + // 单条记录 + SoMap aVal = (SoMap) list.get(j); + // 在hash中取出key为单条记录中pid的值 + SoMap hashVp = (SoMap) hash.get(aVal.get(parentIdKey, "").toString()); + // 如果记录的pid存在,则说明它有父节点,将她添加到孩子节点的集合中 + if (hashVp != null) { + // 检查是否有child属性,有则添加,没有则新建 + if (hashVp.get(childListKey) != null) { + @SuppressWarnings("unchecked") + List ch = (List) hashVp.get(childListKey); + ch.add(aVal); + hashVp.put(childListKey, ch); + } else { + List ch = new ArrayList(); + ch.add(aVal); + hashVp.put(childListKey, ch); + } + } else { + newTreeList.add(aVal); + } + } + return newTreeList; + } + + + + /** 指定字符串的字符串下划线转大写模式 */ + private static String wordEachBig(String str){ + String newStr = ""; + for (String s : str.split("_")) { + newStr += wordFirstBig(s); + } + return newStr; + } + /** 返回下划线转小驼峰形式 */ + private static String wordEachBigFs(String str){ + return wordFirstSmall(wordEachBig(str)); + } + + /** 将指定单词首字母大写 */ + private static String wordFirstBig(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1, str.length()); + } + + /** 将指定单词首字母小写 */ + private static String wordFirstSmall(String str) { + return str.substring(0, 1).toLowerCase() + str.substring(1, str.length()); + } + + /** 下划线转中划线 */ + private static String wordEachKebabCase(String str) { + return str.replaceAll("_", "-"); + } + + /** 驼峰转下划线 */ + private static String wordHumpToLine(String str) { + return str.replaceAll("[A-Z]", "_$0").toLowerCase(); + } + + +} diff --git a/oauth2-server/src/main/resources/templates/confirm.html b/oauth2-server/src/main/resources/templates/confirm.html new file mode 100755 index 00000000..92495e9e --- /dev/null +++ b/oauth2-server/src/main/resources/templates/confirm.html @@ -0,0 +1,117 @@ + + + + + 奇特物联认证授权 + + + +
+
+ + + + + + diff --git a/oauth2-server/src/main/resources/templates/login.html b/oauth2-server/src/main/resources/templates/login.html new file mode 100755 index 00000000..d9e0209a --- /dev/null +++ b/oauth2-server/src/main/resources/templates/login.html @@ -0,0 +1,123 @@ + + + + + 奇特物联登录授权 + + + +
+
+ + + + + + diff --git a/pom.xml b/pom.xml index 2fae0fa9..8a95a4a7 100755 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ dao protocol-gateway standalone-package + oauth2-server org.springframework.boot @@ -21,13 +22,13 @@ cc.iotkit iotkit-parent - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT iotkit-parent iotkit parent 11 - 17.0.0 4.2.2 + 1.30.0 @@ -96,21 +97,21 @@ - org.keycloak - keycloak-spring-boot-starter - ${keycloak-spring.version} + cn.dev33 + sa-token-oauth2 + ${sa-token.version} - org.keycloak - keycloak-spring-security-adapter - ${keycloak-spring.version} + cn.dev33 + sa-token-spring-boot-starter + ${sa-token.version} - org.keycloak - keycloak-admin-client - ${keycloak-spring.version} + cn.dev33 + sa-token-dao-redis-jackson + ${sa-token.version} @@ -179,6 +180,24 @@ 3.0.1 + + it.ozimov + embedded-redis + 0.7.3 + + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.17.1 + + + + com.ejlchina + okhttps + 3.1.1 + + io.vertx vertx-core @@ -257,6 +276,12 @@ ${project.version} + + cc.iotkit + oauth2-server + ${project.version} + + diff --git a/protocol-gateway/component-server/pom.xml b/protocol-gateway/component-server/pom.xml index fd23f733..71221cc5 100755 --- a/protocol-gateway/component-server/pom.xml +++ b/protocol-gateway/component-server/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/protocol-gateway/component/pom.xml b/protocol-gateway/component/pom.xml index 85ede8d1..fabd10b0 100755 --- a/protocol-gateway/component/pom.xml +++ b/protocol-gateway/component/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/protocol-gateway/converter/pom.xml b/protocol-gateway/converter/pom.xml index e47e47f8..fb621ecc 100755 --- a/protocol-gateway/converter/pom.xml +++ b/protocol-gateway/converter/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/protocol-gateway/emqx-component/dependency-reduced-pom.xml b/protocol-gateway/emqx-component/dependency-reduced-pom.xml old mode 100644 new mode 100755 index b1d6e872..06486c93 --- a/protocol-gateway/emqx-component/dependency-reduced-pom.xml +++ b/protocol-gateway/emqx-component/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 emqx-component @@ -61,19 +61,19 @@ cc.iotkit model - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile cc.iotkit common - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile cc.iotkit component - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile diff --git a/protocol-gateway/emqx-component/pom.xml b/protocol-gateway/emqx-component/pom.xml index 679e464d..55c03d46 100755 --- a/protocol-gateway/emqx-component/pom.xml +++ b/protocol-gateway/emqx-component/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/protocol-gateway/http-biz-component/dependency-reduced-pom.xml b/protocol-gateway/http-biz-component/dependency-reduced-pom.xml old mode 100644 new mode 100755 index 3c92d399..e348a2f8 --- a/protocol-gateway/http-biz-component/dependency-reduced-pom.xml +++ b/protocol-gateway/http-biz-component/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 http-biz-component @@ -58,7 +58,7 @@ cc.iotkit component - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile diff --git a/protocol-gateway/http-biz-component/pom.xml b/protocol-gateway/http-biz-component/pom.xml index 195d1797..1d6523b9 100755 --- a/protocol-gateway/http-biz-component/pom.xml +++ b/protocol-gateway/http-biz-component/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/protocol-gateway/mqtt-client-simulator/pom.xml b/protocol-gateway/mqtt-client-simulator/pom.xml index 980d7a3b..9d265ede 100755 --- a/protocol-gateway/mqtt-client-simulator/pom.xml +++ b/protocol-gateway/mqtt-client-simulator/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 mqtt客户端模拟器 diff --git a/protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/Application.java b/protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/Application.java index 0ca63986..b683752f 100755 --- a/protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/Application.java +++ b/protocol-gateway/mqtt-client-simulator/src/main/java/cc/iotkit/simulator/Application.java @@ -11,8 +11,8 @@ public class Application { public static void main(String[] args) throws IOException { if (args.length == 0) { -// Mqtt.broker = "tcp://127.0.0.1:1883"; - Mqtt.broker = "tcp://120.76.96.206:1883"; + Mqtt.broker = "tcp://127.0.0.1:1883"; +// Mqtt.broker = "tcp://120.76.96.206:1883"; } else { Mqtt.broker = args[0]; } diff --git a/protocol-gateway/mqtt-component/dependency-reduced-pom.xml b/protocol-gateway/mqtt-component/dependency-reduced-pom.xml old mode 100644 new mode 100755 index 90875955..44978957 --- a/protocol-gateway/mqtt-component/dependency-reduced-pom.xml +++ b/protocol-gateway/mqtt-component/dependency-reduced-pom.xml @@ -3,7 +3,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 mqtt-component @@ -80,19 +80,19 @@ cc.iotkit common - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile cc.iotkit component - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile cc.iotkit dao - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT compile diff --git a/protocol-gateway/mqtt-component/pom.xml b/protocol-gateway/mqtt-component/pom.xml index 4f62838e..663d6834 100755 --- a/protocol-gateway/mqtt-component/pom.xml +++ b/protocol-gateway/mqtt-component/pom.xml @@ -5,7 +5,7 @@ protocol-gateway cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/protocol-gateway/pom.xml b/protocol-gateway/pom.xml index 8ec9a3c9..1d17ca79 100755 --- a/protocol-gateway/pom.xml +++ b/protocol-gateway/pom.xml @@ -5,7 +5,7 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 7bec6da0..9fab3264 100755 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -5,7 +5,7 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0 diff --git a/standalone-package/pom.xml b/standalone-package/pom.xml index 41826c6f..192713d2 100755 --- a/standalone-package/pom.xml +++ b/standalone-package/pom.xml @@ -5,7 +5,7 @@ iotkit-parent cc.iotkit - 0.1.0-SNAPSHOT + 0.2.0-SNAPSHOT 4.0.0