Browse Source

oauth2 support

zhou-hao 4 years ago
parent
commit
4aa4d1acc6
27 changed files with 748 additions and 5 deletions
  1. 1 1
      hsweb-authorization/hsweb-authorization-api/pom.xml
  2. 34 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java
  3. 5 4
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenReactiveAuthenticationSupplier.java
  4. 54 0
      hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java
  5. 33 0
      hsweb-authorization/hsweb-authorization-oauth2/pom.xml
  6. 108 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ErrorType.java
  7. 32 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/GrantType.java
  8. 41 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Constants.java
  9. 18 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java
  10. 29 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ResponseType.java
  11. 24 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java
  12. 14 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java
  13. 7 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/ClientCredentialGranter.java
  14. 33 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Client.java
  15. 9 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ClientManager.java
  16. 15 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java
  17. 7 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Granter.java
  18. 31 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Request.java
  19. 24 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java
  20. 36 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java
  21. 27 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeRequest.java
  22. 21 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java
  23. 30 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeTokenRequest.java
  24. 99 0
      hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java
  25. 1 0
      hsweb-authorization/pom.xml
  26. 14 0
      hsweb-system/hsweb-system-oauth2/pom.xml
  27. 1 0
      hsweb-system/pom.xml

+ 1 - 1
hsweb-authorization/hsweb-authorization-api/pom.xml

@@ -43,7 +43,7 @@
         </dependency>
 
         <dependency>
-            <groupId>io.swagger</groupId>
+            <groupId>io.swagger.core.v3</groupId>
             <artifactId>swagger-annotations</artifactId>
         </dependency>
 

+ 34 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/TokenAuthenticationManager.java

@@ -0,0 +1,34 @@
+package org.hswebframework.web.authorization.token;
+
+import org.hswebframework.web.authorization.Authentication;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+/**
+ * token 权限管理器,根据token来进行权限关联.
+ *
+ * @author zhouhao
+ * @since 4.0.7
+ */
+public interface TokenAuthenticationManager {
+
+    /**
+     * 根据token获取认证信息
+     *
+     * @param token token
+     * @return 认证信息
+     */
+    Mono<Authentication> getByToken(String token);
+
+    /**
+     * 设置token认证信息
+     *
+     * @param token token
+     * @param auth  认证信息
+     * @param ttl   有效期
+     * @return void
+     */
+    Mono<Void> putAuthentication(String token, Authentication auth, Duration ttl);
+
+}

+ 5 - 4
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/UserTokenReactiveAuthenticationSupplier.java

