diff --git a/sample-tom/src/main/resources/application.yml b/sample-tom/src/main/resources/application.yml
index 981543e5..2a41527d 100644
--- a/sample-tom/src/main/resources/application.yml
+++ b/sample-tom/src/main/resources/application.yml
@@ -41,4 +41,4 @@ spring:
initialization-mode: always
jpa:
database: mysql
- show-sql: true
\ No newline at end of file
+ show-sql: true
diff --git a/samples/pom.xml b/samples/pom.xml
index 1614c943..16ba2592 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -25,6 +25,8 @@
solon-plugin-sureness-demo
spring-gateway-sureness
zuul-sureness
+ shenyu-sureness
+ shenyu-sureness-sql
sureness-spring-boot-starter-example
micro-profile-sureness
@@ -38,4 +40,4 @@
-
\ No newline at end of file
+
diff --git a/samples/shenyu-service/pom.xml b/samples/shenyu-service/pom.xml
new file mode 100644
index 00000000..1707d6a7
--- /dev/null
+++ b/samples/shenyu-service/pom.xml
@@ -0,0 +1,55 @@
+
+
+
+ samples
+ com.usthe.sureness
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ shenyu-service
+
+
+ 8
+ 8
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ 2.2.2.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ 2.2.2.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+ org.dromara
+ soul-spring-boot-starter-gateway
+ 2.3.0-RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+ org.dromara
+ soul-spring-boot-starter-sync-data-websocket
+ 2.3.0-RELEASE
+
+
+
+
\ No newline at end of file
diff --git a/samples/shenyu-service/src/main/java/com/shenyu/AppBootStrap.java b/samples/shenyu-service/src/main/java/com/shenyu/AppBootStrap.java
new file mode 100644
index 00000000..5d5fa094
--- /dev/null
+++ b/samples/shenyu-service/src/main/java/com/shenyu/AppBootStrap.java
@@ -0,0 +1,18 @@
+package com.shenyu;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @title: AppBootStrap
+ * @Author hqgordon
+ * @Date: 2021/8/3 3:15 下午
+ * @Description:
+ * @Version 1.0
+ */
+@SpringBootApplication
+public class AppBootStrap {
+ public static void main(String[] args) {
+ SpringApplication.run(AppBootStrap.class,args);
+ }
+}
\ No newline at end of file
diff --git a/samples/shenyu-service/src/main/resources/application.yml b/samples/shenyu-service/src/main/resources/application.yml
new file mode 100644
index 00000000..f71bec8b
--- /dev/null
+++ b/samples/shenyu-service/src/main/resources/application.yml
@@ -0,0 +1,13 @@
+spring:
+ main:
+ allow-bean-definition-overriding: true
+management:
+ health:
+ defaults:
+ enabled: false
+shenyu:
+ sync:
+ websocket:
+ urls: ws://localhost:9095/websocket
+server:
+ port: 9196
\ No newline at end of file
diff --git a/samples/shenyu-sureness-sql/pom.xml b/samples/shenyu-sureness-sql/pom.xml
new file mode 100644
index 00000000..34772f67
--- /dev/null
+++ b/samples/shenyu-sureness-sql/pom.xml
@@ -0,0 +1,108 @@
+
+
+
+ samples
+ com.usthe.sureness
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ shenyu-sureness-sql
+
+
+ 13
+ 13
+
+
+
+ org.yaml
+ snakeyaml
+ 1.25
+
+
+ com.usthe.sureness
+ sureness-core
+ 1.0.3
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ 2.2.2.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ 2.2.2.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+ org.dromara
+ soul-spring-boot-starter-gateway
+ 2.3.0-RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+ org.dromara
+ soul-spring-boot-starter-sync-data-websocket
+ 2.3.0-RELEASE
+
+
+ org.dromara
+ soul-spring-boot-starter-plugin-httpclient
+ 2.3.0-RELEASE
+
+
+ org.dromara
+ soul-spring-boot-starter-plugin-divide
+ 2.3.0-RELEASE
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+ 2.2.2.RELEASE
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ 2.2.2.RELEASE
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.47
+
+
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.6
+ provided
+
+
+
+
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/AppBootSql.java b/samples/shenyu-sureness-sql/src/main/java/sureness/AppBootSql.java
new file mode 100644
index 00000000..5fe1023d
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/AppBootSql.java
@@ -0,0 +1,18 @@
+package sureness;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @title: AppBootSql
+ * @Author hqgordon
+ * @Date: 2021/8/5 9:41 上午
+ * @Description:
+ * @Version 1.0
+ */
+@SpringBootApplication
+public class AppBootSql {
+ public static void main(String[] args) {
+ SpringApplication.run(AppBootSql.class,args);
+ }
+}
\ No newline at end of file
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/controller/AccountController.java b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/AccountController.java
new file mode 100644
index 00000000..7b2fee9a
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/AccountController.java
@@ -0,0 +1,98 @@
+package sureness.controller;
+
+
+import com.usthe.sureness.util.JsonWebTokenUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import sureness.pojo.dto.Account;
+import sureness.pojo.dto.Message;
+import sureness.service.AccountService;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * @author tomsun28
+ * @date 00:24 2019-08-01
+ */
+@RestController
+@RequestMapping("/auth")
+@Slf4j
+public class AccountController {
+
+ @Autowired
+ private AccountService accountService;
+
+ private static final String TOKEN_SPLIT = "--";
+
+ @PostMapping("/token")
+ public ResponseEntity issueJwtToken(@RequestBody @Validated Account account) {
+ boolean authenticatedFlag = accountService.authenticateAccount(account);
+ if (!authenticatedFlag) {
+ Message message = Message.builder()
+ .errorMsg("username or password not incorrect").build();
+ if (log.isDebugEnabled()) {
+ log.debug("account: {} authenticated fail", account);
+ }
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).body(message);
+ }
+ List ownRole = accountService.loadAccountRoles(account.getUsername());
+ String jwt = JsonWebTokenUtil.issueJwt(UUID.randomUUID().toString(), account.getUsername(),
+ "tom-auth-server", 3600L, ownRole);
+ Map responseData = Collections.singletonMap("token", jwt);
+ Message message = Message.builder().data(responseData).build();
+ if (log.isDebugEnabled()) {
+ log.debug("issue token success, account: {} -- token: {}", account, jwt);
+ }
+ return ResponseEntity.status(HttpStatus.CREATED).body(message);
+ }
+
+ @PostMapping("/custom/token")
+ public ResponseEntity issueCustomToken(@RequestBody @Validated Account account) {
+ boolean authenticatedFlag = accountService.authenticateAccount(account);
+ if (!authenticatedFlag) {
+ Message message = Message.builder()
+ .errorMsg("username or password not incorrect").build();
+ if (log.isDebugEnabled()) {
+ log.debug("account: {} authenticated fail", account);
+ }
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).body(message);
+ }
+ long refreshPeriodTime = 36000L;
+ String token = account.getUsername() + TOKEN_SPLIT + System.currentTimeMillis()
+ + TOKEN_SPLIT + refreshPeriodTime
+ + TOKEN_SPLIT + UUID.randomUUID().toString().replace("-", "");
+ TokenStorage.addToken(account.getUsername(), token);
+ Map responseData = Collections.singletonMap("customToken", token);
+ Message message = Message.builder().data(responseData).build();
+ if (log.isDebugEnabled()) {
+ log.debug("issue token success, account: {} -- token: {}", account, token);
+ }
+ return ResponseEntity.status(HttpStatus.CREATED).body(message);
+ }
+
+ @PostMapping("/register")
+ public ResponseEntity accountRegister(@RequestBody @Validated Account account) {
+ if (accountService.registerAccount(account)) {
+ Map responseData = Collections.singletonMap("success", "sign up success, login after");
+ Message message = Message.builder().data(responseData).build();
+ if (log.isDebugEnabled()) {
+ log.debug("account: {}, sign up success", account);
+ }
+ return ResponseEntity.status(HttpStatus.CREATED).body(message);
+ } else {
+ Message message = Message.builder()
+ .errorMsg("username already exist").build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/controller/AnnotationController.java b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/AnnotationController.java
new file mode 100644
index 00000000..4bdc54c5
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/AnnotationController.java
@@ -0,0 +1,29 @@
+package sureness.controller;
+
+import com.usthe.sureness.provider.annotation.RequiresRoles;
+import com.usthe.sureness.provider.annotation.WithoutAuth;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+
+/**
+ * @author tomsun28
+ * @date 2021/1/19 14:53
+ */
+@RestController
+public class AnnotationController {
+
+ @GetMapping("/api/annotation/source1")
+ @RequiresRoles(roles = {"role1", "role2", "role3"}, mapping = "/api/annotation/source1", method = "get")
+ public ResponseEntity api1Mock1() {
+ return ResponseEntity.ok("success");
+ }
+
+ @GetMapping("/api/annotation/source2")
+ @WithoutAuth(mapping = "/api/annotation/source2", method = "get")
+ public ResponseEntity api1Mock2() {
+ return ResponseEntity.ok("success");
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/controller/ResourceController.java b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/ResourceController.java
new file mode 100644
index 00000000..78216746
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/ResourceController.java
@@ -0,0 +1,92 @@
+package sureness.controller;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import sureness.pojo.dto.Message;
+import sureness.pojo.entity.AuthResourceDO;
+import sureness.service.ResourceService;
+
+import javax.validation.constraints.NotBlank;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 00:24 2019-08-01
+ */
+@RequestMapping("/resource")
+@RestController
+@Slf4j
+public class ResourceController {
+
+ @Autowired
+ private ResourceService resourceService;
+
+ @PostMapping
+ public ResponseEntity addResource(@RequestBody @Validated AuthResourceDO authResource) {
+ if (resourceService.addResource(authResource)) {
+ if (log.isDebugEnabled()) {
+ log.debug("add resource success: {}", authResource);
+ }
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ } else {
+ Message message = Message.builder().errorMsg("resource already exist").build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+ }
+
+ @PutMapping
+ public ResponseEntity updateResource(@RequestBody @Validated AuthResourceDO authResource) {
+ if (resourceService.updateResource(authResource)) {
+ if (log.isDebugEnabled()) {
+ log.debug("update resource success: {}", authResource);
+ }
+ return ResponseEntity.ok().build();
+ } else {
+ Message message = Message.builder().errorMsg("resource not exist").build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+ }
+
+ @DeleteMapping("/{resourceId}")
+ public ResponseEntity deleteResource(@PathVariable @NotBlank Long resourceId ) {
+ if (resourceService.deleteResource(resourceId)) {
+ if (log.isDebugEnabled()) {
+ log.debug("delete resource success: {}", resourceId);
+ }
+ return ResponseEntity.ok().build();
+ } else {
+ Message message = Message.builder().errorMsg("delete resource fail, please try again later").build();
+ log.error("delete resource fail: {}", resourceId);
+ return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body(message);
+ }
+ }
+
+ @GetMapping("/{currentPage}/{pageSize}")
+ public ResponseEntity getResource(@PathVariable Integer currentPage, @PathVariable Integer pageSize ) {
+ if (Objects.isNull(currentPage) || Objects.isNull(pageSize)) {
+ // no pageable
+ Optional> resourceListOptional = resourceService.getAllResource();
+ if (resourceListOptional.isPresent()) {
+ Message message = Message.builder().data(resourceListOptional.get()).build();
+ return ResponseEntity.ok().body(message);
+ } else {
+ Message message = Message.builder().data(new ArrayList<>(0)).build();
+ return ResponseEntity.ok().body(message);
+ }
+ } else {
+ // pageable
+ Page resourcePage = resourceService.getPageResource(currentPage, pageSize);
+ Message message = Message.builder().data(resourcePage.get()).build();
+ return ResponseEntity.ok().body(message);
+ }
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/controller/RoleController.java b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/RoleController.java
new file mode 100644
index 00000000..c10dd36c
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/RoleController.java
@@ -0,0 +1,137 @@
+package sureness.controller;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import sureness.pojo.dto.Message;
+import sureness.pojo.entity.AuthResourceDO;
+import sureness.pojo.entity.AuthRoleDO;
+import sureness.service.RoleService;
+
+import javax.validation.constraints.NotBlank;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 00:24 2019-08-01
+ */
+@RestController
+@RequestMapping("/role")
+@Slf4j
+public class RoleController {
+
+ @Autowired
+ private RoleService roleService;
+
+
+ @GetMapping("/resource/{roleId}/{currentPage}/{pageSize}")
+ public ResponseEntity getResourceOwnByRole(@PathVariable @NotBlank Long roleId, @PathVariable Integer currentPage, @PathVariable Integer pageSize) {
+ if (currentPage == null){
+ currentPage = 1;
+ }
+ if (pageSize == null) {
+ pageSize = 10;
+ }
+ Page resourcePage = roleService.getPageResourceOwnRole(roleId, currentPage, pageSize);
+ Message message = Message.builder().data(resourcePage).build();
+ return ResponseEntity.ok().body(message);
+ }
+
+ @GetMapping("/resource/-/{roleId}/{currentPage}/{pageSize}")
+ public ResponseEntity getResourceNotOwnByRole(@PathVariable @NotBlank Long roleId, @PathVariable Integer currentPage, @PathVariable Integer pageSize) {
+ if (currentPage == null){
+ currentPage = 1;
+ }
+ if (pageSize == null) {
+ pageSize = 10;
+ }
+ Page resourcePage = roleService.getPageResourceNotOwnRole(roleId, currentPage, pageSize);
+ Message message = Message.builder().data(resourcePage).build();
+ return ResponseEntity.ok().body(message);
+ }
+
+ @PostMapping("/authority/resource/{roleId}/{resourceId}")
+ public ResponseEntity authorityRoleResource(@PathVariable @NotBlank Long roleId,
+ @PathVariable @NotBlank Long resourceId) {
+ roleService.authorityRoleResource(roleId,resourceId);
+ return ResponseEntity.status(HttpStatus.OK).build();
+ }
+
+ @DeleteMapping("/authority/resource/{roleId}/{resourceId}")
+ public ResponseEntity deleteAuthorityRoleResource(@PathVariable @NotBlank Long roleId,
+ @PathVariable @NotBlank Long resourceId) {
+ roleService.deleteAuthorityRoleResource(roleId,resourceId);
+ return ResponseEntity.status(HttpStatus.OK).build();
+ }
+
+ @PostMapping
+ public ResponseEntity addRole(@RequestBody @Validated AuthRoleDO authRole) {
+ if (roleService.addRole(authRole)) {
+ if (log.isDebugEnabled()) {
+ log.debug("add role success: {}", authRole);
+ }
+ return ResponseEntity.status(HttpStatus.CREATED).build();
+ } else {
+ Message message = Message.builder()
+ .errorMsg("role already exist").build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+ }
+
+ @PutMapping
+ public ResponseEntity updateRole(@RequestBody @Validated AuthRoleDO authRole) {
+ if (roleService.updateRole(authRole)) {
+ if (log.isDebugEnabled()) {
+ log.debug("update role success: {}", authRole);
+ }
+ return ResponseEntity.ok().build();
+ } else {
+ Message message = Message.builder()
+ .errorMsg("role not exist").build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+ }
+
+ @DeleteMapping("/{roleId}")
+ public ResponseEntity deleteRole(@PathVariable @NotBlank Long roleId) {
+ if (roleService.deleteRole(roleId)) {
+ if (log.isDebugEnabled()) {
+ log.debug("delete role success: {}", roleId);
+ }
+ return ResponseEntity.ok().build();
+ } else {
+ Message message = Message.builder()
+ .errorMsg("delete role fail, no this role here").build();
+ log.debug("delete role fail: {}", roleId);
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+ }
+
+ @GetMapping("/{currentPage}/{pageSize}")
+ public ResponseEntity getRole(@PathVariable Integer currentPage, @PathVariable Integer pageSize ) {
+ if (Objects.isNull(currentPage) || Objects.isNull(pageSize)) {
+ // no pageable
+ Optional> roleListOptional = roleService.getAllRole();
+ if (roleListOptional.isPresent()) {
+ Message message = Message.builder().data(roleListOptional.get()).build();
+ return ResponseEntity.ok().body(message);
+ } else {
+ Message message = Message.builder().data(new ArrayList<>()).build();
+ return ResponseEntity.ok().body(message);
+ }
+ } else {
+ // pageable
+ Page rolePage = roleService.getPageRole(currentPage, pageSize);
+ Message message = Message.builder().data(rolePage).build();
+ return ResponseEntity.ok().body(message);
+ }
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/controller/TokenStorage.java b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/TokenStorage.java
new file mode 100644
index 00000000..16e678d0
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/TokenStorage.java
@@ -0,0 +1,61 @@
+package sureness.controller;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * token storage
+ * you can use redis instead of it
+ * @author tomsun28
+ * @date 2020-12-03 23:01
+ */
+public class TokenStorage {
+
+ private static final String TOKEN_SPLIT = "--";
+ private static final int TOKEN_SPLIT_SIZE = 4;
+ private static final int START_TIME_INDEX = 1;
+ private static final int PERIOD_TIME_INDEX = 2;
+
+ private static final Map TOKEN_MAP = new ConcurrentHashMap<>();
+
+ /**
+ * match token
+ * @param key key
+ * @param currentToken tokenValue is : admin--issueTime--refreshPeriodTime--uuid
+ * @return false when token not exist, not equals or Expired, else true
+ */
+ public static boolean matchToken(String key, String currentToken) {
+ if (key == null || currentToken == null || "".equals(key) || "".equals(currentToken)
+ || currentToken.split(TOKEN_SPLIT).length != TOKEN_SPLIT_SIZE) {
+ return false;
+ }
+ String originToken = TOKEN_MAP.get(key);
+ if (originToken == null || !originToken.equals(currentToken)) {
+ removeToken(key);
+ return false;
+ }
+ String[] tokenArr = currentToken.split(TOKEN_SPLIT);
+ if (Long.parseLong(tokenArr[START_TIME_INDEX]) + Long.parseLong(tokenArr[PERIOD_TIME_INDEX])
+ <= System.currentTimeMillis()) {
+ // token expired, remove it
+ removeToken(key);
+ return false;
+ }
+ return true;
+ }
+
+ public static void removeToken(String key) {
+ if (key == null || "".equals(key)) {
+ return;
+ }
+ TOKEN_MAP.remove(key);
+ }
+
+ public static void addToken(String key, String token) {
+ if (key == null || token == null || "".equals(key) || "".equals(token)
+ || token.split(TOKEN_SPLIT).length != TOKEN_SPLIT_SIZE) {
+ return;
+ }
+ TOKEN_MAP.put(key, token);
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/controller/UserController.java b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/UserController.java
new file mode 100644
index 00000000..7eb3e451
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/controller/UserController.java
@@ -0,0 +1,72 @@
+package sureness.controller;
+
+
+import com.usthe.sureness.subject.SubjectSum;
+import com.usthe.sureness.util.SurenessContextHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import sureness.pojo.dto.Message;
+import sureness.service.AccountService;
+
+import java.util.List;
+
+/**
+ *
+ * @author tomsun28
+ * @date 21:05 2018/3/17
+ */
+@RestController
+@RequestMapping("/user")
+public class UserController {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
+
+ @Autowired
+ private AccountService accountService;
+
+
+ @GetMapping("/role")
+ public ResponseEntity getUserRoles() {
+ SubjectSum subject = SurenessContextHolder.getBindSubject();
+ if (subject == null || subject.getPrincipal() == null) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+ String appId = (String) subject.getPrincipal();
+ List roles = accountService.loadAccountRoles(appId);
+ return ResponseEntity.ok(Message.builder().data(roles).build());
+ }
+
+ @PostMapping("/authority/role/{appId}/{roleId}")
+ public ResponseEntity authorityUserRole(@PathVariable String appId, @PathVariable Long roleId) {
+ SubjectSum subject = SurenessContextHolder.getBindSubject();
+ if (subject == null || subject.getPrincipal() == null) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+ String principal = (String) subject.getPrincipal();
+ if (!principal.equals(appId)) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+
+ boolean flag = accountService.authorityUserRole(appId, roleId);
+ return flag ? ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.CONFLICT).build();
+ }
+
+ @DeleteMapping("/authority/role/{appId}/{roleId}")
+ public ResponseEntity deleteAuthorityUserRole(@PathVariable String appId, @PathVariable Long roleId) {
+ SubjectSum subject = SurenessContextHolder.getBindSubject();
+ if (subject == null || subject.getPrincipal() == null) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+ String principal = (String) subject.getPrincipal();
+ if (!principal.equals(appId)) {
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
+ }
+
+ return accountService.deleteAuthorityUserRole(appId, roleId) ?
+ ResponseEntity.ok().build() : ResponseEntity.status(HttpStatus.CONFLICT).build();
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthResourceDao.java b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthResourceDao.java
new file mode 100644
index 00000000..2fd04581
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthResourceDao.java
@@ -0,0 +1,64 @@
+package sureness.dao;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import sureness.pojo.entity.AuthResourceDO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 16:40 2019-07-27
+ */
+public interface AuthResourceDao extends JpaRepository {
+
+ /**
+ * Get uri resource and resource-role relationship chain, eg: /api/v2/host===post===[role2,role3,role4]
+ * @return resource-role chain set
+ */
+ @Query(value = "SELECT CONCAT(LOWER(res.uri),\"===\",LOWER(res.method),\"===[\",IFNULL(GROUP_CONCAT(DISTINCT role.code),\"\"),\"]\") " +
+ "FROM auth_resource res " +
+ "LEFT JOIN auth_role_resource_bind bind on res.id = bind.resource_id " +
+ "LEFT JOIN auth_role role on role.id = bind.role_id " +
+ "where res.status = 1 " +
+ "group by res.id", nativeQuery = true)
+ Optional> getEnableResourcePathRoleData();
+
+
+
+ /**
+ * Get disabled uri resources eg: /api/v2/host===post
+ * @return resouce set
+ */
+ @Query("select CONCAT(LOWER(resource.uri),'===', resource.method) " +
+ "from AuthResourceDO resource where resource.status = 9 order by resource.id")
+ Optional> getDisableResourcePathData();
+
+ /**
+ * Get the available API resources owned by the current role in the form of paging
+ * @param roleId roleId
+ * @param request page
+ * @return api resource list
+ */
+ @Query("select distinct resource from AuthResourceDO resource " +
+ "left join AuthRoleResourceBindDO bind on bind.resourceId = resource.id " +
+ "where bind.roleId = :roleId " +
+ "order by resource.id asc")
+ Page findRoleOwnResource(@Param("roleId") Long roleId, Pageable request);
+
+ /**
+ * Get the available API resources owned by the current role in the form of paging
+ * @param roleId roleId
+ * @param request page
+ * @return api resource list
+ */
+ @Query("select distinct resource from AuthResourceDO resource " +
+ " where resource.id not in " +
+ "(select distinct bind.resourceId from AuthRoleResourceBindDO bind where bind.roleId = :roleId) " +
+ "order by resource.id asc ")
+ Page findRoleNotOwnResource(@Param("roleId") Long roleId, Pageable request);
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthRoleDao.java b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthRoleDao.java
new file mode 100644
index 00000000..08a05946
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthRoleDao.java
@@ -0,0 +1,24 @@
+package sureness.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import sureness.pojo.entity.AuthRoleDO;
+
+import java.util.List;
+
+/**
+ * @author tomsun28
+ * @date 16:42 2019-07-27
+ */
+public interface AuthRoleDao extends JpaRepository {
+
+ /**
+ * Query the role owned by the current user
+ * @param username username
+ * @return role list
+ */
+ @Query("select ar.name from AuthRoleDO ar, AuthUserDO au, AuthUserRoleBindDO bind " +
+ "where ar.id = bind.roleId and au.id = bind.userId and au.username = :username")
+ List findAccountOwnRoles(@Param("username") String username);
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthRoleResourceBindDao.java b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthRoleResourceBindDao.java
new file mode 100644
index 00000000..ef7a5625
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthRoleResourceBindDao.java
@@ -0,0 +1,35 @@
+package sureness.dao;
+
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import sureness.pojo.entity.AuthResourceDO;
+import sureness.pojo.entity.AuthRoleResourceBindDO;
+
+import java.util.List;
+
+/**
+ * @author tomsun28
+ * @date 16:43 2019-07-27
+ */
+public interface AuthRoleResourceBindDao extends JpaRepository {
+
+ /**
+ * Query the resources owned by the current role
+ * @param roleId roleId
+ * @return resource list
+ */
+ @Query("select rs from AuthResourceDO rs, AuthRoleResourceBindDO bind " +
+ "where rs.id = bind.resourceId and bind.roleId = :roleId")
+ List findRoleBindResourceList(@Param("roleId") Long roleId);
+
+ /**
+ * delete record which roleId and resource equals this
+ * @param roleId roleID
+ * @param resourceId resourceId
+ */
+ @Query("delete from AuthRoleResourceBindDO bind " +
+ "where bind.roleId = :roleId and bind.resourceId = :resourceId")
+ void deleteRoleResourceBind(@Param("roleId") Long roleId,@Param("resourceId") Long resourceId);
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthUserDao.java b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthUserDao.java
new file mode 100644
index 00000000..e44149b1
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthUserDao.java
@@ -0,0 +1,34 @@
+package sureness.dao;
+
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import sureness.pojo.entity.AuthUserDO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 16:43 2019-07-27
+ */
+public interface AuthUserDao extends JpaRepository {
+
+ /**
+ * Get user by username
+ * @param username username
+ * @return user
+ */
+ @Query("select au from AuthUserDO au where au.username = :username")
+ Optional findAuthUserByUsername(@Param("username") String username);
+
+ /**
+ * Query the role owned by the current user
+ * @param username username
+ * @return role list
+ */
+ @Query("select ar.code from AuthRoleDO ar, AuthUserDO au, AuthUserRoleBindDO bind " +
+ "where ar.id = bind.roleId and au.id = bind.userId and au.username = :username")
+ List findAccountOwnRoles(@Param("username") String username);
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthUserRoleBindDao.java b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthUserRoleBindDao.java
new file mode 100644
index 00000000..f2e78dc6
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/dao/AuthUserRoleBindDao.java
@@ -0,0 +1,35 @@
+package sureness.dao;
+
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import sureness.pojo.entity.AuthRoleDO;
+import sureness.pojo.entity.AuthUserRoleBindDO;
+
+import java.util.List;
+
+/**
+ * @author tomsun28
+ * @date 16:44 2019-07-27
+ */
+public interface AuthUserRoleBindDao extends JpaRepository {
+
+ /**
+ * Query the role owned by the current user
+ * @param userId userId
+ * @return role list
+ */
+ @Query("select ar from AuthRoleDO ar, AuthUserRoleBindDO bind " +
+ "where ar.id = bind.roleId and bind.userId = :userId")
+ List findUserBindRoleList(@Param("userId") Long userId);
+
+ /**
+ * delete record which roleId and userId equals this
+ * @param roleId roleID
+ * @param userId userId
+ */
+ @Query("delete from AuthUserRoleBindDO bind " +
+ "where bind.roleId = :roleId and bind.userId = :userId")
+ void deleteRoleResourceBind(@Param("roleId") Long roleId,@Param("userId") Long userId);
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/handler/GlobalExceptionHandler.java b/samples/shenyu-sureness-sql/src/main/java/sureness/handler/GlobalExceptionHandler.java
new file mode 100644
index 00000000..3d80f5d8
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/handler/GlobalExceptionHandler.java
@@ -0,0 +1,95 @@
+package sureness.handler;
+
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DataAccessException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import sureness.pojo.dto.Message;
+import sureness.service.impl.DataConflictException;
+
+/**
+ * controller exception handler
+ * @author tomsun28
+ * @date 22:45 2019-08-01
+ */
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+ /**
+ * handler the exception thrown for data input verify
+ * @param exception data input verify exception
+ * @return response
+ */
+ @ExceptionHandler(MethodArgumentNotValidException.class)
+ @ResponseBody
+ ResponseEntity handleInputValidException(MethodArgumentNotValidException exception) {
+ StringBuffer errorMessage = new StringBuffer();
+ if (exception != null) {
+ exception.getBindingResult().getAllErrors().forEach(error ->
+ errorMessage.append(error.getDefaultMessage()).append("."));
+ }
+ if (log.isDebugEnabled()) {
+ log.debug("[sample-tom]-[input argument not valid happen]-{}", errorMessage, exception);
+ }
+ Message message = Message.builder().errorMsg(errorMessage.toString()).build();
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(message);
+ }
+
+ /**
+ * handler the exception thrown for datastore error
+ * @param exception datastore exception
+ * @return response
+ */
+ @ExceptionHandler(DataAccessException.class)
+ @ResponseBody
+ ResponseEntity handleDataAccessException(DataAccessException exception) {
+ String errorMessage = "database error happen";
+ if (exception != null) {
+ errorMessage = exception.getMessage();
+ }
+ log.warn("[sample-tom]-[database error happen]-{}", errorMessage, exception);
+ Message message = Message.builder().errorMsg(errorMessage).build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+
+ /**
+ * handler the exception thrown for data conflict
+ * @param exception data conflict
+ * @return response
+ */
+ @ExceptionHandler(DataConflictException.class)
+ @ResponseBody
+ ResponseEntity handleDataConflictException(DataConflictException exception) {
+ String errorMessage = "data status conflict warning";
+ if (exception != null && exception.getMessage() != null) {
+ errorMessage = exception.getMessage();
+ }
+ log.info("[sample-tom]-[data status conflict warning]-{}", errorMessage, exception);
+ Message message = Message.builder().errorMsg(errorMessage).build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+
+ /**
+ * handler the exception thrown for unCatch and unKnown
+ * @param exception UnknownException
+ * @return response
+ */
+ @ExceptionHandler(Exception.class)
+ @ResponseBody
+ ResponseEntity handleUnknownException(Exception exception) {
+ String errorMessage = "unknown error happen";
+ if (exception != null) {
+ errorMessage = exception.getMessage();
+ }
+ log.error("[sample-tom]-[unknown error happen]-{}", errorMessage, exception);
+ Message message = Message.builder().errorMsg(errorMessage).build();
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(message);
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/dto/Account.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/dto/Account.java
new file mode 100644
index 00000000..9195039a
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/dto/Account.java
@@ -0,0 +1,29 @@
+package sureness.pojo.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author tomsun28
+ * @date 20:36 2019-08-01
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class Account {
+
+ @NotBlank(message = "username can not null")
+ @Length(min = 3, max = 100, message = "username length in 3-100")
+ private String username;
+
+ @NotBlank(message = "password can not null")
+ @Length(min = 3, max = 100, message = "password length in 3-100")
+ private String password;
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/dto/Message.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/dto/Message.java
new file mode 100644
index 00000000..32812d97
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/dto/Message.java
@@ -0,0 +1,34 @@
+package sureness.pojo.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Unified message structure definition for front and back ends
+ *
+ * {
+ * data:{....},
+ * errorMsg: message
+ * }
+ * @author tomsun28
+ * @date 23:48 2019/08/01
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class Message {
+
+ /**
+ * message body data
+ */
+ private Object data;
+
+ /**
+ * exception message when error happen
+ */
+ private String errorMsg;
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthResourceDO.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthResourceDO.java
new file mode 100644
index 00000000..efd50465
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthResourceDO.java
@@ -0,0 +1,54 @@
+package sureness.pojo.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.Range;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import java.time.LocalDateTime;
+
+/**
+ * resource entity
+ * @author tomsun28
+ * @date 00:00 2019-07-26
+ */
+@Entity
+@Table(name = "auth_resource")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthResourceDO {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank(message = "name can not null")
+ @Length(min = 3, max = 100, message = "name length in 3-100")
+ private String name;
+
+ @NotBlank(message = "code can not null")
+ @Length(min = 3, max = 100, message = "code length in 3-100")
+ private String code;
+
+ @NotBlank(message = "uri can not null")
+ private String uri;
+
+ private String type;
+
+ @NotBlank(message = "method can not null")
+ private String method;
+
+ @Range(min = 0, max = 9, message = "1 enable, 9 disable")
+ private Integer status;
+
+ private String description;
+
+ private LocalDateTime gmtCreate;
+
+ private LocalDateTime gmtUpdate;
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthRoleDO.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthRoleDO.java
new file mode 100644
index 00000000..c72c0136
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthRoleDO.java
@@ -0,0 +1,47 @@
+package sureness.pojo.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.Range;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import java.time.LocalDateTime;
+
+/**
+ * role entity
+ * @author tomsun28
+ * @date 00:27 2019-07-27
+ */
+@Entity
+@Table(name = "auth_role")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthRoleDO {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank(message = "name can not null")
+ @Length(min = 3, max = 100, message = "name length in 3-100")
+ private String name;
+
+ @NotBlank(message = "code can not null")
+ @Length(min = 3, max = 100, message = "code length in 3-100")
+ private String code;
+
+ @Range(min = 0, max = 9, message = "1 enable, 9 disable")
+ private Integer status;
+
+ private String description;
+
+ private LocalDateTime gmtCreate;
+
+ private LocalDateTime gmtUpdate;
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthRoleResourceBindDO.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthRoleResourceBindDO.java
new file mode 100644
index 00000000..b8fdfb3a
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthRoleResourceBindDO.java
@@ -0,0 +1,38 @@
+package sureness.pojo.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;;
+
+/**
+ * resource-role mapping entity
+ * @author tomsun28
+ * @date 00:28 2019-07-27
+ */
+@Entity
+@Table(name = "auth_role_resource_bind")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthRoleResourceBindDO {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull(message = "roleId can not null")
+ private Long roleId;
+
+ @NotNull(message = "resourceId can not null")
+ private Long resourceId;
+
+ private LocalDateTime gmtCreate;
+
+ private LocalDateTime gmtUpdate;
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthUserDO.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthUserDO.java
new file mode 100644
index 00000000..e8896eb5
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthUserDO.java
@@ -0,0 +1,58 @@
+package sureness.pojo.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Length;
+import org.hibernate.validator.constraints.Range;
+
+
+import javax.persistence.*;
+import javax.validation.constraints.NotBlank;
+import java.time.LocalDateTime;
+
+/**
+ * user entity
+ * @author tomsun28
+ * @date 00:29 2019-07-27
+ */
+@Entity
+@Table(name = "auth_user")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthUserDO {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotBlank(message = "username can not null")
+ @Length(min = 3, max = 100, message = "name length in 3-100")
+ private String username;
+
+ @NotBlank(message = "password can not null")
+ @Length(min = 3, max = 100, message = "password length in 3-100")
+ private String password;
+
+ private String salt;
+
+ private String avatar;
+
+ private String phone;
+
+ private String email;
+
+ private Integer sex;
+
+ @Range(min = 0, max = 9, message = "1 enable, 2 locked, 3 deleted, 4 illegal")
+ private Integer status;
+
+ private Integer createWhere;
+
+ private LocalDateTime gmtCreate;
+
+ private LocalDateTime gmtUpdate;
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthUserRoleBindDO.java b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthUserRoleBindDO.java
new file mode 100644
index 00000000..5386d8f2
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/pojo/entity/AuthUserRoleBindDO.java
@@ -0,0 +1,38 @@
+package sureness.pojo.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+import java.time.LocalDateTime;
+
+/**
+ * user-role mapping entity
+ * @author tomsun28
+ * @date 00:30 2019-07-27
+ */
+@Entity
+@Table(name = "auth_user_role_bind")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthUserRoleBindDO {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull(message = "userId can not null")
+ private Long userId;
+
+ @NotNull(message = "roleId can not null")
+ private Long roleId;
+
+ private LocalDateTime gmtCreate;
+
+ private LocalDateTime gmtUpdate;
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/AccountService.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/AccountService.java
new file mode 100644
index 00000000..e4b06a2f
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/AccountService.java
@@ -0,0 +1,63 @@
+package sureness.service;
+
+import com.usthe.sureness.provider.SurenessAccount;
+import sureness.pojo.dto.Account;
+
+import java.util.List;
+
+/**
+ * @author tomsun28
+ * @date 00:12 2019-08-01
+ */
+public interface AccountService {
+ /**
+ * Verify account validity, username and password
+ * @param account account info
+ * @return success-true failed-false
+ */
+ boolean authenticateAccount(Account account);
+
+ /**
+ * Get all roles owned by this username account, combine them into string list
+ * @param username account username
+ * @return role-string eg role1,role3,role2
+ */
+ List loadAccountRoles(String username);
+
+ /**
+ * register account
+ * @param account account info
+ * @return success-true failed-false
+ */
+ boolean registerAccount(Account account);
+
+ /**
+ * Determine whether the account already exists
+ * @param account account info
+ * @return exist-true no-false
+ */
+ boolean isAccountExist(Account account);
+
+ /**
+ * Load the account information by username
+ * @param username account username
+ * @return account
+ */
+ SurenessAccount loadAccount(String username);
+
+ /**
+ * authority User Role by username and roleId
+ * @param appId account username
+ * @param roleId roleId
+ * @return success-true failed-false
+ */
+ boolean authorityUserRole(String appId, Long roleId);
+
+ /**
+ * delete authority User Role by username and roleId
+ * @param appId account username
+ * @param roleId roleId
+ * @return success-true failed-false
+ */
+ boolean deleteAuthorityUserRole(String appId, Long roleId);
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/ResourceService.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/ResourceService.java
new file mode 100644
index 00000000..dd34cc60
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/ResourceService.java
@@ -0,0 +1,70 @@
+package sureness.service;
+
+
+import org.springframework.data.domain.Page;
+import sureness.pojo.entity.AuthResourceDO;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author tomsun28
+ * @date 00:13 2019-08-01
+ */
+public interface ResourceService {
+
+ /**
+ * add uri resource
+ * @param authResource resource
+ * @return success-true failed-false
+ */
+ boolean addResource(AuthResourceDO authResource);
+
+ /**
+ * Determine whether the resource already exists
+ * @param authResource resource
+ * @return existed-true no-false
+ */
+ boolean isResourceExist(AuthResourceDO authResource);
+
+ /**
+ * update uri resource
+ * @param authResource resource
+ * @return success-true failed-false
+ */
+ boolean updateResource(AuthResourceDO authResource);
+
+ /**
+ * delete uri resource
+ * @param resourceId resource ID
+ * @return success-true no existed-false
+ */
+ boolean deleteResource(Long resourceId);
+
+ /**
+ * get all resources
+ * @return resource list
+ */
+ Optional> getAllResource();
+
+ /**
+ * get resource by page
+ * @param currentPage current page
+ * @param pageSize page size
+ * @return Page of resource
+ */
+ Page getPageResource(Integer currentPage, Integer pageSize);
+
+ /**
+ * get enabled resource-path-role eg: /api/v2/host===post===[role2,role3,role4]
+ * @return resource-path-role
+ */
+ Set getAllEnableResourcePath();
+
+ /**
+ * get disable resource-path-role eg: /api/v2/host===post===[role2,role3,role4]
+ * @return resource-path-role
+ */
+ Set getAllDisableResourcePath();
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/RoleService.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/RoleService.java
new file mode 100644
index 00000000..85b155ad
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/RoleService.java
@@ -0,0 +1,91 @@
+package sureness.service;
+
+
+import org.springframework.data.domain.Page;
+import sureness.pojo.entity.AuthResourceDO;
+import sureness.pojo.entity.AuthRoleDO;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 00:14 2019-08-01
+ */
+public interface RoleService {
+
+ /**
+ * Determine whether the role already exists
+ * @param authRole role
+ * @return existed-true no-false
+ */
+ boolean isRoleExist(AuthRoleDO authRole);
+
+ /**
+ * add role
+ * @param authRole role
+ * @return add success-true failed-false
+ */
+ boolean addRole(AuthRoleDO authRole);
+
+ /**
+ * update role
+ * @param authRole role
+ * @return success-true failed-false
+ */
+ boolean updateRole(AuthRoleDO authRole);
+
+ /**
+ * delete role
+ * @param roleId role ID
+ * @return success-true failed-false
+ */
+ boolean deleteRole(Long roleId);
+
+ /**
+ * get all role list
+ * @return role list
+ */
+ Optional> getAllRole();
+
+ /**
+ * get roles page
+ * @param currentPage current page
+ * @param pageSize page size
+ * @return Page of roles
+ */
+ Page getPageRole(Integer currentPage, Integer pageSize);
+
+ /**
+ * get pageable resources which this role owned
+ * @param roleId role ID
+ * @param currentPage current page
+ * @param pageSize page size
+ * @return Page of resources
+ */
+ Page getPageResourceOwnRole(Long roleId, Integer currentPage, Integer pageSize);
+
+ /**
+ * get pageable resources which this role not owned
+ * @param roleId role ID
+ * @param currentPage current page
+ * @param pageSize page size
+ * @return Page of resources
+ */
+ Page getPageResourceNotOwnRole(Long roleId, Integer currentPage, Integer pageSize);
+
+ /**
+ * authority this resource to this role
+ * @param roleId role ID
+ * @param resourceId resource ID
+ */
+ void authorityRoleResource(Long roleId, Long resourceId);
+
+ /**
+ * unAuthority this resource in this role
+ * @param roleId role ID
+ * @param resourceId resource ID
+ */
+ void deleteAuthorityRoleResource(Long roleId, Long resourceId);
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/AccountServiceImpl.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/AccountServiceImpl.java
new file mode 100644
index 00000000..7aaf4bf2
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/AccountServiceImpl.java
@@ -0,0 +1,121 @@
+package sureness.service.impl;
+
+import com.usthe.sureness.provider.DefaultAccount;
+import com.usthe.sureness.provider.SurenessAccount;
+import com.usthe.sureness.util.Md5Util;
+import com.usthe.sureness.util.SurenessCommonUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import sureness.dao.AuthUserDao;
+import sureness.dao.AuthUserRoleBindDao;
+import sureness.pojo.dto.Account;
+import sureness.pojo.entity.AuthUserDO;
+import sureness.pojo.entity.AuthUserRoleBindDO;
+import sureness.service.AccountService;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 10:58 2019-08-04
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class AccountServiceImpl implements AccountService {
+
+ @Autowired
+ private AuthUserDao authUserDao;
+
+ private AuthUserRoleBindDao userRoleBindDao;
+
+ @Override
+ public boolean authenticateAccount(Account account) {
+ Optional authUserOptional = authUserDao.findAuthUserByUsername(account.getUsername());
+ if (!authUserOptional.isPresent()) {
+ return false;
+ }
+ AuthUserDO authUser = authUserOptional.get();
+ String password = account.getPassword();
+ if (password == null) {
+ return false;
+ }
+ if (Objects.nonNull(authUser.getSalt())) {
+ // md5 with salt
+ password = Md5Util.md5(password + authUser.getSalt());
+
+ }
+ return authUser.getPassword().equals(password);
+ }
+
+ @Override
+ public List loadAccountRoles(String username) {
+ return authUserDao.findAccountOwnRoles(username);
+ }
+
+ @Override
+ public boolean registerAccount(Account account) {
+ if (isAccountExist(account)) {
+ return false;
+ }
+ String salt = SurenessCommonUtil.getRandomString(6);
+ String password = Md5Util.md5(account.getPassword() + salt);
+ AuthUserDO authUser = AuthUserDO.builder().username(account.getUsername())
+ .password(password).salt(salt).status(1).build();
+ authUserDao.save(authUser);
+ return true;
+ }
+
+ @Override
+ public boolean isAccountExist(Account account) {
+ Optional authUserOptional = authUserDao.findAuthUserByUsername(account.getUsername());
+ return authUserOptional.isPresent();
+ }
+
+ @Override
+ public SurenessAccount loadAccount(String username) {
+ Optional authUserOptional = authUserDao.findAuthUserByUsername(username);
+ if (authUserOptional.isPresent()) {
+ AuthUserDO authUser = authUserOptional.get();
+ DefaultAccount.Builder accountBuilder = DefaultAccount.builder(username)
+ .setPassword(authUser.getPassword())
+ .setSalt(authUser.getSalt())
+ .setDisabledAccount(1 != authUser.getStatus())
+ .setExcessiveAttempts(2 == authUser.getStatus());
+ List roles = loadAccountRoles(username);
+ if (roles != null) {
+ accountBuilder.setOwnRoles(roles);
+ }
+ return accountBuilder.build();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean authorityUserRole(String appId, Long roleId) {
+ Optional optional = authUserDao.findAuthUserByUsername(appId);
+ if (!optional.isPresent()) {
+ return false;
+ }
+ Long userId = optional.get().getId();
+ AuthUserRoleBindDO userRoleBindDO = AuthUserRoleBindDO.builder().userId(userId).roleId(roleId).build();
+
+ userRoleBindDao.save(userRoleBindDO);
+ return true;
+ }
+
+ @Override
+ public boolean deleteAuthorityUserRole(String appId, Long roleId) {
+ Optional optional = authUserDao.findAuthUserByUsername(appId);
+ if (!optional.isPresent()) {
+ return false;
+ }
+ Long userId = optional.get().getId();
+ userRoleBindDao.deleteRoleResourceBind(roleId, userId);
+ return true;
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/DataConflictException.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/DataConflictException.java
new file mode 100644
index 00000000..df04a8f5
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/DataConflictException.java
@@ -0,0 +1,14 @@
+package sureness.service.impl;
+
+
+/**
+ * data conflict exception
+ * @author tomsun28
+ * @date 22:55 2020-04-27
+ */
+public class DataConflictException extends RuntimeException {
+
+ public DataConflictException(String msg) {
+ super(msg);
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/ResourceServiceImpl.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/ResourceServiceImpl.java
new file mode 100644
index 00000000..46e283b4
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/ResourceServiceImpl.java
@@ -0,0 +1,93 @@
+package sureness.service.impl;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import sureness.dao.AuthResourceDao;
+import sureness.pojo.entity.AuthResourceDO;
+import sureness.service.ResourceService;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * @author tomsun28
+ * @date 13:09 2019-08-04
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class ResourceServiceImpl implements ResourceService {
+
+ @Autowired
+ private AuthResourceDao authResourceDao;
+
+ @Override
+ public boolean addResource(AuthResourceDO authResource) {
+ if (isResourceExist(authResource)) {
+ return false;
+ } else {
+ authResourceDao.saveAndFlush(authResource);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean isResourceExist(AuthResourceDO authResource) {
+ AuthResourceDO resource = AuthResourceDO.builder()
+ .uri(authResource.getUri())
+ .method(authResource.getMethod())
+ .build();
+ Example example = Example.of(resource);
+ return authResourceDao.exists(example);
+ }
+
+ @Override
+ public boolean updateResource(AuthResourceDO authResource) {
+ if (authResourceDao.existsById(authResource.getId())) {
+ authResourceDao.saveAndFlush(authResource);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean deleteResource(Long resourceId) {
+ if (authResourceDao.existsById(resourceId)) {
+ authResourceDao.deleteById(resourceId);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Optional> getAllResource() {
+ List resourceList = authResourceDao.findAll();
+ return Optional.of(resourceList);
+ }
+
+ @Override
+ public Page getPageResource(Integer currentPage, Integer pageSize) {
+ PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
+ return authResourceDao.findAll(pageRequest);
+ }
+
+ @Override
+ public Set getAllEnableResourcePath() {
+ Optional> optional = authResourceDao.getEnableResourcePathRoleData();
+ return optional.>map(HashSet::new).orElseGet(() -> new HashSet<>(0));
+ }
+
+ @Override
+ public Set getAllDisableResourcePath() {
+ Optional> optional = authResourceDao.getDisableResourcePathData();
+ return optional.>map(HashSet::new).orElseGet(() -> new HashSet<>(0));
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/RoleServiceImpl.java b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/RoleServiceImpl.java
new file mode 100644
index 00000000..1de7c1eb
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/service/impl/RoleServiceImpl.java
@@ -0,0 +1,123 @@
+package sureness.service.impl;
+
+import com.usthe.sureness.matcher.TreePathRoleMatcher;
+import org.springframework.beans.factory.annotation.Autowired;
+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.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import sureness.dao.AuthResourceDao;
+import sureness.dao.AuthRoleDao;
+import sureness.dao.AuthRoleResourceBindDao;
+import sureness.pojo.entity.AuthResourceDO;
+import sureness.pojo.entity.AuthRoleDO;
+import sureness.pojo.entity.AuthRoleResourceBindDO;
+import sureness.service.RoleService;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author tomsun28
+ * @date 13:10 2019-08-04
+ */
+@Service
+@Transactional(rollbackFor = Exception.class)
+public class RoleServiceImpl implements RoleService {
+
+ @Autowired
+ private AuthRoleDao authRoleDao;
+
+ @Autowired
+ private AuthResourceDao authResourceDao;
+
+ @Autowired
+ private AuthRoleResourceBindDao roleResourceBindDao;
+
+ @Autowired
+ private TreePathRoleMatcher treePathRoleMatcher;
+
+ @Override
+ public boolean isRoleExist(AuthRoleDO authRole) {
+ AuthRoleDO role = AuthRoleDO.builder()
+ .name(authRole.getName()).code(authRole.getCode()).build();
+ return authRoleDao.exists(Example.of(role));
+ }
+
+ @Override
+ public boolean addRole(AuthRoleDO authRole) {
+ if (isRoleExist(authRole)) {
+ return false;
+ } else {
+ authRoleDao.saveAndFlush(authRole);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean updateRole(AuthRoleDO authRole) {
+ if (authRoleDao.existsById(authRole.getId())) {
+ authRoleDao.saveAndFlush(authRole);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean deleteRole(Long roleId) {
+ if (authRoleDao.existsById(roleId)) {
+ authRoleDao.deleteById(roleId);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Optional> getAllRole() {
+ List roleList = authRoleDao.findAll();
+ return Optional.of(roleList);
+ }
+
+ @Override
+ public Page getPageRole(Integer currentPage, Integer pageSize) {
+ PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
+ return authRoleDao.findAll(pageRequest);
+ }
+
+ @Override
+ public Page getPageResourceOwnRole(Long roleId, Integer currentPage, Integer pageSize) {
+ PageRequest pageRequest = PageRequest.of(currentPage, pageSize, Sort.Direction.ASC, "id");
+ return authResourceDao.findRoleOwnResource(roleId, pageRequest);
+ }
+
+ @Override
+ public Page getPageResourceNotOwnRole(Long roleId, Integer currentPage, Integer pageSize) {
+ PageRequest pageRequest = PageRequest.of(currentPage, pageSize, Sort.Direction.ASC, "id");
+ return authResourceDao.findRoleNotOwnResource(roleId, pageRequest);
+ }
+
+ @Override
+ public void authorityRoleResource(Long roleId, Long resourceId) {
+ // Determine whether this resource and role exist
+ if (!authRoleDao.existsById(roleId) || !authResourceDao.existsById(resourceId)) {
+ throw new DataConflictException("roleId or resourceId not exist");
+ }
+ // insert it in database, if existed the unique index will work
+ AuthRoleResourceBindDO bind = AuthRoleResourceBindDO
+ .builder().roleId(roleId).resourceId(resourceId).build();
+ roleResourceBindDao.saveAndFlush(bind);
+ // refresh resource path data tree
+ treePathRoleMatcher.rebuildTree();
+ }
+
+ @Override
+ public void deleteAuthorityRoleResource(Long roleId, Long resourceId) {
+ roleResourceBindDao.deleteRoleResourceBind(roleId, resourceId);
+ // refresh resource path data tree
+ treePathRoleMatcher.rebuildTree();
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/support/BasicSubjectReactiveCreator.java b/samples/shenyu-sureness-sql/src/main/java/sureness/support/BasicSubjectReactiveCreator.java
new file mode 100644
index 00000000..85277f4d
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/support/BasicSubjectReactiveCreator.java
@@ -0,0 +1,74 @@
+package sureness.support;
+
+import com.usthe.sureness.subject.Subject;
+import com.usthe.sureness.subject.SubjectCreate;
+import com.usthe.sureness.subject.support.PasswordSubject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+/**
+ * the creator to create PasswordSubject(basic auth)
+ * only support reactive server-side HTTP request
+ * org.springframework.http.server.reactive.ServerHttpRequest
+ * @author tomsun28
+ * @date 23:53 2020-02-27
+ */
+public class BasicSubjectReactiveCreator implements SubjectCreate {
+
+ private static final Logger logger = LoggerFactory.getLogger(BasicSubjectReactiveCreator.class);
+
+ private static final String AUTHORIZATION = "Authorization";
+ private static final String BASIC = "Basic";
+ private static final int COUNT_2 = 2;
+
+ @Override
+ public boolean canSupportSubject(Object context) {
+ // ("Authorization", "Basic YWRtaW46YWRtaW4=") --- basic auth
+ if (context instanceof ServerHttpRequest) {
+ String authorization = ((ServerHttpRequest)context).getHeaders().getFirst(AUTHORIZATION);
+ return authorization != null && authorization.startsWith(BASIC);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Subject createSubject(Object context) {
+ String authorization = ((ServerHttpRequest)context).getHeaders().getFirst(AUTHORIZATION);
+ if (authorization == null) {
+ return null;
+ }
+ //basic auth
+ String basicAuth = authorization.replace(BASIC, "").trim();
+ basicAuth = new String(Base64.getDecoder().decode(basicAuth), StandardCharsets.UTF_8);
+ String[] auth = basicAuth.split(":");
+ if (auth.length != COUNT_2) {
+ if (logger.isInfoEnabled()) {
+ logger.info("can not create basic auth PasswordSubject by this request message");
+ }
+ return null;
+ }
+ String username = auth[0];
+ if (username == null || "".equals(username)) {
+ if (logger.isInfoEnabled()) {
+ logger.info("can not create basic auth PasswordSubject by this request message, appId can not null");
+ }
+ return null;
+ }
+ String password = auth[1];
+ InetSocketAddress remoteAddress = ((ServerHttpRequest) context).getRemoteAddress();
+ String remoteHost = remoteAddress == null ? "" : remoteAddress.getHostString();
+ String requestUri = ((ServerHttpRequest) context).getPath().value();
+ String requestType = ((ServerHttpRequest) context).getMethodValue();
+ String targetUri = requestUri.concat("===").concat(requestType).toLowerCase();
+ return PasswordSubject.builder(username, password)
+ .setRemoteHost(remoteHost)
+ .setTargetResource(targetUri)
+ .build();
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/support/JwtSubjectReactiveCreator.java b/samples/shenyu-sureness-sql/src/main/java/sureness/support/JwtSubjectReactiveCreator.java
new file mode 100644
index 00000000..19d0e919
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/support/JwtSubjectReactiveCreator.java
@@ -0,0 +1,65 @@
+package sureness.support;
+
+import com.usthe.sureness.subject.Subject;
+import com.usthe.sureness.subject.SubjectCreate;
+import com.usthe.sureness.subject.support.JwtSubject;
+import com.usthe.sureness.util.JsonWebTokenUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+import java.net.InetSocketAddress;
+
+
+/**
+ * JwtSubject creator
+ * only support ServerHttpRequest
+ * @author tomsun28
+ * @date 23:58 2020-09-29
+ */
+public class JwtSubjectReactiveCreator implements SubjectCreate {
+
+ private static final Logger logger = LoggerFactory.getLogger(JwtSubjectReactiveCreator.class);
+
+ private static final String BEARER = "Bearer";
+ private static final String AUTHORIZATION = "Authorization";
+
+ @Override
+ public boolean canSupportSubject(Object context) {
+ // support bearer jwt
+ // ("Authorization", "Bearer eyJhbGciOiJIUzUxMi...") --- jwt auth
+ if (context instanceof ServerHttpRequest) {
+ String authorization = ((ServerHttpRequest)context).getHeaders().getFirst(AUTHORIZATION);
+ if (authorization != null && authorization.startsWith(BEARER)) {
+ String jwtValue = authorization.replace(BEARER, "").trim();
+ return !JsonWebTokenUtil.isNotJsonWebToken(jwtValue);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Subject createSubject(Object context) {
+ String authorization = ((ServerHttpRequest)context).getHeaders().getFirst(AUTHORIZATION);
+ if (authorization != null && authorization.startsWith(BEARER)) {
+ // jwt token
+ String jwtValue = authorization.replace(BEARER, "").trim();
+ if (JsonWebTokenUtil.isNotJsonWebToken(jwtValue)) {
+ if (logger.isInfoEnabled()) {
+ logger.info("can not create JwtSubject by this request message, is not jwt");
+ }
+ return null;
+ }
+ InetSocketAddress remoteAddress = ((ServerHttpRequest) context).getRemoteAddress();
+ String remoteHost = remoteAddress == null ? "" : remoteAddress.getHostString();
+ String requestUri = ((ServerHttpRequest) context).getPath().value();
+ String requestType = ((ServerHttpRequest) context).getMethodValue();
+ String targetUri = requestUri.concat("===").concat(requestType.toLowerCase());
+ return JwtSubject.builder(jwtValue)
+ .setRemoteHost(remoteHost)
+ .setTargetResource(targetUri)
+ .build();
+ }
+ return null;
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/support/NoneSubjectReactiveCreator.java b/samples/shenyu-sureness-sql/src/main/java/sureness/support/NoneSubjectReactiveCreator.java
new file mode 100644
index 00000000..9e3d3c87
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/support/NoneSubjectReactiveCreator.java
@@ -0,0 +1,33 @@
+package sureness.support;
+
+import com.usthe.sureness.subject.Subject;
+import com.usthe.sureness.subject.SubjectCreate;
+import com.usthe.sureness.subject.support.NoneSubject;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+import java.net.InetSocketAddress;
+
+/**
+ * the creator to create NoneSubject - no auth info
+ * all request can be created a NoneSubject by NoneSubjectReactiveCreator
+ * only support ServerHttpRequest
+ * @author tomsun28
+ * @date 22:55 2020-09-29
+ */
+public class NoneSubjectReactiveCreator implements SubjectCreate {
+ @Override
+ public boolean canSupportSubject(Object context) {
+ return context instanceof ServerHttpRequest;
+ }
+
+ @Override
+ public Subject createSubject(Object context) {
+ InetSocketAddress remoteAddress = ((ServerHttpRequest) context).getRemoteAddress();
+ String remoteHost = remoteAddress == null ? "" : remoteAddress.getHostString();
+ String requestUri = ((ServerHttpRequest) context).getPath().value();
+ String requestType = ((ServerHttpRequest) context).getMethodValue();
+ String targetUri = requestUri.concat("===").concat(requestType).toLowerCase();
+ return NoneSubject.builder().setRemoteHost(remoteHost)
+ .setTargetUri(targetUri).build();
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/SurenessFilterExample.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/SurenessFilterExample.java
new file mode 100644
index 00000000..1ea3a2d8
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/SurenessFilterExample.java
@@ -0,0 +1,105 @@
+package sureness.sureness;
+
+import com.usthe.sureness.mgt.SurenessSecurityManager;
+import com.usthe.sureness.processor.exception.ExpiredCredentialsException;
+import com.usthe.sureness.processor.exception.IncorrectCredentialsException;
+import com.usthe.sureness.processor.exception.UnauthorizedException;
+import com.usthe.sureness.processor.exception.UnknownAccountException;
+import com.usthe.sureness.subject.SubjectSum;
+import com.usthe.sureness.util.SurenessContextHolder;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.NettyDataBufferFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Nonnull;
+import java.nio.charset.StandardCharsets;
+
+
+/**
+ * @title: SurenessConfiguration
+ * @Author hqgordon
+ * @Date: 2021/7/31 5:13 下午
+ * @Description: sureness过滤器
+ * @Version 1.0
+ */
+@Configuration
+@Order(-1)
+public class SurenessFilterExample implements WebFilter {
+
+ private static final Logger logger = LoggerFactory.getLogger(SurenessFilterExample.class);
+
+ /*
+ * @Author hqgordon
+ * @Description 过滤方法
+ * @Date 2021/8/9 8:29 下午
+ * @Param [exchange, chain]
+ * @return reactor.core.publisher.Mono
+ **/
+ @Override
+ @Nonnull
+ public Mono filter(ServerWebExchange exchange,@Nonnull WebFilterChain chain) {
+ ServerHttpRequest request = exchange.getRequest();
+ HttpStatus statusCode = null;
+ String errorMsg = null;
+ try {
+ SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(request);
+ // You can consider using SurenessContextHolder to bind subject in threadLocal
+ // if bind, please remove it when end
+ if (subject != null) {
+ SurenessContextHolder.bindSubject(subject);
+ }
+ } catch (IncorrectCredentialsException | UnknownAccountException | ExpiredCredentialsException e1) {
+ logger.debug("this request account info is illegal");
+ statusCode = HttpStatus.UNAUTHORIZED;
+ errorMsg = e1.getMessage();
+ } catch (UnauthorizedException e5) {
+ logger.debug("this account can not access this resource");
+ statusCode = HttpStatus.FORBIDDEN;
+ errorMsg = e5.getMessage();
+ } catch (RuntimeException e) {
+ logger.error("other exception happen: ", e);
+ statusCode = HttpStatus.CONFLICT;
+ errorMsg = e.getMessage();
+ }
+
+ // auth error filter to error collect api
+ if (statusCode != null && errorMsg != null) {
+ String finalErrorMsg = errorMsg;
+ Integer finalStatusCode = statusCode.value();
+ request = request.mutate().headers(httpHeaders -> {
+ httpHeaders.add("statusCode", String.valueOf(finalStatusCode));
+ httpHeaders.add("errorMsg", finalErrorMsg);
+ }).path("/auth/error").build();
+ exchange = exchange.mutate().request(request).build();
+ return responseWrite(exchange,statusCode,errorMsg);
+ }
+ return chain.filter(exchange).doFinally(x -> SurenessContextHolder.unbindSubject());
+ }
+ private Mono responseWrite(ServerWebExchange exchange, HttpStatus statusCode,
+ String message) {
+
+ exchange.getResponse().setStatusCode(statusCode);
+ if (message != null) {
+ return exchange.getResponse().writeWith(Flux.create(sink -> {
+ NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
+ DataBuffer dataBuffer= nettyDataBufferFactory.wrap(message.getBytes(StandardCharsets.UTF_8));
+ sink.next(dataBuffer);
+ sink.complete();
+ }));
+ } else {
+ return exchange.getResponse().setComplete();
+ }
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/config/SurenessConfiguration.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/config/SurenessConfiguration.java
new file mode 100644
index 00000000..03261ce5
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/config/SurenessConfiguration.java
@@ -0,0 +1,121 @@
+package sureness.sureness.config;
+
+import com.usthe.sureness.matcher.DefaultPathRoleMatcher;
+import com.usthe.sureness.matcher.PathTreeProvider;
+import com.usthe.sureness.matcher.TreePathRoleMatcher;
+import com.usthe.sureness.mgt.SurenessSecurityManager;
+import com.usthe.sureness.processor.DefaultProcessorManager;
+import com.usthe.sureness.processor.Processor;
+import com.usthe.sureness.processor.ProcessorManager;
+import com.usthe.sureness.processor.support.JwtProcessor;
+import com.usthe.sureness.processor.support.NoneProcessor;
+import com.usthe.sureness.processor.support.PasswordProcessor;
+import com.usthe.sureness.provider.SurenessAccountProvider;
+import com.usthe.sureness.provider.annotation.AnnotationPathTreeProvider;
+import com.usthe.sureness.provider.ducument.DocumentPathTreeProvider;
+import com.usthe.sureness.subject.SubjectFactory;
+import com.usthe.sureness.subject.SurenessSubjectFactory;
+import com.usthe.sureness.util.JsonWebTokenUtil;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import sureness.support.BasicSubjectReactiveCreator;
+import sureness.support.JwtSubjectReactiveCreator;
+import sureness.support.NoneSubjectReactiveCreator;
+import sureness.sureness.processor.CustomTokenProcessor;
+import sureness.sureness.subject.CustomPasswdSubjectCreator;
+import sureness.sureness.subject.CustomTokenSubjectCreator;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * sureness config
+ * @author tomsun28
+ * @date 22:40 2020-03-02
+ */
+@Configuration
+public class SurenessConfiguration {
+
+ /**
+ * jwt secret key
+ */
+ private static final String TOM_SECRET_KEY = "?::4s9ssf2sf4sed45pf):" +
+ "RnLN7XNn4wARoQXizIv6MHUsIV+EFfiMw/x7R0ntu4aWr/CWuApcFaj" +
+ "CyaFv0bwq2Eik0jdrKUtsA6bx3sDJeFV643R+YYzGMRIqcBIp6AKA98" +
+ "GM2RIqcBIp6-?::4390fsf4sdl6opf)4ZI:tdQMtcQQ14pkOAQdQ546";
+
+ @Bean
+ ProcessorManager processorManager(SurenessAccountProvider accountProvider) {
+ // process init
+ List processorList = new LinkedList<>();
+ // use default none processor
+ NoneProcessor noneProcessor = new NoneProcessor();
+ processorList.add(noneProcessor);
+ // use default jwt processor
+ JwtProcessor jwtProcessor = new JwtProcessor();
+ processorList.add(jwtProcessor);
+ // use default basic auth processor
+ PasswordProcessor passwordProcessor = new PasswordProcessor();
+ passwordProcessor.setAccountProvider(accountProvider);
+ processorList.add(passwordProcessor);
+
+ // use custom token processor
+ CustomTokenProcessor customTokenProcessor = new CustomTokenProcessor();
+ customTokenProcessor.setAccountProvider(accountProvider);
+ processorList.add(customTokenProcessor);
+ return new DefaultProcessorManager(processorList);
+ }
+
+ /**
+ * @param databasePathTreeProvider the path tree resource load from database
+ */
+ @Bean
+ TreePathRoleMatcher pathRoleMatcher(PathTreeProvider databasePathTreeProvider) {
+ // the path tree resource load from document - sureness.yml
+ PathTreeProvider documentPathTreeProvider = new DocumentPathTreeProvider();
+ // the path tree resource load form annotation - @RequiresRoles @WithoutAuth
+ AnnotationPathTreeProvider annotationPathTreeProvider = new AnnotationPathTreeProvider();
+ annotationPathTreeProvider.setScanPackages(Collections.singletonList("com.usthe.sureness.sample.tom.controller"));
+ // pathRoleMatcher init
+ DefaultPathRoleMatcher pathRoleMatcher = new DefaultPathRoleMatcher();
+ pathRoleMatcher.setPathTreeProviderList(Arrays.asList(
+ documentPathTreeProvider,
+ annotationPathTreeProvider,
+ databasePathTreeProvider));
+ pathRoleMatcher.buildTree();
+ return pathRoleMatcher;
+ }
+
+ @Bean
+ SubjectFactory subjectFactory() {
+ // SubjectFactory init
+ SubjectFactory subjectFactory = new SurenessSubjectFactory();
+ subjectFactory.registerSubjectCreator(Arrays.asList(
+ // attention! must add noSubjectCreator first
+ new NoneSubjectReactiveCreator(),
+ // use default basic auth subject creator
+ new BasicSubjectReactiveCreator(),
+ // use default jwt subject creator
+ new JwtSubjectReactiveCreator(),
+ // use custom password creator
+ new CustomPasswdSubjectCreator(),
+ // use custom token creator
+ new CustomTokenSubjectCreator()));
+ return subjectFactory;
+ }
+
+ @Bean
+ SurenessSecurityManager securityManager(ProcessorManager processorManager,
+ TreePathRoleMatcher pathRoleMatcher, SubjectFactory subjectFactory) {
+ JsonWebTokenUtil.setDefaultSecretKey(TOM_SECRET_KEY);
+ // surenessSecurityManager init
+ SurenessSecurityManager securityManager = SurenessSecurityManager.getInstance();
+ securityManager.setPathRoleMatcher(pathRoleMatcher);
+ securityManager.setSubjectFactory(subjectFactory);
+ securityManager.setProcessorManager(processorManager);
+ return securityManager;
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/processor/CustomTokenProcessor.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/processor/CustomTokenProcessor.java
new file mode 100644
index 00000000..c71b75a6
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/processor/CustomTokenProcessor.java
@@ -0,0 +1,104 @@
+package sureness.sureness.processor;
+
+import com.usthe.sureness.processor.BaseProcessor;
+import com.usthe.sureness.processor.exception.IncorrectCredentialsException;
+import com.usthe.sureness.processor.exception.SurenessAuthenticationException;
+import com.usthe.sureness.processor.exception.SurenessAuthorizationException;
+import com.usthe.sureness.processor.exception.UnauthorizedException;
+import com.usthe.sureness.provider.SurenessAccount;
+import com.usthe.sureness.provider.SurenessAccountProvider;
+import com.usthe.sureness.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import sureness.controller.TokenStorage;
+import sureness.sureness.subject.CustomTokenSubject;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * custom token processor, support CustomTokenSubject
+ * when token Expired and can refresh, return refresh token value
+ *
+ * @author tomsun28
+ * @date 2020-12-03 20:37
+ */
+public class CustomTokenProcessor extends BaseProcessor {
+
+ private static final Logger logger = LoggerFactory.getLogger(CustomTokenProcessor.class);
+ private static final String TOKEN_SPLIT = "--";
+ private static final int START_TIME_INDEX = 1;
+ private static final int PERIOD_TIME_INDEX = 2;
+ private static final int DOUBLE_TIME = 2;
+
+ private SurenessAccountProvider accountProvider;
+
+ @Override
+ public boolean canSupportSubjectClass(Class> var) {
+ return var == CustomTokenSubject.class;
+ }
+
+ @Override
+ public Class> getSupportSubjectClass() {
+ return CustomTokenSubject.class;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Subject authenticated(Subject var) throws SurenessAuthenticationException {
+ String token = (String) var.getCredential();
+ String[] tokenArr = token.split(TOKEN_SPLIT);
+ if (TokenStorage.matchToken(tokenArr[0], token)) {
+ // auth passed
+ String appId = tokenArr[0];
+ SurenessAccount account = accountProvider.loadAccount(appId);
+ return CustomTokenSubject.builder(var)
+ .setPrincipal(appId)
+ .setOwnRoles(account.getOwnRoles()).build();
+
+ } else {
+ // token expired or not exist, if token can refresh, refresh it
+ // if expired time is not longer than refreshPeriodTime/2 , it can refresh
+ if (Long.parseLong(tokenArr[START_TIME_INDEX]) + (Long.parseLong(tokenArr[PERIOD_TIME_INDEX]) * DOUBLE_TIME)
+ >= System.currentTimeMillis()) {
+ long refreshPeriodTime = 36000L;
+ String refreshToken = tokenArr[0] + TOKEN_SPLIT + System.currentTimeMillis()
+ + TOKEN_SPLIT + refreshPeriodTime
+ + TOKEN_SPLIT + UUID.randomUUID().toString().replace("-", "");
+ TokenStorage.addToken(tokenArr[0], refreshToken);
+ throw new RefreshExpiredTokenException(refreshToken);
+ } else if (Long.parseLong(tokenArr[START_TIME_INDEX]) + Long.parseLong(tokenArr[PERIOD_TIME_INDEX])
+ <= System.currentTimeMillis()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("CustomTokenProcessor authenticated expired");
+ }
+ throw new IncorrectCredentialsException("the token authenticated expired, please get new one");
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("CustomTokenProcessor authenticated fail");
+ }
+ throw new IncorrectCredentialsException("the token authenticated error");
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void authorized(Subject var) throws SurenessAuthorizationException {
+ List ownRoles = (List) var.getOwnRoles();
+ List supportRoles = (List) var.getSupportRoles();
+ // if null, note that not config this resource
+ if (supportRoles == null) {
+ return;
+ }
+ // if config, ownRole must contain the supportRole item
+ if (ownRoles != null && supportRoles.stream().anyMatch(ownRoles::contains)) {
+ return;
+ }
+ throw new UnauthorizedException("custom authorized: do not have the role to access resource");
+ }
+
+ public void setAccountProvider(SurenessAccountProvider accountProvider) {
+ this.accountProvider = accountProvider;
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/processor/RefreshExpiredTokenException.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/processor/RefreshExpiredTokenException.java
new file mode 100644
index 00000000..76f9c335
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/processor/RefreshExpiredTokenException.java
@@ -0,0 +1,14 @@
+package sureness.sureness.processor;
+
+import com.usthe.sureness.processor.exception.SurenessAuthenticationException;
+
+/**
+ * refresh token message
+ * @author tomsun28
+ * @date 2020-12-03 23:29
+ */
+public class RefreshExpiredTokenException extends SurenessAuthenticationException {
+ public RefreshExpiredTokenException(String message) {
+ super(message);
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/provider/DatabaseAccountProvider.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/provider/DatabaseAccountProvider.java
new file mode 100644
index 00000000..0e3832fb
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/provider/DatabaseAccountProvider.java
@@ -0,0 +1,25 @@
+package sureness.sureness.provider;
+
+import com.usthe.sureness.provider.SurenessAccount;
+import com.usthe.sureness.provider.SurenessAccountProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import sureness.service.AccountService;
+
+/**
+ * the provider provides account info
+ * load account info from database
+ * @author tomsun28
+ * @date 22:44 2020-03-02
+ */
+@Component
+public class DatabaseAccountProvider implements SurenessAccountProvider {
+
+ @Autowired
+ AccountService accountService;
+
+ @Override
+ public SurenessAccount loadAccount(String appId) {
+ return accountService.loadAccount(appId);
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/provider/DatabasePathTreeProvider.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/provider/DatabasePathTreeProvider.java
new file mode 100644
index 00000000..05c10171
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/provider/DatabasePathTreeProvider.java
@@ -0,0 +1,34 @@
+package sureness.sureness.provider;
+
+import com.usthe.sureness.matcher.PathTreeProvider;
+import com.usthe.sureness.util.SurenessCommonUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import sureness.service.ResourceService;
+
+import java.util.Set;
+
+/**
+ * ths provider provides path resources
+ * load sureness config resource form database
+ * @author tomsun28
+ * @date 16:00 2019-08-04
+ */
+@Component
+public class DatabasePathTreeProvider implements PathTreeProvider {
+
+ @Autowired
+ private ResourceService resourceService;
+
+ @Override
+ public Set providePathData() {
+ return SurenessCommonUtil.attachContextPath(getContextPath(), resourceService.getAllEnableResourcePath());
+
+ }
+
+ @Override
+ public Set provideExcludedResource() {
+ return SurenessCommonUtil.attachContextPath(getContextPath(), resourceService.getAllDisableResourcePath());
+ }
+
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomPasswdSubjectCreator.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomPasswdSubjectCreator.java
new file mode 100644
index 00000000..c1f73bc0
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomPasswdSubjectCreator.java
@@ -0,0 +1,54 @@
+package sureness.sureness.subject;
+
+import com.usthe.sureness.subject.Subject;
+import com.usthe.sureness.subject.SubjectCreate;
+import com.usthe.sureness.subject.support.PasswordSubject;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+
+/**
+ * custom subject creator
+ * A custom creator is demonstrated here
+ * In addition to the basic auth method, we may obtain our account password from other places for authentication.
+ * eg: username and password in header
+ * header {
+ * "username": "userTom",
+ * "password": "123456"
+ * }
+ * Here we define a creator to create PasswordSubject from this request header like above.
+ * @author tomsun28
+ * @date 22:59 2020-03-02
+ */
+public class CustomPasswdSubjectCreator implements SubjectCreate {
+
+ private static final String USERNAME = "username";
+ private static final String PASSWORD = "password";
+
+ @Override
+ public boolean canSupportSubject(Object context) {
+ // define which request can be access
+ if (context instanceof ServerHttpRequest) {
+ String username = ((ServerHttpRequest)context).getHeaders().getFirst("Username");
+ String password = ((ServerHttpRequest)context).getHeaders().getFirst("Password");
+ return username != null && password != null;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Subject createSubject(Object context) {
+ // create PasswordSubject from request
+ String username = ((ServerHttpRequest)context).getHeaders().getFirst("Username");
+ String password = ((ServerHttpRequest)context).getHeaders().getFirst("Password");
+
+ String remoteHost = ((ServerHttpRequest) context).getRemoteAddress().getHostString();
+ String requestUri = ((ServerHttpRequest) context).getURI().getPath();
+ String requestType = ((ServerHttpRequest) context).getMethod().toString();
+ String targetUri = requestUri.concat("===").concat(requestType).toLowerCase();
+ return PasswordSubject.builder(username, password)
+ .setRemoteHost(remoteHost)
+ .setTargetResource(targetUri)
+ .build();
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomTokenSubject.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomTokenSubject.java
new file mode 100644
index 00000000..df8462d3
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomTokenSubject.java
@@ -0,0 +1,163 @@
+package sureness.sureness.subject;
+
+import com.usthe.sureness.subject.PrincipalMap;
+import com.usthe.sureness.subject.Subject;
+
+import java.util.List;
+
+/**
+ * custom define token subject
+ * @author tomsun28
+ * @date 2020-12-03 22:08
+ */
+public class CustomTokenSubject implements Subject {
+
+ private static final long serialVersionUID = 1L;
+
+ /** user identifier **/
+ private String appId;
+
+ /** token : admin--issueTime--refreshPeriodTime--uuid **/
+ private String token;
+
+ /** remote ip **/
+ private String remoteHost;
+
+ /** remote device **/
+ private String userAgent;
+
+ /** the roles which this user owned **/
+ private List ownRoles;
+
+ /** the uri resource which this user want access **/
+ private String targetUri;
+
+ /** the Roles which can access this resource above-targetUri **/
+ private List supportRoles;
+
+ private CustomTokenSubject(Builder builder) {
+ this.appId = builder.appId;
+ this.token = builder.token;
+ this.remoteHost = builder.remoteHost;
+ this.userAgent = builder.userAgent;
+ this.ownRoles = builder.ownRoles;
+ this.supportRoles = builder.supportRoles;
+ this.targetUri = builder.targetUri;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return this.appId;
+ }
+
+ @Override
+ public PrincipalMap getPrincipalMap() {
+ return null;
+ }
+
+ @Override
+ public Object getCredential() {
+ return this.token;
+ }
+
+ @Override
+ public Object getOwnRoles() {
+ return this.ownRoles;
+ }
+
+ @Override
+ public Object getTargetResource() {
+ return this.targetUri;
+ }
+
+ @Override
+ public Object getSupportRoles() {
+ return this.supportRoles;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setSupportRoles(Object var1) {
+ this.supportRoles = (List) var1;
+ }
+
+ public String getRemoteHost() {
+ return remoteHost;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ public static Builder builder(String token) {
+ return new Builder(token);
+ }
+
+ public static Builder builder(Subject subject) {
+ return new Builder(subject);
+ }
+
+ public static class Builder {
+
+ private String appId;
+ private String token;
+ private String remoteHost;
+ private String userAgent;
+ private List ownRoles;
+ private String targetUri;
+ private List supportRoles;
+
+ public Builder(String token) {
+ this.token = token;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Builder(Subject subject) {
+ this.appId = String.valueOf(subject.getPrincipal());
+ this.token = String.valueOf(subject.getCredential());
+ this.ownRoles = (List) subject.getOwnRoles();
+ this.targetUri = String.valueOf(subject.getTargetResource());
+ this.supportRoles = (List) subject.getSupportRoles();
+ }
+
+ public Builder setPrincipal(String appId) {
+ this.appId = appId;
+ return this;
+ }
+
+ public Builder setCredentials(String token) {
+ this.token = token;
+ return this;
+ }
+
+ public Builder setTargetResource(String targetUri) {
+ this.targetUri = targetUri;
+ return this;
+ }
+
+ public Builder setOwnRoles(List ownRoles) {
+ this.ownRoles = ownRoles;
+ return this;
+ }
+
+ public Builder setSupportRoles(List supportRoles) {
+ this.supportRoles = supportRoles;
+ return this;
+ }
+
+ public Builder setRemoteHost(String remoteHost) {
+ this.remoteHost = remoteHost;
+ return this;
+ }
+
+ public Builder setUserAgent(String userAgent) {
+ this.userAgent = userAgent;
+ return this;
+ }
+
+ public CustomTokenSubject build() {
+ return new CustomTokenSubject(this);
+ }
+
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomTokenSubjectCreator.java b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomTokenSubjectCreator.java
new file mode 100644
index 00000000..68a76959
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/java/sureness/sureness/subject/CustomTokenSubjectCreator.java
@@ -0,0 +1,45 @@
+package sureness.sureness.subject;
+
+import com.usthe.sureness.subject.Subject;
+import com.usthe.sureness.subject.SubjectCreate;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+
+import java.util.Objects;
+
+
+/**
+ * custom token creator, get token from http request header - {"Token" : "tokenValue"}
+ * tokenValue is : admin--issueTime--refreshPeriodTime--uuid
+ * @author tomsun28
+ * @date 2020-12-03 22:08
+ */
+public class CustomTokenSubjectCreator implements SubjectCreate {
+
+ private static final String TOKEN_SPLIT = "--";
+
+ private static final int TOKEN_SPLIT_SIZE = 4;
+
+ @Override
+ public boolean canSupportSubject(Object context) {
+ // support token
+ // {"Token" : "tokenValue"}
+ if (context instanceof ServerHttpRequest) {
+ String authorization = ((ServerHttpRequest)context).getHeaders().getFirst("Token");
+ return authorization != null && authorization.split(TOKEN_SPLIT).length == TOKEN_SPLIT_SIZE;
+ }
+ return false;
+ }
+
+ @Override
+ public Subject createSubject(Object context) {
+ String authorization = ((ServerHttpRequest)context).getHeaders().getFirst("Token");
+ String remoteHost = Objects.requireNonNull(((ServerHttpRequest) context).getRemoteAddress()).getHostString();
+ String requestUri = ((ServerHttpRequest) context).getURI().getPath();
+ String requestType = ((ServerHttpRequest) context).getMethodValue();
+ String targetUri = requestUri.concat("===").concat(requestType.toLowerCase());
+ return CustomTokenSubject.builder(authorization)
+ .setRemoteHost(remoteHost)
+ .setTargetResource(targetUri)
+ .build();
+ }
+}
diff --git a/samples/shenyu-sureness-sql/src/main/resources/application.yml b/samples/shenyu-sureness-sql/src/main/resources/application.yml
new file mode 100644
index 00000000..b1cbcce4
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/resources/application.yml
@@ -0,0 +1,54 @@
+server:
+ port: 8081
+spring:
+ profiles:
+ active: dev
+ main:
+ allow-bean-definition-overriding: true
+soul:
+ sync:
+ websocket:
+ urls: ws://localhost:9095/websocket
+
+#jasypt:
+# encryptor:
+# password: e!swhdg&d763jo
+
+---
+spring:
+ profiles: dev
+ datasource:
+ driver-class-name: com.mysql.jdbc.Driver
+ username: username
+ password: password
+ url: jdbc:mysql://localhost:3306/tom?useUnicode=true&characterEncoding=utf-8&useSSL=false
+ platform: mysql
+ schema: classpath:db/schema.sql
+ data: classpath:db/data.sql
+ # 每次重启都会根据data.sql schema.sql重建表数据, 设置 NEVER 就不启动重建
+ # Each restart will rebuild the table data according to data.sql schema.sql
+ # setting NEVER will not start the reconstruction
+ initialization-mode: always
+ jackson:
+ serialization:
+ FAIL_ON_EMPTY_BEANS: false
+ jpa:
+ database: mysql
+ show-sql: true
+
+
+---
+spring:
+ profiles: prod
+ datasource:
+ driver-class-name: com.mysql.jdbc.Driver
+ username: username
+ password: password
+ url: jdbc:mysql://localhost:3306/tom?useUnicode=true&characterEncoding=utf-8&useSSL=false
+ platform: mysql
+ schema: classpath:db/schema.sql
+ data: classpath:db/data.sql
+ initialization-mode: always
+ jpa:
+ database: mysql
+ show-sql: true
diff --git a/samples/shenyu-sureness-sql/src/main/resources/db/data.sql b/samples/shenyu-sureness-sql/src/main/resources/db/data.sql
new file mode 100644
index 00000000..34eea684
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/resources/db/data.sql
@@ -0,0 +1,71 @@
+use tom ;
+-- ----------------------------
+-- Records of auth_resource
+-- ----------------------------
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (101, 'User get token', 'ACCOUNT_TOKEN', '/auth/token', 'account', 'POST', 9, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (102, 'User register', 'ACCOUNT_REGISTER', '/auth/register', 'account', 'POST', 9, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (103, 'Add resource', 'ADD_RESOURCE', '/resource', 'resource', 'POST', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (104, 'Update resource', 'UPDATE_RESOURCE', '/resource', 'resource', 'PUT', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (105, 'Delete resource', 'DELETE_RESOURCE', '/resource/*', 'resource', 'DELETE', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (106, 'Get resource', 'GET_RESOURCES', '/resource/*/*', 'resource', 'GET', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (107, 'Add role', 'ADD_ROLE', '/role', 'role', 'POST', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (108, 'Update role', 'UPDATE_ROLE', '/role', 'role', 'PUT', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (109, 'Delete role', 'DELETE_ROLE', '/role/*', 'role', 'DELETE', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (110, 'Get role', 'GET_ROLES', '/role/*/*', 'role', 'GET', 1, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (111, 'User get custom token', 'ACCOUNT_CUSTOM_TOKEN', '/auth/custom/token', 'account', 'POST', 9, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (112, 'Static Resource', 'Static Resource', '/**/*.html', 'static', 'GET', 9, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (113, 'Static Resource', 'Static Resource', '/**/*.js', 'static', 'GET', 9, null);
+insert into auth_resource (id, name, code, uri, type, method, status, description) values (114, 'Static Resource', 'Static Resource', '/**/*.css', 'static', 'GET', 9, null);
+
+-- ----------------------------
+-- Records of auth_role
+-- ----------------------------
+insert into auth_role (id, name, code, status, description) values (100, 'admin role', 'role_admin', 1, null);
+insert into auth_role (id, name, code, status, description) values (102, 'user role', 'role_user', 1, null);
+insert into auth_role (id, name, code, status, description) values (103, 'guest role', 'role_guest', 1, null);
+
+-- ----------------------------
+-- Records of auth_role_resource_bind
+-- ----------------------------
+-- role_admin has these resource
+insert into auth_role_resource_bind (id, role_id, resource_id) values (1, 100, 101);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (2, 100, 102);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (3, 100, 103);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (4, 100, 104);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (5, 100, 105);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (6, 100, 106);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (7, 100, 107);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (8, 100, 108);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (9, 100, 109);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (10, 100, 110);
+
+-- role_user has "get,add,update" related resources, do not have "delete" related resources
+insert into auth_role_resource_bind (id, role_id, resource_id) values (11, 102, 103);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (12, 102, 104);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (13, 102, 106);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (14, 102, 107);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (15, 102, 108);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (16, 102, 110);
+
+-- role_guest has "get" related resources
+insert into auth_role_resource_bind (id, role_id, resource_id) values (17, 103, 106);
+insert into auth_role_resource_bind (id, role_id, resource_id) values (18, 103, 110);
+
+
+-- ----------------------------
+-- Records of auth_user
+-- ----------------------------
+insert into auth_user (id, username, password, salt, avatar, phone, email, sex, status, create_where) values (111, 'admin', 'admin', null, null, null, null, null, 1, 1);
+insert into auth_user (id, username, password, salt, avatar, phone, email, sex, status, create_where) values (112, 'user', 'user', null, null, null, null, null, 1, 1);
+insert into auth_user (id, username, password, salt, avatar, phone, email, sex, status, create_where) values (113, 'guest', 'guest', null, null, null, null, null, 1, 1);
+insert into auth_user (id, username, password, salt, avatar, phone, email, sex, status, create_where) values (114, 'noRole', 'noRole', null, null, null, null, null, 1, 1);
+
+
+
+-- ----------------------------
+-- Records of auth_user_role_bind
+-- ----------------------------
+insert into auth_user_role_bind (id, user_id, role_id) values (12, 111, 100);
+insert into auth_user_role_bind (id, user_id, role_id) values (13, 111, 102);
+insert into auth_user_role_bind (id, user_id, role_id) values (14, 112, 102);
+insert into auth_user_role_bind (id, user_id, role_id) values (15, 113, 103);
\ No newline at end of file
diff --git a/samples/shenyu-sureness-sql/src/main/resources/db/schema.sql b/samples/shenyu-sureness-sql/src/main/resources/db/schema.sql
new file mode 100644
index 00000000..a238c9d6
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/resources/db/schema.sql
@@ -0,0 +1,88 @@
+use tom;
+
+-- ----------------------------
+-- Table structure for auth_resource
+-- ----------------------------
+DROP TABLE IF EXISTS auth_resource ;
+CREATE TABLE auth_resource
+(
+ id bigint not null auto_increment comment 'resource ID',
+ name varchar(50) not null comment 'resource name',
+ code varchar(50) not null comment 'resource code',
+ uri varchar(255) not null comment 'access URL',
+ type varchar(20) comment 'resource type',
+ method varchar(10) not null comment 'access method: GET POST PUT DELETE PATCH',
+ status smallint(4) not null default 1 comment 'status 1:normal、9:filtering(Exclusion protection-all can access this api)',
+ description varchar(255) comment 'resource description',
+ gmt_create timestamp default current_timestamp comment 'create time',
+ gmt_update datetime default current_timestamp on update current_timestamp comment 'update time',
+ primary key (id)
+) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for auth_role
+-- ----------------------------
+DROP TABLE IF EXISTS auth_role ;
+CREATE TABLE auth_role
+(
+ id bigint not null auto_increment comment 'role ID',
+ name varchar(50) not null comment 'role name',
+ code varchar(50) not null comment 'role code',
+ status smallint(4) not null default 1 comment 'status 1:normal、9:disable',
+ description varchar(255) comment 'role description',
+ gmt_create timestamp default current_timestamp comment 'create time',
+ gmt_update datetime default current_timestamp on update current_timestamp comment 'update time',
+ primary key ( id )
+) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for auth_role_resource_bind
+-- ----------------------------
+DROP TABLE IF EXISTS auth_role_resource_bind;
+CREATE TABLE auth_role_resource_bind
+(
+ id bigint not null auto_increment comment 'ID',
+ role_id bigint not null comment 'role ID',
+ resource_id bigint not null comment 'resource ID',
+ gmt_create timestamp default current_timestamp comment 'create time',
+ gmt_update datetime default current_timestamp on update current_timestamp comment 'update time',
+ primary key (id),
+ unique key unique_bind (role_id, resource_id)
+) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for auth_user
+-- ----------------------------
+DROP TABLE IF EXISTS auth_user ;
+CREATE TABLE auth_user
+(
+ id bigint not null auto_increment comment 'ID',
+ username varchar(50) not null comment 'username(nick_name)',
+ password varchar(50) not null comment 'password=MD5(passwd+salt)',
+ salt varchar(20) comment 'salt',
+ avatar varchar(100) comment 'avatar',
+ phone varchar(20) comment 'phone number(unique)',
+ email varchar(50) comment 'email(unique)',
+ sex tinyint(4) comment 'sex(1.man 2.woman)',
+ status tinyint(4) not null default 1 comment 'account status(1.normal 2.locked 3.deleted 4.illegal)',
+ create_where tinyint(4) comment 'create where(1.web 2.android 3.ios 4.win 5.mac 6.linux)',
+ gmt_create timestamp default current_timestamp comment 'create time',
+ gmt_update datetime default current_timestamp on update current_timestamp comment 'update time',
+ primary key (id),
+ unique (username, phone, email)
+) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for auth_user_role_bind
+-- ----------------------------
+DROP TABLE IF EXISTS auth_user_role_bind ;
+CREATE TABLE auth_user_role_bind
+(
+ id bigint not null auto_increment comment 'ID',
+ user_id bigint not null comment 'user ID',
+ role_id bigint not null comment 'role ID',
+ gmt_create timestamp default current_timestamp comment 'create time',
+ gmt_update datetime default current_timestamp on update current_timestamp comment 'update time',
+ primary key (id),
+ unique key unique_bind (user_id, role_id)
+) ENGINE = InnoDB DEFAULT CHARSET=utf8;
\ No newline at end of file
diff --git a/samples/shenyu-sureness-sql/src/main/resources/sureness.yml b/samples/shenyu-sureness-sql/src/main/resources/sureness.yml
new file mode 100644
index 00000000..c5087610
--- /dev/null
+++ b/samples/shenyu-sureness-sql/src/main/resources/sureness.yml
@@ -0,0 +1,22 @@
+## -- sureness.yml document dataSource-- ##
+
+# load api resource which need be protected, config role who can access these resource.
+# resources that are not configured are also authenticated and protected by default, but not authorized
+# eg: /api/v2/host===post===[role2,role3,role4] means /api/v2/host===post can be access by role2,role3,role4
+# eg: /api/v1/getSource3===get===[] means /api/v1/getSource3===get can not be access by any role
+resourceRole:
+ - /api/v2/host===post===[role2,role3,role4]
+ - /api/v1/getSource3===get===[]
+
+
+# load api resource which do not need be protected, means them need be excluded.
+# these api resource can be access by everyone
+excludedResource:
+ - /api/v3/host===get
+ - /api/v3/book===get
+ - /api/v1/account/auth===post
+ - /**/*.html===get
+ - /**/*.js===get
+ - /**/*.css===get
+ - /**/*.ico===get
+ - /**/*.png===*
diff --git a/samples/shenyu-sureness/pom.xml b/samples/shenyu-sureness/pom.xml
new file mode 100644
index 00000000..0c0ef605
--- /dev/null
+++ b/samples/shenyu-sureness/pom.xml
@@ -0,0 +1,77 @@
+
+
+
+ samples
+ com.usthe.sureness
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ shenyu-sureness
+
+
+ 1.8
+ 2020.0.2
+ 1.0.3
+
+
+
+
+ org.yaml
+ snakeyaml
+ 1.25
+
+
+ com.usthe.sureness
+ sureness-core
+ 1.0.3
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+ 2.2.2.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+ 2.2.2.RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+ org.dromara
+ soul-spring-boot-starter-gateway
+ 2.3.0-RELEASE
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+ org.dromara
+ soul-spring-boot-starter-sync-data-websocket
+ 2.3.0-RELEASE
+
+
+ org.dromara
+ soul-spring-boot-starter-plugin-httpclient
+ 2.3.0-RELEASE
+
+
+ org.dromara
+ soul-spring-boot-starter-plugin-divide
+ 2.3.0-RELEASE
+
+
+
+
diff --git a/samples/shenyu-sureness/src/main/java/gateway/ShenyuApplication.java b/samples/shenyu-sureness/src/main/java/gateway/ShenyuApplication.java
new file mode 100644
index 00000000..d2a1180f
--- /dev/null
+++ b/samples/shenyu-sureness/src/main/java/gateway/ShenyuApplication.java
@@ -0,0 +1,18 @@
+package gateway;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @title: ShenyuApplication
+ * @Author hqgordon
+ * @Date: 2021/7/31 9:05 上午
+ * @Description:
+ * @Version 1.0
+ */
+@SpringBootApplication
+public class ShenyuApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ShenyuApplication.class,args);
+ }
+}
\ No newline at end of file
diff --git a/samples/shenyu-sureness/src/main/java/gateway/shenyu/SurenessConfiguration.java b/samples/shenyu-sureness/src/main/java/gateway/shenyu/SurenessConfiguration.java
new file mode 100644
index 00000000..5b3ecadc
--- /dev/null
+++ b/samples/shenyu-sureness/src/main/java/gateway/shenyu/SurenessConfiguration.java
@@ -0,0 +1,25 @@
+package gateway.shenyu;
+
+import com.usthe.sureness.DefaultSurenessConfig;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @title: SurenessConfiguration
+ * @Author hqgordon
+ * @Date: 2021/7/31 5:13 下午
+ * @Description: 配置类
+ * @Version 1.0
+ */
+@Configuration
+public class SurenessConfiguration {
+ /**
+ * new sureness default config bean
+ * @return default config bean
+ */
+ @Bean
+ public DefaultSurenessConfig surenessConfig() {
+ return new DefaultSurenessConfig(DefaultSurenessConfig.SUPPORT_SPRING_REACTIVE);
+ }
+
+}
diff --git a/samples/shenyu-sureness/src/main/java/gateway/shenyu/SurenessFilter.java b/samples/shenyu-sureness/src/main/java/gateway/shenyu/SurenessFilter.java
new file mode 100644
index 00000000..708759e2
--- /dev/null
+++ b/samples/shenyu-sureness/src/main/java/gateway/shenyu/SurenessFilter.java
@@ -0,0 +1,102 @@
+package gateway.shenyu;
+
+import com.usthe.sureness.mgt.SurenessSecurityManager;
+import com.usthe.sureness.processor.exception.*;
+import com.usthe.sureness.subject.SubjectSum;
+import com.usthe.sureness.util.SurenessContextHolder;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import lombok.NonNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.NettyDataBufferFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+
+import javax.annotation.Nonnull;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * @title: SurenessConfiguration
+ * @Author hqgordon
+ * @Date: 2021/7/31 5:13 下午
+ * @Description: sureness过滤器
+ * @Version 1.0
+ */
+@Component
+@Order(-1)
+public class SurenessFilter implements WebFilter {
+
+ private static final Logger logger = LoggerFactory.getLogger(SurenessFilter.class);
+ /*
+ * @Author hqgordon
+ * @Description //TODO
+ * @Date 2021/8/4 8:38 上午
+ * @Param [exchange, chain]
+ * @return reactor.core.publisher.Mono
+ **/
+ @Override
+ @Nonnull
+ public Mono filter(ServerWebExchange exchange, @NonNull WebFilterChain chain) {
+ ServerHttpRequest request = exchange.getRequest();
+ try {
+ SubjectSum subject = SurenessSecurityManager.getInstance().checkIn(request);
+ // You can consider using SurenessContextHolder to bind subject in threadLocal
+ // if bind, please remove it when end
+ if (subject != null) {
+ SurenessContextHolder.bindSubject(subject);
+ }
+ } catch (IncorrectCredentialsException | UnknownAccountException | ExpiredCredentialsException e1) {
+ logger.debug("this request is illegal");
+ return responseWrite(exchange, HttpStatus.UNAUTHORIZED, e1.getMessage(),null);
+ } catch (NeedDigestInfoException e5) {
+ logger.debug("you should try once again with digest auth information");
+ return responseWrite(exchange, HttpStatus.UNAUTHORIZED,
+ "try once again with digest auth information",
+ Collections.singletonMap("WWW-Authenticate", e5.getAuthenticate()));
+ } catch (UnauthorizedException e5) {
+ logger.debug("this account can not access this resource");
+ return responseWrite(exchange, HttpStatus.FORBIDDEN, e5.getMessage(),null);
+ } catch (RuntimeException e) {
+ logger.error("other exception happen: ", e);
+ return responseWrite(exchange, HttpStatus.FORBIDDEN, e.getMessage(),null);
+ }
+ return chain.filter(exchange).doFinally(x -> SurenessContextHolder.unbindSubject());
+
+ }
+ /*
+ * @Author hqgordon
+ * @Description //TODO
+ * @Date 2021/8/4 8:38 上午
+ * @Param [exchange, statusCode, message, headers]
+ * @return reactor.core.publisher.Mono
+ **/
+ private Mono responseWrite(ServerWebExchange exchange, HttpStatus statusCode,
+ String message, Map headers) {
+ exchange.getResponse().setStatusCode(statusCode);
+ if (headers != null) {
+ headers.forEach((key, value) -> exchange.getResponse().getHeaders().add(key, value));
+ }
+ if (message != null) {
+ return exchange.getResponse().writeWith(Flux.create(sink -> {
+ NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
+ DataBuffer dataBuffer= nettyDataBufferFactory.wrap(message.getBytes(StandardCharsets.UTF_8));
+ sink.next(dataBuffer);
+ sink.complete();
+ }));
+ } else {
+ return exchange.getResponse().setComplete();
+ }
+ }
+
+}
diff --git a/samples/shenyu-sureness/src/main/resources/application.yml b/samples/shenyu-sureness/src/main/resources/application.yml
new file mode 100644
index 00000000..f3fcd838
--- /dev/null
+++ b/samples/shenyu-sureness/src/main/resources/application.yml
@@ -0,0 +1,13 @@
+spring:
+ main:
+ allow-bean-definition-overriding: true
+management:
+ health:
+ defaults:
+ enabled: false
+soul:
+ sync:
+ websocket:
+ urls: ws://localhost:9095/websocket
+server:
+ port: 9195
diff --git a/samples/shenyu-sureness/src/main/resources/sureness.yml b/samples/shenyu-sureness/src/main/resources/sureness.yml
new file mode 100644
index 00000000..7a1f9c18
--- /dev/null
+++ b/samples/shenyu-sureness/src/main/resources/sureness.yml
@@ -0,0 +1,43 @@
+## -- sureness.yml document dataSource-- ##
+
+# load api resource which need be protected, config role who can access these resource.
+# resources that are not configured are also authenticated and protected by default, but not authorized
+# eg: /api/v1/bar===post===[role1] means /api/v1/bar===post can be access by role1
+# eg: /api/v3/foo===get===[] means /api/v3/foo===* can not be access by any role
+resourceRole:
+ - /api/v1/bar/*===get===[role1,role2,role3]
+ - /api/v1/bar===post===[role1]
+ - /api/v2/bar===put===[role2]
+ - /api/v2/foo===get===[role3]
+ - /api/v3/foo===get===[]
+
+# load api resource which do not need be protected, means them need be excluded.
+# these api resource can be access by everyone
+# eg: /**/*.png===* means get/post/put/delete/patch any url suffixed with `.png` can be access by everyone
+excludedResource:
+ - /api/v2/foo===delete
+ - /**/*.html===get
+ - /**/*.js===get
+ - /**/*.css===get
+ - /**/*.ico===*
+ - /**/*.png===*
+
+
+# account info
+# there are three account: sam, tom, lisa
+# eg: sam has [role1,role2,role3], password is sam123, has salt -> 123
+# eg: tom has role2, password is tom123
+# eg: lisa has role3, password is lisa123
+account:
+ - appId: sam
+ # if add salt, the credential is encrypted by md5 - result is: MD5(password+salt)
+ # digest auth not support encrypted credential
+ credential: 1B9E9136628CB1B161AE47132DD7AF19
+ salt: 123
+ role: [role1,role2,role3]
+ - appId: tom
+ credential: tom123
+ role: [role2]
+ - appId: lisa
+ credential: lisa123
+ role: [role3]
\ No newline at end of file
diff --git a/samples/spring-gateway-sureness/src/main/java/gateway/sureness/SurenessFilter.java b/samples/spring-gateway-sureness/src/main/java/gateway/sureness/SurenessFilter.java
index 0d0f4cfe..958c022e 100644
--- a/samples/spring-gateway-sureness/src/main/java/gateway/sureness/SurenessFilter.java
+++ b/samples/spring-gateway-sureness/src/main/java/gateway/sureness/SurenessFilter.java
@@ -72,7 +72,7 @@ public int getOrder() {
* @param message message
*/
private Mono responseWrite(ServerWebExchange exchange, HttpStatus statusCode,
- String message, Map headers) {
+ String message, Map headers) {
exchange.getResponse().setStatusCode(statusCode);
if (headers != null) {
@@ -80,11 +80,11 @@ private Mono responseWrite(ServerWebExchange exchange, HttpStatus statusCo
}
if (message != null) {
return exchange.getResponse().writeWith(Flux.create(sink -> {
- NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
- DataBuffer dataBuffer= nettyDataBufferFactory.wrap(message.getBytes(StandardCharsets.UTF_8));
- sink.next(dataBuffer);
- sink.complete();
- }));
+ NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(new UnpooledByteBufAllocator(false));
+ DataBuffer dataBuffer= nettyDataBufferFactory.wrap(message.getBytes(StandardCharsets.UTF_8));
+ sink.next(dataBuffer);
+ sink.complete();
+ }));
} else {
return exchange.getResponse().setComplete();
}
diff --git a/samples/spring-gateway-sureness/src/main/resources/application.yml b/samples/spring-gateway-sureness/src/main/resources/application.yml
index be28e23d..736b2202 100644
--- a/samples/spring-gateway-sureness/src/main/resources/application.yml
+++ b/samples/spring-gateway-sureness/src/main/resources/application.yml
@@ -7,4 +7,4 @@ spring:
- id: route1
uri: https://www.bing.com
predicates:
- - Path=/api/**
+ - Path=/api/**