From 6d0a5a7fa277c1924bcacab5f69ffe5cf335dbb1 Mon Sep 17 00:00:00 2001 From: zhou-hao Date: Sat, 10 Oct 2020 19:34:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0OAuth2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web/authorization/Authentication.java | 11 ++ .../web/authorization/Permission.java | 6 +- ...DefaultAuthorizationAutoConfiguration.java | 5 +- .../simple/SimpleAuthentication.java | 20 ++- .../simple/SimplePermission.java | 14 +- .../web/authorization/token/ParsedToken.java | 4 + .../ReactiveTokenAuthenticationSupplier.java | 32 ++++ .../token/SimpleParsedToken.java | 17 +++ .../token/TokenAuthenticationManager.java | 6 + .../RedisTokenAuthenticationManager.java | 7 + .../basic/web/AuthorizationController.java | 6 - .../web/oauth2/OAuth2Exception.java | 5 +- .../web/oauth2/server/AccessToken.java | 16 +- .../web/oauth2/server/AccessTokenManager.java | 4 +- .../web/oauth2/server/OAuth2GrantService.java | 5 +- .../web/oauth2/server/OAuth2Response.java | 3 +- .../server/OAuth2ServerAutoConfiguration.java | 77 ++++++++++ .../auth/ReactiveOAuth2AccessTokenParser.java | 61 ++++++++ .../server/code/AuthorizationCodeCache.java | 26 ++++ .../server/code/AuthorizationCodeGranter.java | 5 - .../code/AuthorizationCodeResponse.java | 2 + .../code/DefaultAuthorizationCodeGranter.java | 105 +++++++++++++ .../impl/CompositeOAuth2GrantService.java | 26 ++++ .../oauth2/server/impl/RedisAccessToken.java | 37 +++++ .../server/impl/RedisAccessTokenManager.java | 143 ++++++++++++++++++ .../server/web/OAuth2AuthorizeController.java | 90 ++++++----- .../main/resources/META-INF/spring.factories | 3 + .../web/oauth2/server/RedisHelper.java | 15 ++ .../DefaultAuthorizationCodeGranterTest.java | 69 +++++++++ .../impl/RedisAccessTokenManagerTest.java | 45 ++++++ .../web/OAuth2AuthorizeControllerTest.java | 25 +++ .../src/test/resources/logback.xml | 17 +++ .../hsweb-system-authorization-oauth2/pom.xml | 84 ++++++++++ .../OAuth2ClientManagerAutoConfiguration.java | 37 +++++ .../web/oauth2/entity/OAuth2ClientEntity.java | 84 ++++++++++ .../web/oauth2/enums/OAuth2ClientState.java | 20 +++ .../service/InDBOAuth2ClientManager.java | 21 +++ .../oauth2/service/OAuth2ClientService.java | 12 ++ .../web/WebFluxOAuth2ClientController.java | 26 ++++ .../main/resources/META-INF/spring.factories | 3 + .../web/oauth2/ReactiveTestApplication.java | 22 +++ ...th2ClientManagerAutoConfigurationTest.java | 24 +++ .../service/OAuth2ClientServiceTest.java | 46 ++++++ .../src/test/resources/application.yml | 16 ++ 44 files changed, 1230 insertions(+), 72 deletions(-) create mode 100644 hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ReactiveTokenAuthenticationSupplier.java create mode 100644 hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/SimpleParsedToken.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/auth/ReactiveOAuth2AccessTokenParser.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeCache.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/CompositeOAuth2GrantService.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessToken.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/main/resources/META-INF/spring.factories create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/RedisHelper.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManagerTest.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeControllerTest.java create mode 100644 hsweb-authorization/hsweb-authorization-oauth2/src/test/resources/logback.xml create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/pom.xml create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfiguration.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/entity/OAuth2ClientEntity.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/enums/OAuth2ClientState.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/InDBOAuth2ClientManager.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/OAuth2ClientService.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/web/WebFluxOAuth2ClientController.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/resources/META-INF/spring.factories create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/ReactiveTestApplication.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfigurationTest.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/service/OAuth2ClientServiceTest.java create mode 100644 hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/resources/application.yml diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java index ebb5b5b3d..3719c58a1 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Authentication.java @@ -22,6 +22,8 @@ import reactor.core.publisher.Mono; import java.io.Serializable; import java.util.*; +import java.util.function.BiPredicate; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -207,4 +209,13 @@ public interface Authentication extends Serializable { */ Authentication merge(Authentication source); + /** + * copy为新的权限信息 + * + * @param permissionFilter 权限过滤 + * @param dimension 维度过滤 + * @return 新的权限信息 + */ + Authentication copy(BiPredicate permissionFilter, + Predicate dimension); } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java index 803f38e3c..d8d9fb076 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/Permission.java @@ -153,7 +153,7 @@ public interface Permission extends Serializable { * @see FieldFilterDataAccessConfig#getFields() */ default Optional findFieldFilter(String action) { - return findDataAccess(conf -> FieldFilterDataAccessConfig.class.isInstance(conf) && conf.getAction().equals(action)); + return findDataAccess(conf -> conf instanceof FieldFilterDataAccessConfig && conf.getAction().equals(action)); } /** @@ -164,7 +164,7 @@ public interface Permission extends Serializable { */ default Set findDenyFields(String action) { return findFieldFilter(action) - .filter(conf -> DENY_FIELDS.equals(conf.getType())) + .filter(conf -> DENY_FIELDS.equals(conf.getType().getId())) .map(FieldFilterDataAccessConfig::getFields) .orElseGet(Collections::emptySet); } @@ -210,6 +210,8 @@ public interface Permission extends Serializable { Permission copy(); + Permission copy(Predicate actionFilter,Predicate dataAccessFilter); + /** * 数据权限查找判断逻辑接口 * diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java index befc744d9..229ef8c50 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/DefaultAuthorizationAutoConfiguration.java @@ -6,10 +6,7 @@ import org.hswebframework.web.authorization.builder.DataAccessConfigBuilderFacto import org.hswebframework.web.authorization.simple.builder.DataAccessConfigConverter; import org.hswebframework.web.authorization.simple.builder.SimpleAuthenticationBuilderFactory; import org.hswebframework.web.authorization.simple.builder.SimpleDataAccessConfigBuilderFactory; -import org.hswebframework.web.authorization.token.DefaultUserTokenManager; -import org.hswebframework.web.authorization.token.UserTokenAuthenticationSupplier; -import org.hswebframework.web.authorization.token.UserTokenReactiveAuthenticationSupplier; -import org.hswebframework.web.authorization.token.UserTokenManager; +import org.hswebframework.web.authorization.token.*; import org.hswebframework.web.authorization.twofactor.TwoFactorValidatorManager; import org.hswebframework.web.authorization.twofactor.defaults.DefaultTwoFactorValidatorManager; import org.hswebframework.web.convert.CustomMessageConverter; diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java index c9d0318a7..356ec9d4e 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java @@ -23,7 +23,9 @@ import org.hswebframework.web.authorization.*; import java.io.Serializable; import java.util.*; +import java.util.function.BiPredicate; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; @Getter @@ -40,9 +42,10 @@ public class SimpleAuthentication implements Authentication { private Map attributes = new HashMap<>(); - public static Authentication of(){ + public static Authentication of() { return new SimpleAuthentication(); } + @Override @SuppressWarnings("unchecked") public Optional getAttribute(String name) { @@ -77,4 +80,19 @@ public class SimpleAuthentication implements Authentication { } return this; } + + @Override + public Authentication copy(BiPredicate permissionFilter, + Predicate dimension) { + SimpleAuthentication authentication = new SimpleAuthentication(); + authentication.setUser(user); + authentication.setDimensions(dimensions.stream().filter(dimension).collect(Collectors.toList())); + authentication.setPermissions(permissions + .stream() + .map(permission -> permission.copy(action -> permissionFilter.test(permission, action), conf -> true)) + .filter(per -> !per.getActions().isEmpty()) + .collect(Collectors.toList()) + ); + return authentication; + } } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java index 098efb7d5..c2d46908c 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimplePermission.java @@ -5,6 +5,8 @@ import org.hswebframework.web.authorization.Permission; import org.hswebframework.web.authorization.access.DataAccessConfig; import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; /** * @author zhouhao @@ -42,16 +44,22 @@ public class SimplePermission implements Permission { return dataAccesses; } - public Permission copy() { + @Override + public Permission copy(Predicate actionFilter, + Predicate dataAccessFilter) { SimplePermission permission = new SimplePermission(); permission.setId(id); permission.setName(name); - permission.setActions(new HashSet<>(getActions())); - permission.setDataAccesses(new HashSet<>(getDataAccesses())); + permission.setActions(getActions().stream().filter(actionFilter).collect(Collectors.toSet())); + permission.setDataAccesses(getDataAccesses().stream().filter(dataAccessFilter).collect(Collectors.toSet())); if (options != null) { permission.setOptions(new HashMap<>(options)); } return permission; } + + public Permission copy() { + return copy(action -> true, conf -> true); + } } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ParsedToken.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ParsedToken.java index 8bb30a009..e058775da 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ParsedToken.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ParsedToken.java @@ -15,4 +15,8 @@ public interface ParsedToken { * @return 令牌类型 */ String getType(); + + static ParsedToken of(String type, String token) { + return SimpleParsedToken.of(type, token); + } } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ReactiveTokenAuthenticationSupplier.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ReactiveTokenAuthenticationSupplier.java new file mode 100644 index 000000000..bd7b59516 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/ReactiveTokenAuthenticationSupplier.java @@ -0,0 +1,32 @@ +package org.hswebframework.web.authorization.token; + +import lombok.AllArgsConstructor; +import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier; +import org.hswebframework.web.context.ContextKey; +import org.hswebframework.web.context.ContextUtils; +import org.hswebframework.web.logger.ReactiveLogger; +import reactor.core.publisher.Mono; + +@AllArgsConstructor +public class ReactiveTokenAuthenticationSupplier implements ReactiveAuthenticationSupplier { + + private final TokenAuthenticationManager tokenManager; + + @Override + public Mono get(String userId) { + return Mono.empty(); + } + + @Override + public Mono get() { + return ContextUtils.reactiveContext() + .flatMap(context -> + context.get(ContextKey.of(ParsedToken.class)) + .map(t -> tokenManager.getByToken(t.getToken())) + .orElseGet(Mono::empty)) + .flatMap(auth -> ReactiveLogger.mdc("userId", auth.getUser().getId()) + .then(ReactiveLogger.mdc("username", auth.getUser().getName())) + .thenReturn(auth)); + } +} diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/SimpleParsedToken.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/SimpleParsedToken.java new file mode 100644 index 000000000..cedcac0cd --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/SimpleParsedToken.java @@ -0,0 +1,17 @@ +package org.hswebframework.web.authorization.token; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor(staticName = "of") +public class SimpleParsedToken implements ParsedToken{ + + private String type; + + private String token; + + +} diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java index 9c2d976d6..c806a9353 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java @@ -31,4 +31,10 @@ public interface TokenAuthenticationManager { */ Mono putAuthentication(String token, Authentication auth, Duration ttl); + /** + * 删除token + * @param token token + * @return void + */ + Mono removeToken(String token); } diff --git a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java index a4fb8430a..31874ea98 100644 --- a/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java +++ b/hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java @@ -38,6 +38,13 @@ public class RedisTokenAuthenticationManager implements TokenAuthenticationManag .get("token-auth:" + token); } + @Override + public Mono removeToken(String token) { + return operations + .delete(token) + .then(); + } + @Override public Mono putAuthentication(String token, Authentication auth, Duration ttl) { return ttl.isNegative() diff --git a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java index 23a3bd2d4..472b074be 100644 --- a/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java +++ b/hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/web/AuthorizationController.java @@ -17,11 +17,8 @@ package org.hswebframework.web.authorization.basic.web; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.SneakyThrows; import org.hswebframework.web.authorization.Authentication; @@ -33,12 +30,10 @@ import org.hswebframework.web.authorization.events.AuthorizationFailedEvent; import org.hswebframework.web.authorization.events.AuthorizationSuccessEvent; import org.hswebframework.web.authorization.exception.AuthenticationException; import org.hswebframework.web.authorization.exception.UnAuthorizedException; -import org.hswebframework.web.authorization.simple.CompositeReactiveAuthenticationManager; import org.hswebframework.web.authorization.simple.PlainTextUsernamePasswordAuthenticationRequest; import org.hswebframework.web.logging.AccessLogger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.data.repository.query.Param; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; @@ -71,7 +66,6 @@ public class AuthorizationController { } @PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE) - @ApiOperation("用户名密码登录,json方式") @Authorize(ignore = true) @AccessLogger(ignore = true) @Operation(summary = "登录",description = "必要参数:username,password.根据配置不同,其他参数也不同,如:验证码等.") diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java index 2a5e760c2..fc48ad3e1 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java @@ -1,13 +1,14 @@ package org.hswebframework.web.oauth2; import lombok.Getter; +import org.hswebframework.web.exception.BusinessException; @Getter -public class OAuth2Exception extends RuntimeException { +public class OAuth2Exception extends BusinessException { private final ErrorType type; public OAuth2Exception(ErrorType type) { - super(type.message()); + super(type.message(), type.name(), type.code()); this.type = type; } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java index ad51da3f1..a4c1956c8 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java @@ -2,23 +2,27 @@ package org.hswebframework.web.oauth2.server; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Getter; -import lombok.Setter; +import lombok.*; @Getter @Setter +@AllArgsConstructor +@NoArgsConstructor +@ToString public class AccessToken extends OAuth2Response { + private static final long serialVersionUID = -6849794470754667710L; + @Schema(name="access_token") @JsonProperty("access_token") private String accessToken; - @Schema(name="expires_in") - @JsonProperty("expires_in") - private int expiresIn; - @Schema(name="refresh_token") @JsonProperty("refresh_token") private String refreshToken; + @Schema(name="expires_in") + @JsonProperty("expires_in") + private int expiresIn; + } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java index 4410a1c59..1645357d3 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java @@ -7,7 +7,9 @@ public interface AccessTokenManager { Mono getAuthenticationByToken(String accessToken); - Mono createAccessToken(String clientId, Authentication authentication); + Mono createAccessToken(String clientId, + Authentication authentication, + boolean singleton); Mono refreshAccessToken(String clientId, String refreshToken); diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java index 333be7a94..0b1feebf1 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java @@ -1,15 +1,12 @@ package org.hswebframework.web.oauth2.server; -import org.hswebframework.web.authorization.Authentication; import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter; -import reactor.core.publisher.Mono; public interface OAuth2GrantService { - AuthorizationCodeGranter code(); + AuthorizationCodeGranter authorizationCode(); ClientCredentialGranter clientCredential(); - Mono grant(String scope, Authentication authentication); } diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java index a23eeedc4..a6b80098c 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java @@ -5,12 +5,13 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; @Getter @Setter -public class OAuth2Response { +public class OAuth2Response implements Serializable { @Hidden private Map parameters; diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java new file mode 100644 index 000000000..51c979d50 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ServerAutoConfiguration.java @@ -0,0 +1,77 @@ +package org.hswebframework.web.oauth2.server; + +import org.hswebframework.web.authorization.ReactiveAuthenticationHolder; +import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser; +import org.hswebframework.web.oauth2.server.auth.ReactiveOAuth2AccessTokenParser; +import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter; +import org.hswebframework.web.oauth2.server.code.DefaultAuthorizationCodeGranter; +import org.hswebframework.web.oauth2.server.impl.CompositeOAuth2GrantService; +import org.hswebframework.web.oauth2.server.impl.RedisAccessTokenManager; +import org.hswebframework.web.oauth2.server.web.OAuth2AuthorizeController; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; + +@Configuration(proxyBeanMethods = false) +public class OAuth2ServerAutoConfiguration { + + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ReactiveUserTokenParser.class) + static class ReactiveOAuth2AccessTokenParserConfiguration { + + @Bean + @ConditionalOnBean(AccessTokenManager.class) + public ReactiveOAuth2AccessTokenParser reactiveOAuth2AccessTokenParser(AccessTokenManager accessTokenManager) { + ReactiveOAuth2AccessTokenParser parser = new ReactiveOAuth2AccessTokenParser(accessTokenManager); + ReactiveAuthenticationHolder.addSupplier(parser); + return parser; + } + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + static class ReactiveOAuth2ServerAutoConfiguration { + + + @Bean + @ConditionalOnMissingBean + public AccessTokenManager accessTokenManager(ReactiveRedisConnectionFactory redisConnectionFactory) { + return new RedisAccessTokenManager(redisConnectionFactory); + } + + + @Bean + @ConditionalOnMissingBean + public AuthorizationCodeGranter authorizationCodeGranter(AccessTokenManager tokenManager, + ReactiveRedisConnectionFactory redisConnectionFactory) { + return new DefaultAuthorizationCodeGranter(tokenManager, redisConnectionFactory); + } + + @Bean + @ConditionalOnMissingBean + public OAuth2GrantService oAuth2GrantService(ObjectProvider codeProvider, + ObjectProvider credentialProvider) { + CompositeOAuth2GrantService grantService = new CompositeOAuth2GrantService(); + grantService.setAuthorizationCodeGranter(codeProvider.getIfAvailable()); + grantService.setClientCredentialGranter(credentialProvider.getIfAvailable()); + + return grantService; + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean(OAuth2ClientManager.class) + public OAuth2AuthorizeController oAuth2AuthorizeController(OAuth2GrantService grantService, + OAuth2ClientManager clientManager) { + return new OAuth2AuthorizeController(grantService, clientManager); + } + + } + +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/auth/ReactiveOAuth2AccessTokenParser.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/auth/ReactiveOAuth2AccessTokenParser.java new file mode 100644 index 000000000..7f1ab25a8 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/auth/ReactiveOAuth2AccessTokenParser.java @@ -0,0 +1,61 @@ +package org.hswebframework.web.oauth2.server.auth; + +import lombok.AllArgsConstructor; +import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.authorization.ReactiveAuthenticationSupplier; +import org.hswebframework.web.authorization.basic.web.ReactiveUserTokenParser; +import org.hswebframework.web.authorization.token.ParsedToken; +import org.hswebframework.web.context.ContextKey; +import org.hswebframework.web.context.ContextUtils; +import org.hswebframework.web.logger.ReactiveLogger; +import org.hswebframework.web.oauth2.server.AccessTokenManager; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@AllArgsConstructor +public class ReactiveOAuth2AccessTokenParser implements ReactiveUserTokenParser, ReactiveAuthenticationSupplier { + + private final AccessTokenManager accessTokenManager; + + @Override + public Mono parseToken(ServerWebExchange exchange) { + + String token = exchange.getRequest().getQueryParams().getFirst("access_token"); + if (StringUtils.isEmpty(token)) { + token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); + if (StringUtils.hasText(token)) { + String[] typeAndToken = token.split("[ ]"); + if (typeAndToken.length == 2 && typeAndToken[0].equalsIgnoreCase("bearer")) { + token = typeAndToken[1]; + } + } + } + + if (StringUtils.hasText(token)) { + return Mono.just(ParsedToken.of("oauth2", token)); + } + + return Mono.empty(); + } + + @Override + public Mono get(String userId) { + return Mono.empty(); + } + + @Override + public Mono get() { + return ContextUtils.reactiveContext() + .flatMap(context -> + context.get(ContextKey.of(ParsedToken.class)) + .filter(token -> "oauth2".equals(token.getType())) + .map(t -> accessTokenManager + .getAuthenticationByToken(t.getToken())) + .orElse(Mono.empty())) + .flatMap(auth -> ReactiveLogger.mdc("userId", auth.getUser().getId()) + .then(ReactiveLogger.mdc("username", auth.getUser().getName())) + .thenReturn(auth)); + } +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeCache.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeCache.java new file mode 100644 index 000000000..0c7a3c4bb --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeCache.java @@ -0,0 +1,26 @@ +package org.hswebframework.web.oauth2.server.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hswebframework.web.authorization.Authentication; + +import java.io.Serializable; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class AuthorizationCodeCache implements Serializable { + private static final long serialVersionUID = -6849794470754667710L; + + private String clientId; + + private String code; + + private Authentication authentication; + + private String scope; + +} \ No newline at end of file diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java index fcd055e92..ba0edbfec 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java @@ -12,11 +12,6 @@ import reactor.core.publisher.Mono; */ public interface AuthorizationCodeGranter extends OAuth2Granter { - /** - * @return 申请授权码界面 - */ - String getLoginUrl(); - /** * 申请授权码 * diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java index a6d148e59..da3cb769d 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java @@ -3,6 +3,7 @@ package org.hswebframework.web.oauth2.server.code; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import org.hswebframework.web.oauth2.server.OAuth2Client; import org.hswebframework.web.oauth2.server.OAuth2Request; import org.hswebframework.web.oauth2.server.OAuth2Response; @@ -11,6 +12,7 @@ import java.util.HashMap; @Getter @Setter +@ToString public class AuthorizationCodeResponse extends OAuth2Response { private String code; diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java new file mode 100644 index 000000000..a8066145b --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranter.java @@ -0,0 +1,105 @@ +package org.hswebframework.web.oauth2.server.code; + +import lombok.AllArgsConstructor; +import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.authorization.Permission; +import org.hswebframework.web.id.IDGenerator; +import org.hswebframework.web.oauth2.ErrorType; +import org.hswebframework.web.oauth2.OAuth2Exception; +import org.hswebframework.web.oauth2.server.AccessToken; +import org.hswebframework.web.oauth2.server.AccessTokenManager; +import org.hswebframework.web.oauth2.server.OAuth2Client; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.core.ReactiveRedisOperations; +import org.springframework.data.redis.core.ReactiveRedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.util.StringUtils; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.*; +import java.util.function.BiPredicate; + +@AllArgsConstructor +public class DefaultAuthorizationCodeGranter implements AuthorizationCodeGranter { + + private final AccessTokenManager accessTokenManager; + + private final ReactiveRedisOperations redis; + + @SuppressWarnings("all") + public DefaultAuthorizationCodeGranter(AccessTokenManager accessTokenManager, ReactiveRedisConnectionFactory connectionFactory) { + this(accessTokenManager, new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext + .newSerializationContext() + .key((RedisSerializer) RedisSerializer.string()) + .value(RedisSerializer.java()) + .hashKey(RedisSerializer.string()) + .hashValue(RedisSerializer.java()) + .build() + )); + } + + @Override + public Mono requestCode(AuthorizationCodeRequest request) { + OAuth2Client client = request.getClient(); + Authentication authentication = request.getAuthentication(); + AuthorizationCodeCache codeCache = new AuthorizationCodeCache(); + String code = IDGenerator.MD5.generate(); + request.getParameter("scope").map(String::valueOf).ifPresent(codeCache::setScope); + codeCache.setCode(code); + codeCache.setClientId(client.getClientId()); + codeCache.setAuthentication(authentication.copy(createPredicate(codeCache.getScope()), dimension -> true)); + + createPredicate(codeCache.getScope()); + + return redis + .opsForValue() + .set(getRedisKey(code), codeCache, Duration.ofMinutes(5)) + .thenReturn(new AuthorizationCodeResponse(code)); + } + + static BiPredicate createPredicate(String scopeStr) { + if (StringUtils.isEmpty(scopeStr)) { + return ((permission, s) -> false); + } + String[] scopes = scopeStr.split("[,]"); + Map> actions = new HashMap<>(); + for (String scope : scopes) { + String[] permissions = scope.split("[:]"); + String per = permissions[0]; + Set acts = actions.computeIfAbsent(per, k -> new HashSet<>()); + acts.addAll(Arrays.asList(permissions).subList(1, permissions.length)); + } + + return ((permission, action) -> Optional + .ofNullable(actions.get(permission.getId())) + .map(acts -> acts.contains(action)) + .orElse(false)); + } + + private String getRedisKey(String code) { + return "oauth2-code:" + code; + } + + @Override + public Mono requestToken(AuthorizationCodeTokenRequest request) { + + return Mono + .justOrEmpty(request.code()) + .map(this::getRedisKey) + .flatMap(redis.opsForValue()::get) + .switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CODE))) + .flatMap(cache -> redis + .opsForValue() + .delete(getRedisKey(cache.getCode())) + .thenReturn(cache)) + .flatMap(cache -> { + if (!request.getClient().getClientId().equals(cache.getClientId())) { + return Mono.error(new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID)); + } + return accessTokenManager.createAccessToken(cache.getClientId(), cache.getAuthentication(), false); + }); + + } +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/CompositeOAuth2GrantService.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/CompositeOAuth2GrantService.java new file mode 100644 index 000000000..c4d3ec5ad --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/CompositeOAuth2GrantService.java @@ -0,0 +1,26 @@ +package org.hswebframework.web.oauth2.server.impl; + +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.web.oauth2.server.ClientCredentialGranter; +import org.hswebframework.web.oauth2.server.OAuth2GrantService; +import org.hswebframework.web.oauth2.server.code.AuthorizationCodeGranter; + +@Getter +@Setter +public class CompositeOAuth2GrantService implements OAuth2GrantService { + + private AuthorizationCodeGranter authorizationCodeGranter; + + private ClientCredentialGranter clientCredentialGranter; + + @Override + public AuthorizationCodeGranter authorizationCode() { + return authorizationCodeGranter; + } + + @Override + public ClientCredentialGranter clientCredential() { + return clientCredentialGranter; + } +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessToken.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessToken.java new file mode 100644 index 000000000..262116926 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessToken.java @@ -0,0 +1,37 @@ +package org.hswebframework.web.oauth2.server.impl; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.oauth2.server.AccessToken; + +import java.io.Serializable; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class RedisAccessToken implements Serializable { + + private String clientId; + + private String accessToken; + + private String refreshToken; + + private long createTime; + + private Authentication authentication; + + private boolean singleton; + + public AccessToken toAccessToken(int expiresIn){ + AccessToken token=new AccessToken(); + token.setAccessToken(accessToken); + token.setRefreshToken(refreshToken); + token.setExpiresIn(expiresIn); + return token; + } +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java new file mode 100644 index 000000000..e6a0a0ac6 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManager.java @@ -0,0 +1,143 @@ +package org.hswebframework.web.oauth2.server.impl; + +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.codec.digest.DigestUtils; +import org.hswebframework.web.authorization.Authentication; +import org.hswebframework.web.oauth2.ErrorType; +import org.hswebframework.web.oauth2.OAuth2Exception; +import org.hswebframework.web.oauth2.server.AccessToken; +import org.hswebframework.web.oauth2.server.AccessTokenManager; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.core.ReactiveRedisOperations; +import org.springframework.data.redis.core.ReactiveRedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.RedisSerializer; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.UUID; + +public class RedisAccessTokenManager implements AccessTokenManager { + + private final ReactiveRedisOperations tokenRedis; + + @Getter + @Setter + private int tokenExpireIn = 7200;//2小时 + + @Getter + @Setter + private int refreshExpireIn = 2592000; //30天 + + public RedisAccessTokenManager(ReactiveRedisOperations tokenRedis) { + this.tokenRedis = tokenRedis; + } + + @SuppressWarnings("all") + public RedisAccessTokenManager(ReactiveRedisConnectionFactory connectionFactory) { + this(new ReactiveRedisTemplate<>(connectionFactory, RedisSerializationContext + .newSerializationContext() + .key((RedisSerializer) RedisSerializer.string()) + .value(RedisSerializer.java()) + .hashKey(RedisSerializer.string()) + .hashValue(RedisSerializer.java()) + .build() + )); + } + + @Override + public Mono getAuthenticationByToken(String accessToken) { + + return tokenRedis + .opsForValue() + .get(createTokenRedisKey(accessToken)) + .map(RedisAccessToken::getAuthentication); + } + + private String createTokenRedisKey(String token) { + return "oauth2-token:" + token; + } + + private String createRefreshTokenRedisKey(String token) { + return "oauth2-refresh-token:" + token; + } + + private String createSingletonTokenRedisKey(String clientId) { + return "oauth2-" + clientId + "-token"; + } + + private Mono doCreateAccessToken(String clientId, Authentication authentication, boolean singleton) { + String token = DigestUtils.md5Hex(UUID.randomUUID().toString()); + String refresh = DigestUtils.md5Hex(UUID.randomUUID().toString()); + RedisAccessToken accessToken = new RedisAccessToken(clientId, token, refresh, System.currentTimeMillis(), authentication, singleton); + + return storeToken(accessToken).thenReturn(accessToken); + } + + private Mono storeToken(RedisAccessToken token) { + return Mono + .zip( + tokenRedis.opsForValue().set(createTokenRedisKey(token.getAccessToken()), token, Duration.ofSeconds(tokenExpireIn)), + tokenRedis.opsForValue().set(createRefreshTokenRedisKey(token.getRefreshToken()), token, Duration.ofSeconds(refreshExpireIn)) + ).then(); + } + + private Mono doCreateSingletonAccessToken(String clientId, Authentication authentication) { + String redisKey = createSingletonTokenRedisKey(clientId); + + return tokenRedis + .opsForValue() + .get(redisKey) + .flatMap(token -> tokenRedis + .getExpire(redisKey) + .map(duration -> token.toAccessToken((int) (duration.toMillis() / 1000)))) + .switchIfEmpty(Mono.defer(() -> doCreateAccessToken(clientId, authentication, true) + .flatMap(redisAccessToken -> tokenRedis + .opsForValue() + .set(redisKey, redisAccessToken, Duration.ofSeconds(tokenExpireIn)) + .thenReturn(redisAccessToken.toAccessToken(tokenExpireIn)))) + ); + } + + @Override + public Mono createAccessToken(String clientId, + Authentication authentication, + boolean singleton) { + return singleton + ? doCreateSingletonAccessToken(clientId, authentication) + : doCreateAccessToken(clientId, authentication, false).map(token -> token.toAccessToken(tokenExpireIn)); + } + + @Override + public Mono refreshAccessToken(String clientId, String refreshToken) { + String redisKey = createRefreshTokenRedisKey(refreshToken); + + return tokenRedis + .opsForValue() + .get(redisKey) + .switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.EXPIRED_REFRESH_TOKEN))) + .flatMap(token -> { + if (!token.getClientId().equals(clientId)) { + return Mono.error(new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID)); + } + //生成新token + String accessToken = DigestUtils.md5Hex(UUID.randomUUID().toString()); + token.setAccessToken(accessToken); + token.setCreateTime(System.currentTimeMillis()); + return storeToken(token) + .as(result -> { + // 单例token + if (token.isSingleton()) { + return tokenRedis + .opsForValue() + .set(createSingletonTokenRedisKey(clientId), token, Duration.ofSeconds(tokenExpireIn)) + .then(result); + } + return result; + }) + .thenReturn(token.toAccessToken(tokenExpireIn)); + }); + + } +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java index a2a1524e4..dc30e1dd7 100644 --- a/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java @@ -2,86 +2,102 @@ package org.hswebframework.web.oauth2.server.web; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import org.hswebframework.web.authorization.Authentication; -import org.hswebframework.web.authorization.AuthenticationManager; import org.hswebframework.web.authorization.annotation.Authorize; import org.hswebframework.web.authorization.exception.UnAuthorizedException; -import org.hswebframework.web.authorization.token.TokenAuthenticationManager; -import org.hswebframework.web.oauth2.server.*; +import org.hswebframework.web.oauth2.ErrorType; +import org.hswebframework.web.oauth2.OAuth2Exception; +import org.hswebframework.web.oauth2.server.AccessToken; +import org.hswebframework.web.oauth2.server.OAuth2Client; +import org.hswebframework.web.oauth2.server.OAuth2ClientManager; +import org.hswebframework.web.oauth2.server.OAuth2GrantService; import org.hswebframework.web.oauth2.server.code.AuthorizationCodeRequest; import org.hswebframework.web.oauth2.server.code.AuthorizationCodeTokenRequest; import org.springframework.http.ResponseEntity; -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 org.springframework.web.bind.annotation.*; +import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.URLEncoder; +import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; @RestController @RequestMapping("/oauth2") @AllArgsConstructor +@Tag(name = "OAuth2认证") public class OAuth2AuthorizeController { private final OAuth2GrantService oAuth2GrantService; private final OAuth2ClientManager clientManager; - @PostMapping(value = "/authorize", params = "response_type=code") + + @GetMapping(value = "/authorize", params = "response_type=code") @Operation(summary = "申请授权码,并获取重定向地址", parameters = { - @Parameter(description = "client_id"), - @Parameter(description = "redirect_uri"), - @Parameter(description = "state") + @Parameter(name = "client_id", required = true), + @Parameter(name = "redirect_uri", required = true), + @Parameter(name = "state"), + @Parameter(name = "response_type", description = "固定值为code") }) - public Mono authorizeByCode(@RequestBody Mono> params) { + public Mono authorizeByCode(ServerWebExchange exchange) { + Map param = new HashMap<>(exchange.getRequest().getQueryParams().toSingleValueMap()); return Authentication .currentReactive() .switchIfEmpty(Mono.error(UnAuthorizedException::new)) - .flatMap(auth -> params - .flatMap(param -> this - .getOAuth2Client((String) param.get("client_id")) - .flatMap(client -> { - String redirectUri = (String) param.getOrDefault("redirect_uri", client.getRedirectUrl()); - client.validateRedirectUri(redirectUri); - return oAuth2GrantService - .code() - .requestCode(new AuthorizationCodeRequest(client, auth, param)) - .map(authorizationCodeResponse -> buildRedirect(redirectUri, authorizationCodeResponse.getParameters())); - }))); + .flatMap(auth -> this + .getOAuth2Client((String) param.get("client_id")) + .switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID))) + .flatMap(client -> { + String redirectUri = (String) param.getOrDefault("redirect_uri", client.getRedirectUrl()); + client.validateRedirectUri(redirectUri); + return oAuth2GrantService + .authorizationCode() + .requestCode(new AuthorizationCodeRequest(client, auth, param)) + .doOnNext(response -> { + Optional.ofNullable(param.get("state")).ifPresent(state -> response.with("state", state)); + }) + .map(response -> buildRedirect(redirectUri, response.getParameters())); + })); } - @PostMapping(value = "/token", params = "grant_type=authorization_code") - @Operation(summary = "使用授权码申请token",parameters = { - @Parameter(description = "client_id"), - @Parameter(description = "client_secret"), - @Parameter(description = "code") + @GetMapping(value = "/token", params = "grant_type=authorization_code") + @Operation(summary = "使用授权码申请token", parameters = { + @Parameter(name = "client_id"), + @Parameter(name = "client_secret"), + @Parameter(name = "code"), + @Parameter(name = "grant_type", description = "固定值为authorization_code") }) @Authorize(ignore = true) - public Mono> requestTokenByCode(@RequestBody Mono> params) { + public Mono> requestTokenByCode(ServerWebExchange exchange) { + Map params = exchange.getRequest().getQueryParams().toSingleValueMap(); - return params - .flatMap(param -> this - .getOAuth2Client((String) param.get("client_id")) - .flatMap(client -> oAuth2GrantService - .code() - .requestToken(new AuthorizationCodeTokenRequest(client, param)))) + return doRequestCode(new HashMap<>(params)) .map(ResponseEntity::ok); } + private Mono doRequestCode(Map param) { + return this + .getOAuth2Client((String) param.get("client_id")) + .switchIfEmpty(Mono.error(() -> new OAuth2Exception(ErrorType.ILLEGAL_CLIENT_ID))) + .flatMap(client -> oAuth2GrantService + .authorizationCode() + .requestToken(new AuthorizationCodeTokenRequest(client, param))); + } + @SneakyThrows public static String urlEncode(String url) { return URLEncoder.encode(url, "utf-8"); } - public String buildRedirect(String redirectUri, Map params) { + static String buildRedirect(String redirectUri, Map params) { String paramsString = params.entrySet() .stream() .map(e -> e.getKey() + "=" + urlEncode(String.valueOf(e.getValue()))) diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/main/resources/META-INF/spring.factories b/hsweb-authorization/hsweb-authorization-oauth2/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..8e76d8af6 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.hswebframework.web.oauth2.server.OAuth2ServerAutoConfiguration \ No newline at end of file diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/RedisHelper.java b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/RedisHelper.java new file mode 100644 index 000000000..c5237bc20 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/RedisHelper.java @@ -0,0 +1,15 @@ +package org.hswebframework.web.oauth2.server; + +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +public class RedisHelper { + + public static LettuceConnectionFactory factory; + + static { + factory = new LettuceConnectionFactory(new RedisStandaloneConfiguration("127.0.0.1")); + factory.afterPropertiesSet(); + } +} diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java new file mode 100644 index 000000000..1f4487b2f --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/code/DefaultAuthorizationCodeGranterTest.java @@ -0,0 +1,69 @@ +package org.hswebframework.web.oauth2.server.code; + +import org.hswebframework.web.authorization.Permission; +import org.hswebframework.web.authorization.simple.SimpleAuthentication; +import org.hswebframework.web.authorization.simple.SimplePermission; +import org.hswebframework.web.oauth2.server.OAuth2Client; +import org.hswebframework.web.oauth2.server.RedisHelper; +import org.hswebframework.web.oauth2.server.impl.RedisAccessTokenManager; +import org.junit.Test; +import reactor.test.StepVerifier; + +import java.util.Collections; +import java.util.function.BiPredicate; + +import static org.junit.Assert.*; + +public class DefaultAuthorizationCodeGranterTest { + + + @Test + public void testPermission() { + BiPredicate predicate = DefaultAuthorizationCodeGranter.createPredicate("user:info,device:query"); + + { + SimplePermission permission=new SimplePermission(); + permission.setId("user"); + permission.setActions(Collections.singleton("info")); + + + assertTrue(predicate.test(permission,"info")); + assertFalse(predicate.test(permission,"info2")); + } + + { + SimplePermission permission=new SimplePermission(); + permission.setId("device"); + permission.setActions(Collections.singleton("query")); + + + assertTrue(predicate.test(permission,"query")); + assertFalse(predicate.test(permission,"query2")); + } + + } + + @Test + public void testRequestToken() { + + DefaultAuthorizationCodeGranter codeGranter = new DefaultAuthorizationCodeGranter( + new RedisAccessTokenManager(RedisHelper.factory), RedisHelper.factory + ); + + OAuth2Client client = new OAuth2Client(); + client.setClientId("test"); + client.setClientSecret("test"); + + codeGranter + .requestCode(new AuthorizationCodeRequest(client, new SimpleAuthentication(), Collections.emptyMap())) + .doOnNext(System.out::println) + .flatMap(response -> codeGranter + .requestToken(new AuthorizationCodeTokenRequest(client, Collections.singletonMap("code", response.getCode())))) + .doOnNext(System.out::println) + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); + + } + +} \ No newline at end of file diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManagerTest.java b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManagerTest.java new file mode 100644 index 000000000..6be2ee561 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/impl/RedisAccessTokenManagerTest.java @@ -0,0 +1,45 @@ +package org.hswebframework.web.oauth2.server.impl; + +import org.hswebframework.web.authorization.simple.SimpleAuthentication; +import org.hswebframework.web.oauth2.server.RedisHelper; +import org.junit.Test; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.Assert.*; + +public class RedisAccessTokenManagerTest { + + @Test + public void testCreateAccessToken() { + RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory); + + SimpleAuthentication authentication = new SimpleAuthentication(); + + tokenManager.createAccessToken("test", authentication, false) + .doOnNext(System.out::println) + .as(StepVerifier::create) + .expectNextCount(1) + .verifyComplete(); + + } + + @Test + public void testCreateSingletonAccessToken() { + RedisAccessTokenManager tokenManager = new RedisAccessTokenManager(RedisHelper.factory); + + SimpleAuthentication authentication = new SimpleAuthentication(); + + Flux + .concat(tokenManager + .createAccessToken("test", authentication, true), + tokenManager + .createAccessToken("test", authentication, true)) + .doOnNext(System.out::println) + .as(StepVerifier::create) + .expectNextCount(2) + .verifyComplete(); + + } +} \ No newline at end of file diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeControllerTest.java b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeControllerTest.java new file mode 100644 index 000000000..5778117a3 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeControllerTest.java @@ -0,0 +1,25 @@ +package org.hswebframework.web.oauth2.server.web; + +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.*; + +public class OAuth2AuthorizeControllerTest { + + @Test + public void testBuildRedirect() { + String url = OAuth2AuthorizeController.buildRedirect("http://hsweb.me/callback", Collections.singletonMap("code", "1234")); + + assertEquals(url,"http://hsweb.me/callback?code=1234"); + } + + @Test + public void testBuildRedirectParam() { + String url = OAuth2AuthorizeController.buildRedirect("http://hsweb.me/callback?a=b", Collections.singletonMap("code", "1234")); + + assertEquals(url,"http://hsweb.me/callback?a=b&code=1234"); + } + +} \ No newline at end of file diff --git a/hsweb-authorization/hsweb-authorization-oauth2/src/test/resources/logback.xml b/hsweb-authorization/hsweb-authorization-oauth2/src/test/resources/logback.xml new file mode 100644 index 000000000..fbdf2f230 --- /dev/null +++ b/hsweb-authorization/hsweb-authorization-oauth2/src/test/resources/logback.xml @@ -0,0 +1,17 @@ + + + +   + +   +   + + + %-4relative [%thread] %-5level %logger{35} - %msg %n + + + + + + + \ No newline at end of file diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/pom.xml b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/pom.xml new file mode 100644 index 000000000..10c2d37fa --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/pom.xml @@ -0,0 +1,84 @@ + + + + hsweb-system-authorization + org.hswebframework.web + 4.0.8-SNAPSHOT + ../pom.xml + + 4.0.0 + + hsweb-system-authorization-oauth2 + + + + org.hswebframework.web + hsweb-commons-crud + ${project.version} + + + + org.hswebframework.web + hsweb-authorization-oauth2 + ${project.version} + + + + org.springframework + spring-aspects + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-jdbc + test + + + + com.h2database + h2 + test + + + + org.springframework.boot + spring-boot-starter-data-r2dbc + test + + + + io.r2dbc + r2dbc-h2 + test + + + + org.hswebframework.web + hsweb-authorization-api + ${project.version} + compile + + + + org.springframework.boot + spring-boot-starter-data-redis-reactive + test + + + + org.springframework.boot + spring-boot-starter-webflux + test + + + + \ No newline at end of file diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfiguration.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfiguration.java new file mode 100644 index 000000000..c9121c58f --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfiguration.java @@ -0,0 +1,37 @@ +package org.hswebframework.web.oauth2.configuration; + +import org.hswebframework.web.oauth2.server.OAuth2ClientManager; +import org.hswebframework.web.oauth2.service.InDBOAuth2ClientManager; +import org.hswebframework.web.oauth2.service.OAuth2ClientService; +import org.hswebframework.web.oauth2.web.WebFluxOAuth2ClientController; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +public class OAuth2ClientManagerAutoConfiguration { + + @Configuration(proxyBeanMethods = false) + @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) + static class ReactiveOAuth2ClientManagerAutoConfiguration { + + @Bean + public OAuth2ClientService oAuth2ClientService() { + return new OAuth2ClientService(); + } + + @Bean + @ConditionalOnMissingBean + public OAuth2ClientManager oAuth2ClientManager(OAuth2ClientService clientService) { + return new InDBOAuth2ClientManager(clientService); + } + + @Bean + @ConditionalOnMissingBean + public WebFluxOAuth2ClientController webFluxOAuth2ClientController(OAuth2ClientService clientService){ + return new WebFluxOAuth2ClientController(clientService); + } + } + +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/entity/OAuth2ClientEntity.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/entity/OAuth2ClientEntity.java new file mode 100644 index 000000000..88274fc54 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/entity/OAuth2ClientEntity.java @@ -0,0 +1,84 @@ +package org.hswebframework.web.oauth2.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import org.hswebframework.ezorm.rdb.mapping.annotation.ColumnType; +import org.hswebframework.ezorm.rdb.mapping.annotation.DefaultValue; +import org.hswebframework.ezorm.rdb.mapping.annotation.EnumCodec; +import org.hswebframework.web.api.crud.entity.GenericEntity; +import org.hswebframework.web.bean.ToString; +import org.hswebframework.web.crud.generator.Generators; +import org.hswebframework.web.oauth2.enums.OAuth2ClientState; +import org.hswebframework.web.oauth2.server.OAuth2Client; + +import javax.persistence.Column; +import javax.persistence.Table; +import javax.validation.constraints.NotBlank; + +@Table(name = "s_oauth2_client") +@Getter +@Setter +public class OAuth2ClientEntity extends GenericEntity { + + @Column(length = 1024) + @Schema(description = "Logo地址") + private String logoUrl; + + @Column(length = 64, nullable = false) + @Schema(description = "客户端名称") + @NotBlank + private String name; + + @Column(length = 128, nullable = false) + @Schema(description = "密钥") + @NotBlank + @ToString.Ignore + private String secret; + + @Column(length = 64, nullable = false) + @Schema(description = "绑定用户ID") + @NotBlank + private String userId; + + @Column(length = 1024, nullable = false) + @Schema(description = "回调地址") + @NotBlank + private String callbackUri; + + @Column(length = 1024, nullable = false) + @Schema(description = "首页地址") + @NotBlank + private String homeUri; + + @Column + @Schema(description = "说明") + private String description; + + @Column(length = 32) + @EnumCodec + @ColumnType(javaType = String.class) + @DefaultValue("enabled") + @Schema(description = "状态") + private OAuth2ClientState state; + + @Column(nullable = false) + @Schema(description = "创建时间") + @DefaultValue(generator = Generators.CURRENT_TIME) + private Long createTime; + + public boolean enabled() { + return state == OAuth2ClientState.enabled; + } + + public OAuth2Client toOAuth2Client() { + OAuth2Client client = new OAuth2Client(); + client.setClientSecret(secret); + client.setClientId(getId()); + client.setName(getName()); + client.setRedirectUrl(callbackUri); + client.setDescription(description); + client.setUserId(userId); + return client; + } +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/enums/OAuth2ClientState.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/enums/OAuth2ClientState.java new file mode 100644 index 000000000..1de35d578 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/enums/OAuth2ClientState.java @@ -0,0 +1,20 @@ +package org.hswebframework.web.oauth2.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.hswebframework.web.dict.EnumDict; + +@Getter +@AllArgsConstructor +public enum OAuth2ClientState implements EnumDict { + + enabled("启用"), + disabled("禁用"); + private final String text; + + @Override + public String getValue() { + return name(); + } + +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/InDBOAuth2ClientManager.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/InDBOAuth2ClientManager.java new file mode 100644 index 000000000..a6d9c33a4 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/InDBOAuth2ClientManager.java @@ -0,0 +1,21 @@ +package org.hswebframework.web.oauth2.service; + +import lombok.AllArgsConstructor; +import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity; +import org.hswebframework.web.oauth2.server.OAuth2Client; +import org.hswebframework.web.oauth2.server.OAuth2ClientManager; +import reactor.core.publisher.Mono; + +@AllArgsConstructor +public class InDBOAuth2ClientManager implements OAuth2ClientManager { + + private final OAuth2ClientService clientService; + + @Override + public Mono getClient(String clientId) { + return clientService + .findById(clientId) + .filter(OAuth2ClientEntity::enabled) + .map(OAuth2ClientEntity::toOAuth2Client); + } +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/OAuth2ClientService.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/OAuth2ClientService.java new file mode 100644 index 000000000..77b5c2cb5 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/service/OAuth2ClientService.java @@ -0,0 +1,12 @@ +package org.hswebframework.web.oauth2.service; + +import org.hswebframework.web.crud.service.GenericReactiveCacheSupportCrudService; +import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity; + +public class OAuth2ClientService extends GenericReactiveCacheSupportCrudService { + + @Override + public String getCacheName() { + return "oauth2-client"; + } +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/web/WebFluxOAuth2ClientController.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/web/WebFluxOAuth2ClientController.java new file mode 100644 index 000000000..ac24b9b00 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/web/WebFluxOAuth2ClientController.java @@ -0,0 +1,26 @@ +package org.hswebframework.web.oauth2.web; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.hswebframework.web.authorization.annotation.Resource; +import org.hswebframework.web.crud.service.ReactiveCrudService; +import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController; +import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity; +import org.hswebframework.web.oauth2.service.OAuth2ClientService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/oauth2/client") +@AllArgsConstructor +@Resource(id = "oauth2-client", name = "OAuth2客户端管理") +@Tag(name = "OAuth2客户端管理") +public class WebFluxOAuth2ClientController implements ReactiveServiceCrudController { + + private final OAuth2ClientService oAuth2ClientService; + + @Override + public ReactiveCrudService getService() { + return oAuth2ClientService; + } +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/resources/META-INF/spring.factories b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..4f804d6fb --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/main/resources/META-INF/spring.factories @@ -0,0 +1,3 @@ +# Auto Configure +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.hswebframework.web.oauth2.configuration.OAuth2ClientManagerAutoConfiguration \ No newline at end of file diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/ReactiveTestApplication.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/ReactiveTestApplication.java new file mode 100644 index 000000000..6a8238962 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/ReactiveTestApplication.java @@ -0,0 +1,22 @@ +package org.hswebframework.web.oauth2; + +import org.hswebframework.web.authorization.simple.DefaultAuthorizationAutoConfiguration; +import org.hswebframework.web.crud.configuration.JdbcSqlExecutorConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +@SpringBootApplication(exclude = { + //TransactionAutoConfiguration.class, + JdbcSqlExecutorConfiguration.class, + DataSourceAutoConfiguration.class +}) +@ImportAutoConfiguration({ + R2dbcTransactionManagerAutoConfiguration.class, + DefaultAuthorizationAutoConfiguration.class +}) +public class ReactiveTestApplication { + + +} diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfigurationTest.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfigurationTest.java new file mode 100644 index 000000000..61284aa8d --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/configuration/OAuth2ClientManagerAutoConfigurationTest.java @@ -0,0 +1,24 @@ +package org.hswebframework.web.oauth2.configuration; + +import org.hswebframework.web.oauth2.ReactiveTestApplication; +import org.hswebframework.web.oauth2.server.OAuth2ClientManager; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ReactiveTestApplication.class) +public class OAuth2ClientManagerAutoConfigurationTest { + + @Autowired + OAuth2ClientManager clientManager; + + @Test + public void test(){ + assertNotNull(clientManager); + } +} \ No newline at end of file diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/service/OAuth2ClientServiceTest.java b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/service/OAuth2ClientServiceTest.java new file mode 100644 index 000000000..f5ac463e7 --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/java/org/hswebframework/web/oauth2/service/OAuth2ClientServiceTest.java @@ -0,0 +1,46 @@ +package org.hswebframework.web.oauth2.service; + +import org.hswebframework.web.oauth2.ReactiveTestApplication; +import org.hswebframework.web.oauth2.entity.OAuth2ClientEntity; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import static org.junit.Assert.*; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = ReactiveTestApplication.class) +public class OAuth2ClientServiceTest { + + @Autowired + OAuth2ClientService clientService; + + @Test + public void test() { + + OAuth2ClientEntity clientEntity = new OAuth2ClientEntity(); + clientEntity.setId("test"); + clientEntity.setHomeUri("http://hsweb.me"); + clientEntity.setCallbackUri("http://hsweb.me/callback"); + clientEntity.setSecret("test"); + clientEntity.setName("test"); + clientEntity.setUserId("admin"); + clientService.insert(Mono.just(clientEntity)) + .as(StepVerifier::create) + .expectNext(1) + .verifyComplete(); + + clientService.findById("test") + .doOnNext(System.out::println) + .as(StepVerifier::create) + .expectNextMatches(client -> { + return client.getCreateTime() != null && client.getState() != null; + }).verifyComplete(); + + } + +} \ No newline at end of file diff --git a/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/resources/application.yml b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/resources/application.yml new file mode 100644 index 000000000..dcb4516fd --- /dev/null +++ b/hsweb-system/hsweb-system-authorization/hsweb-system-authorization-oauth2/src/test/resources/application.yml @@ -0,0 +1,16 @@ +logging: + level: + org.hswebframework: debug + org.springframework.transaction: debug + org.springframework.data.r2dbc.connectionfactory: debug +#spring: +# r2dbc: +spring: + aop: + proxy-target-class: true +hsweb: + authorize: + auto-parse: true +easyorm: + default-schema: PUBLIC + dialect: h2 \ No newline at end of file