@@ -19,13 +19,14 @@ import java.util.Map;
  */
 public class UserTokenReactiveAuthenticationSupplier implements ReactiveAuthenticationSupplier {
 
-    private ReactiveAuthenticationManager defaultAuthenticationManager;
+    private final ReactiveAuthenticationManager defaultAuthenticationManager;
 
-    private UserTokenManager userTokenManager;
+    private final UserTokenManager userTokenManager;
 
-    private Map<String, ThirdPartReactiveAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
+    private final Map<String, ThirdPartReactiveAuthenticationManager> thirdPartAuthenticationManager = new HashMap<>();
 
-    public UserTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager, ReactiveAuthenticationManager defaultAuthenticationManager) {
+    public UserTokenReactiveAuthenticationSupplier(UserTokenManager userTokenManager,
+                                                   ReactiveAuthenticationManager defaultAuthenticationManager) {
         this.defaultAuthenticationManager = defaultAuthenticationManager;
         this.userTokenManager = userTokenManager;
     }

+ 54 - 0
hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/token/redis/RedisTokenAuthenticationManager.java

@@ -0,0 +1,54 @@
+package org.hswebframework.web.authorization.token.redis;
+
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.authorization.token.TokenAuthenticationManager;
+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;
+
+public class RedisTokenAuthenticationManager implements TokenAuthenticationManager {
+
+    private final ReactiveRedisOperations<String, Authentication> operations;
+
+    @SuppressWarnings("all")
+    public RedisTokenAuthenticationManager(ReactiveRedisConnectionFactory connectionFactory) {
+        this(new ReactiveRedisTemplate<>(
+                connectionFactory, RedisSerializationContext.<String, Authentication>newSerializationContext()
+                .key(RedisSerializer.string())
+                .value((RedisSerializer) RedisSerializer.java())
+                .hashKey(RedisSerializer.string())
+                .hashValue(RedisSerializer.java())
+                .build()
+        ));
+    }
+
+    public RedisTokenAuthenticationManager(ReactiveRedisOperations<String, Authentication> operations) {
+        this.operations = operations;
+    }
+
+    @Override
+    public Mono<Authentication> getByToken(String token) {
+        return operations
+                .opsForValue()
+                .get("token-auth:" + token);
+    }
+
+    @Override
+    public Mono<Void> putAuthentication(String token, Authentication auth, Duration ttl) {
+        return ttl.isNegative()
+                ? operations
+                .opsForValue()
+                .set("token-auth:" + token, auth)
+                .then()
+                : operations
+                .opsForValue()
+                .set("token-auth:" + token, auth, ttl)
+                .then()
+                ;
+    }
+}

+ 33 - 0
hsweb-authorization/hsweb-authorization-oauth2/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>hsweb-authorization</artifactId>
+        <groupId>org.hswebframework.web</groupId>
+        <version>4.0.7-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>hsweb-authorization-oauth2</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.hswebframework.web</groupId>
+            <artifactId>hsweb-authorization-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webflux</artifactId>
+            <optional>true</optional>
+        </dependency>
+    </dependencies>
+
+</project>

+ 108 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ErrorType.java

@@ -0,0 +1,108 @@
+/*
+ *  Copyright 2020 http://www.hswebframework.org
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *
+ */
+
+package org.hswebframework.web.oauth2;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public enum ErrorType {
+    ILLEGAL_CODE(1001), //错误的授权码
+    ILLEGAL_ACCESS_TOKEN(1002), //错误的access_token
+    ILLEGAL_CLIENT_ID(1003),//客户端信息错误
+    ILLEGAL_CLIENT_SECRET(1004),//客户端密钥错误
+    ILLEGAL_GRANT_TYPE(1005), //错误的授权方式
+    ILLEGAL_RESPONSE_TYPE(1006),//response_type 错误
+    ILLEGAL_AUTHORIZATION(1007),//Authorization 错误
+    ILLEGAL_REFRESH_TOKEN(1008),//refresh_token 错误
+    ILLEGAL_REDIRECT_URI(1009), //redirect_url 错误
+    ILLEGAL_SCOPE(1010), //scope 错误
+    ILLEGAL_USERNAME(1011), //username 错误
+    ILLEGAL_PASSWORD(1012), //password 错误
+
+    SCOPE_OUT_OF_RANGE(2010), //scope超出范围
+
+    UNAUTHORIZED_CLIENT(4010), //无权限
+    EXPIRED_TOKEN(4011), //TOKEN过期
+    INVALID_TOKEN(4012), //TOKEN已失效
+    UNSUPPORTED_GRANT_TYPE(4013), //不支持的认证类型
+    UNSUPPORTED_RESPONSE_TYPE(4014), //不支持的响应类型
+
+    EXPIRED_CODE(4015), //AUTHORIZATION_CODE过期
+    EXPIRED_REFRESH_TOKEN(4020), //REFRESH_TOKEN过期
+
+    CLIENT_DISABLED(4016),//客户端已被禁用
+
+    CLIENT_NOT_EXIST(4040),//客户端不存在
+
+    USER_NOT_EXIST(4041),//客户端不存在
+
+    STATE_ERROR(4042), //stat错误
+
+    ACCESS_DENIED(503), //访问被拒绝
+
+    OTHER(5001), //其他错误 ;
+
+    PARSE_RESPONSE_ERROR(5002),//解析返回结果错误
+
+    SERVICE_ERROR(5003); //服务器返回错误信息
+
+
+    private final String message;
+    private final int    code;
+    static final Map<Integer, ErrorType> codeMapping = Arrays.stream(ErrorType.values())
+            .collect(Collectors.toMap(ErrorType::code, type -> type));
+
+    ErrorType(int code) {
+        this.code = code;
+        message = this.name().toLowerCase();
+    }
+
+    ErrorType(int code, String message) {
+        this.message = message;
+        this.code = code;
+    }
+
+    public String message() {
+        if (message == null) {
+            return this.name();
+        }
+        return message;
+    }
+
+    public int code() {
+        return code;
+    }
+
+    public <T> T throwThis(Function<ErrorType, ? extends RuntimeException> errorTypeFunction) {
+        throw errorTypeFunction.apply(this);
+    }
+
+    public <T> T throwThis(BiFunction<ErrorType, String, ? extends RuntimeException> errorTypeFunction, String message) {
+        throw errorTypeFunction.apply(this, message);
+    }
+
+    public static Optional<ErrorType> fromCode(int code) {
+        return Optional.ofNullable(codeMapping.get(code));
+    }
+
+}

+ 32 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/GrantType.java

@@ -0,0 +1,32 @@
+/*
+ *  Copyright 2020 http://www.hswebframework.org
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *
+ */
+
+package org.hswebframework.web.oauth2;
+
+/**
+ *
+ * @author zhouhao
+ */
+public interface GrantType {
+    String authorization_code = "authorization_code";
+    String implicit           = "implicit";
+    @SuppressWarnings("all")
+    String password           = "password";
+    String client_credentials = "client_credentials";
+    String refresh_token      = "refresh_token";
+}

+ 41 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Constants.java

@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2020 http://www.hswebframework.org
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *
+ */
+
+package org.hswebframework.web.oauth2;
+
+/**
+ * @author zhouhao
+ */
+public interface OAuth2Constants {
+    String access_token  = "access_token";
+    String refresh_token = "refresh_token";
+    String grant_type    = "grant_type";
+    String scope         = "scope";
+    String client_id     = "client_id";
+    String client_secret = "client_secret";
+    String authorization = "Authorization";
+    String redirect_uri  = "redirect_uri";
+    String response_type = "response_type";
+    String state         = "state";
+    String code          = "code";
+    String username      = "username";
+
+    @SuppressWarnings("all")
+    String password      = "password";
+
+}

+ 18 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/OAuth2Exception.java

@@ -0,0 +1,18 @@
+package org.hswebframework.web.oauth2;
+
+import lombok.Getter;
+
+@Getter
+public class OAuth2Exception extends RuntimeException {
+    private final ErrorType type;
+
+    public OAuth2Exception(ErrorType type) {
+        super(type.message());
+        this.type = type;
+    }
+
+    public OAuth2Exception(String message, Throwable cause, ErrorType type) {
+        super(message, cause);
+        this.type = type;
+    }
+}

+ 29 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/ResponseType.java

@@ -0,0 +1,29 @@
+/*
+ *  Copyright 2020 http://www.hswebframework.org
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *
+ */
+
+package org.hswebframework.web.oauth2;
+
+/**
+ * TODO 完成注释
+ *
+ * @author zhouhao
+ */
+public interface ResponseType {
+    String code  = "code";
+    String token = "token";
+}

+ 24 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessToken.java

@@ -0,0 +1,24 @@
+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;
+
+@Getter
+@Setter
+public class AccessToken extends OAuth2Response {
+
+    @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;
+
+}

+ 14 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/AccessTokenManager.java

@@ -0,0 +1,14 @@
+package org.hswebframework.web.oauth2.server;
+
+import org.hswebframework.web.authorization.Authentication;
+import reactor.core.publisher.Mono;
+
+public interface AccessTokenManager {
+
+    Mono<Authentication> getAuthenticationByToken(String accessToken);
+
+    Mono<AccessToken> createAccessToken(String clientId, Authentication authentication);
+
+    Mono<AccessToken> refreshAccessToken(String clientId, String refreshToken);
+
+}

+ 7 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/ClientCredentialGranter.java

@@ -0,0 +1,7 @@
+package org.hswebframework.web.oauth2.server;
+
+public interface ClientCredentialGranter extends OAuth2Granter {
+
+
+
+}

+ 33 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Client.java

@@ -0,0 +1,33 @@
+package org.hswebframework.web.oauth2.server;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import javax.validation.constraints.NotBlank;
+
+@Getter
+@Setter
+public class OAuth2Client {
+
+    @NotBlank
+    private String clientId;
+
+    @NotBlank
+    private String clientSecret;
+
+    @NotBlank
+    private String name;
+
+    private String description;
+
+    @NotBlank
+    private String redirectUrl;
+
+    //client 所属用户
+    private String userId;
+
+    public void validateRedirectUri(String redirectUri){
+
+    }
+
+}

+ 9 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2ClientManager.java

@@ -0,0 +1,9 @@
+package org.hswebframework.web.oauth2.server;
+
+import reactor.core.publisher.Mono;
+
+public interface OAuth2ClientManager {
+
+    Mono<OAuth2Client> getClient(String clientId);
+
+}

+ 15 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2GrantService.java

@@ -0,0 +1,15 @@
+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();
+
+    ClientCredentialGranter clientCredential();
+
+    Mono<Authentication> grant(String scope, Authentication authentication);
+}

+ 7 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Granter.java

@@ -0,0 +1,7 @@
+package org.hswebframework.web.oauth2.server;
+
+public interface OAuth2Granter {
+
+
+
+}

+ 31 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Request.java

@@ -0,0 +1,31 @@
+package org.hswebframework.web.oauth2.server;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class OAuth2Request {
+
+    private Map<String, Object> parameters;
+
+
+    public Optional<Object> getParameter(String key) {
+        return Optional.ofNullable(parameters)
+                .map(params -> params.get(key));
+    }
+
+    public OAuth2Request with(String parameter, Object key) {
+        if (parameters == null) {
+            parameters = new HashMap<>();
+        }
+        parameters.put(parameter, key);
+        return this;
+    }
+}

+ 24 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/OAuth2Response.java

@@ -0,0 +1,24 @@
+package org.hswebframework.web.oauth2.server;
+
+import io.swagger.v3.oas.annotations.Hidden;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Getter
+@Setter
+public class OAuth2Response {
+    @Hidden
+    private Map<String,Object> parameters;
+
+    public OAuth2Response with(String parameter, Object key) {
+        if (parameters == null) {
+            parameters = new HashMap<>();
+        }
+        parameters.put(parameter, key);
+        return this;
+    }
+}

+ 36 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeGranter.java

@@ -0,0 +1,36 @@
+package org.hswebframework.web.oauth2.server.code;
+
+import org.hswebframework.web.oauth2.server.AccessToken;
+import org.hswebframework.web.oauth2.server.OAuth2Granter;
+import reactor.core.publisher.Mono;
+
+/**
+ * 授权码模式认证
+ *
+ * @author zhouhao
+ * @since 4.0.7
+ */
+public interface AuthorizationCodeGranter extends OAuth2Granter {
+
+    /**
+     * @return 申请授权码界面
+     */
+    String getLoginUrl();
+
+    /**
+     * 申请授权码
+     *
+     * @param request 请求
+     * @return 授权码信息
+     */
+    Mono<AuthorizationCodeResponse> requestCode(AuthorizationCodeRequest request);
+
+    /**
+     * 根据授权码获取token
+     *
+     * @param request 请求
+     * @return token
+     */
+    Mono<AccessToken> requestToken(AuthorizationCodeTokenRequest request);
+
+}

+ 27 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeRequest.java

@@ -0,0 +1,27 @@
+package org.hswebframework.web.oauth2.server.code;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.authorization.Authentication;
+import org.hswebframework.web.oauth2.server.OAuth2Client;
+import org.hswebframework.web.oauth2.server.OAuth2Request;
+
+import java.util.Map;
+
+@Getter
+@Setter
+public class AuthorizationCodeRequest extends OAuth2Request {
+    private OAuth2Client client;
+
+    private Authentication authentication;
+
+
+    public AuthorizationCodeRequest(OAuth2Client client,
+                                    Authentication authentication,
+                                    Map<String, Object> parameters) {
+        super(parameters);
+        this.client = client;
+        this.authentication = authentication;
+    }
+}

+ 21 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeResponse.java

@@ -0,0 +1,21 @@
+package org.hswebframework.web.oauth2.server.code;
+
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.oauth2.server.OAuth2Client;
+import org.hswebframework.web.oauth2.server.OAuth2Request;
+import org.hswebframework.web.oauth2.server.OAuth2Response;
+
+import java.util.HashMap;
+
+@Getter
+@Setter
+public class AuthorizationCodeResponse extends OAuth2Response {
+    private String code;
+
+    public AuthorizationCodeResponse(String code) {
+        this.code = code;
+        with("code", code);
+    }
+}

+ 30 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/code/AuthorizationCodeTokenRequest.java

@@ -0,0 +1,30 @@
+package org.hswebframework.web.oauth2.server.code;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.web.oauth2.server.OAuth2Client;
+import org.hswebframework.web.oauth2.server.OAuth2Request;
+
+import java.util.Map;
+import java.util.Optional;
+
+
+@Getter
+@Setter
+public class AuthorizationCodeTokenRequest extends OAuth2Request {
+
+    private OAuth2Client client;
+
+    public AuthorizationCodeTokenRequest(OAuth2Client client, Map<String, Object> parameters) {
+        super(parameters);
+        this.client = client;
+    }
+
+    public Optional<String> code() {
+        return getParameter("code").map(String::valueOf);
+    }
+
+    public Optional<String> scope() {
+        return getParameter("scope").map(String::valueOf);
+    }
+}

+ 99 - 0
hsweb-authorization/hsweb-authorization-oauth2/src/main/java/org/hswebframework/web/oauth2/server/web/OAuth2AuthorizeController.java

@@ -0,0 +1,99 @@
+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 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.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 reactor.core.publisher.Mono;
+
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/oauth2")
+@AllArgsConstructor
+public class OAuth2AuthorizeController {
+
+    private final OAuth2GrantService oAuth2GrantService;
+
+    private final OAuth2ClientManager clientManager;
+
+    @PostMapping(value = "/authorize", params = "response_type=code")
+    @Operation(summary = "申请授权码,并获取重定向地址", parameters = {
+            @Parameter(description = "client_id"),
+            @Parameter(description = "redirect_uri"),
+            @Parameter(description = "state")
+    })
+    public Mono<String> authorizeByCode(@RequestBody Mono<Map<String, Object>> params) {
+
+        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()));
+                                })));
+    }
+
+    @PostMapping(value = "/token", params = "grant_type=authorization_code")
+    @Operation(summary = "使用授权码申请token",parameters = {
+            @Parameter(description = "client_id"),
+            @Parameter(description = "client_secret"),
+            @Parameter(description = "code")
+    })
+    @Authorize(ignore = true)
+    public Mono<ResponseEntity<AccessToken>> requestTokenByCode(@RequestBody Mono<Map<String, Object>> params) {
+
+        return params
+                .flatMap(param -> this
+                        .getOAuth2Client((String) param.get("client_id"))
+                        .flatMap(client -> oAuth2GrantService
+                                .code()
+                                .requestToken(new AuthorizationCodeTokenRequest(client, param))))
+                .map(ResponseEntity::ok);
+    }
+
+
+    @SneakyThrows
+    public static String urlEncode(String url) {
+        return URLEncoder.encode(url, "utf-8");
+    }
+
+    public String buildRedirect(String redirectUri, Map<String, Object> params) {
+        String paramsString = params.entrySet()
+                .stream()
+                .map(e -> e.getKey() + "=" + urlEncode(String.valueOf(e.getValue())))
+                .collect(Collectors.joining("&"));
+        if (redirectUri.contains("?")) {
+            return redirectUri + "&" + paramsString;
+        }
+        return redirectUri + "?" + paramsString;
+    }
+
+
+    private Mono<OAuth2Client> getOAuth2Client(String id) {
+        return clientManager.getClient(id);
+    }
+}

+ 1 - 0
hsweb-authorization/pom.xml

@@ -14,6 +14,7 @@
     <modules>
         <module>hsweb-authorization-api</module>
         <module>hsweb-authorization-basic</module>
+        <module>hsweb-authorization-oauth2</module>
     </modules>
 
 

+ 14 - 0
hsweb-system/hsweb-system-oauth2/pom.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>hsweb-framework</artifactId>
+        <groupId>org.hswebframework.web</groupId>
+        <version>4.0.7-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>hsweb-system-oauth2</artifactId>
+
+</project>

+ 1 - 0
hsweb-system/pom.xml

@@ -15,6 +15,7 @@
         <module>hsweb-system-authorization</module>
         <module>hsweb-system-file</module>
         <module>hsweb-system-dictionary</module>
+        <module>hsweb-system-oauth2</module>
     </modules>
     <artifactId>hsweb-system</artifactId>