diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md index 6c3ee3d..a06693c 100644 --- a/.gitee/ISSUE_TEMPLATE.zh-CN.md +++ b/.gitee/ISSUE_TEMPLATE.zh-CN.md @@ -1,6 +1,6 @@ > 为了更高效率的处理 issue、解决问题,请按照如下格式填写 BUG 详情。 > -> 如果提交的 issue 不属于 BUG,可以忽略该问题模板。 +> 如果提交的 issue 不属于 BUG 范围,可以忽略/重置该问题模板。 ## 提交前请确认: @@ -9,8 +9,16 @@ ## 当前环境 -- 使用的版本号(比如 `1.0.1`): -- 出问题的模块(比如 `jap-ids`): +- 出问题的模块: + - [ ] jap-ids + - [ ] jap-mfa + - [ ] jap-oauth2 + - [ ] jap-oidc + - [ ] jap-simple + - [ ] jap-social + - [ ] jap-sso + - [ ] jap-core +- 使用的 jap 版本号: ## 问题描述 @@ -22,7 +30,7 @@ ## 实际结果如何? 报错信息(请提交完整的截图或者日志) -> 一定要提供完整详细的异常堆栈。 +> 如果涉及到 BUG,请一定要提供完整详细的异常堆栈。 diff --git a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md index 46b51ba..bb9f7b4 100644 --- a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md +++ b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md @@ -4,7 +4,7 @@ > - 是否新增了第三方依赖库?如果新增了第三方依赖库,其 LICENSE 是否兼容 LGPL-3.0? (LICENSE 兼容,请参考:[开源许可证兼容性指南 - 使用库的兼容性列表](https://shimo.im/docs/uL4VQaYGL2sadQOV#anchor-74ae)) > - 是否参考/借鉴/复制了其他开源项目的代码?被引用的代码所属项目的 LICENSE 是否兼容 LGPL-3.0?(LICENSE 兼容,请参考:[开源许可证兼容性指南 - 合并/修改代码的许可证兼容性列表](https://shimo.im/docs/uL4VQaYGL2sadQOV#anchor-39f8)) > -> 如果你的代码或者代码中新引入的依赖项与 LGPL-3.0 不兼容,非常抱歉,我们可能不会合并你的代码。 +> 如果你的代码或者代码中新引入的依赖项与 LGPL-3.0 不兼容,非常抱歉,我们可能不会通过你的代码。 > > 最后,感谢你的关注、支持。 @@ -13,6 +13,9 @@ ## 【必填】是否自测完成并提供了相关单元测试代码? +- [ ] 是,已经自测完成,并提供了单元测试代码,可以直接合并。 +- [ ] 否,仅完成了代码,并没做测试,也没有添加单元测试。 + ## 【可选】关联的 Issue(有则填) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ad01cc3..c585351 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -15,8 +15,16 @@ assignees: '' ## Environment +- JustAuthPlus(JAP) Module: + - [ ] jap-ids + - [ ] jap-mfa + - [ ] jap-oauth2 + - [ ] jap-oidc + - [ ] jap-simple + - [ ] jap-social + - [ ] jap-sso + - [ ] jap-core - JustAuthPlus(JAP) version(e.g. `1.0.1`): -- JustAuthPlus(JAP) Module(e.g. `jap-ids`): ### Minimal test code / Steps to reproduce the issue 1. diff --git a/.github/ISSUE_TEMPLATE/request-help.md b/.github/ISSUE_TEMPLATE/request-help.md index 64d7ac4..52cebe8 100644 --- a/.github/ISSUE_TEMPLATE/request-help.md +++ b/.github/ISSUE_TEMPLATE/request-help.md @@ -12,5 +12,13 @@ assignees: '' ## Environment -- JustAuthPlus(JAP) version(e.g. `1.0.1`): -- JustAuthPlus(JAP) Module(e.g. `jap-ids`): \ No newline at end of file +- JustAuthPlus(JAP) Module: + - [ ] jap-ids + - [ ] jap-mfa + - [ ] jap-oauth2 + - [ ] jap-oidc + - [ ] jap-simple + - [ ] jap-social + - [ ] jap-sso + - [ ] jap-core +- JustAuthPlus(JAP) version(e.g. `1.0.1`): \ No newline at end of file diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index 15a2bc0..e893371 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -7,8 +7,14 @@ on: workflow_dispatch: push: branches: [ dev ] + paths: + - src/** + - pom.xml pull_request: branches: [ dev ] + paths: + - src/** + - pom.xml jobs: publish: @@ -30,7 +36,22 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSWORD gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - - name: Publish to the Maven Central Repository + - name: Cache m2 package + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + - name: get current project version to set env.VERSION + run: echo "VERSION=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout`" >> $GITHUB_ENV + + - name: set snapshot version + if: ${{ !endsWith( env.VERSION , '-SNAPSHOT') }} + run: mvn versions:set -DnewVersion=${{ env.VERSION }}-SNAPSHOT + + - name: deploy snapshot to oss repository run: mvn clean deploy -P release env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} diff --git a/.gitignore b/.gitignore index 5fdc48a..eb38926 100644 --- a/.gitignore +++ b/.gitignore @@ -51,7 +51,6 @@ build/ .vscode/ ### other ### -/jap-simple/src/main/java/com/fujieid/jap/simple/SimpleCallback.java /docs/bin/deploy.sh /jap-core/pom.xml.versionsBackup /jap-oauth2/pom.xml.versionsBackup diff --git a/CHANGELOGS.md b/CHANGELOGS.md index b85e8fe..0eec064 100644 --- a/CHANGELOGS.md +++ b/CHANGELOGS.md @@ -1,4 +1,87 @@ -## v1.0.4 (2021-08-**) +## v1.0.5 (2021-09-15) + +- feat: Add `jap-http-api` module. (Gitee Issue [#I43ZS7](https://gitee.com/fujieid/jap/issues/I43ZS7)) +- feat: Add `jap-ids-web` module. Package the filter of ids as a separate component. +- feat: add HTTP servlet adapter to decouple jakarta servlets. **Note [1]** +- feat: [jap-social] Support to bind the account of the third-party platform. (Gitee Issue [#I46J6W](https://gitee.com/fujieid/jap/issues/I46J6W)) +- change: [jap-ids] scope changed to optional. +- change: [jap-sso] Upgrade `kisso` to 3.7.7, **Solve the vulnerability of jackson**. +- change: [jap-mfa] Upgrade `googleauth` to 1.5.0, **Solve the vulnerability of apache httpclient**. +- change: Upgrade `simple-http` to 1.0.5. +- change: Upgrade `JustAuth` to 1.16.4. +- change: Optimize code. + +**Note [1]:** + +In versions prior to version 1.0.5 of jap, rely on the `HttpServletRequest`, `Cookie`, `HttpServletResponse`, and `HttpSession` under the `javax.servlet.http` package in `jakarta-servlet`, such as: + +```java +// Interface provided by jap +public interface JapStrategy { + default JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + return null; + } +} +``` + +```java +// Use jap in spring framework +XxJapStrategy.authenticate(config, request, response); +``` + +In order to improve the adaptability of the framework, since version 1.0.5, JAP removed the dependency of `jakarta-servlet` and adopted a new set of interfaces (reference: [jap-http](https:gitee.comfujieidjap-http) ). + +The developer needs to adapt the original request when calling the JAP interface. + +For example, if the developer uses `jakarta-servlet`, then the `HttpServletRequest` needs to be adapted: + +```java +// Use 1.0.5 or higher version of jap in spring framework +XxJapStrategy.authenticate(config, new JakartaRequestAdapter(request), new JakartaResponseAdapter(response)); +``` + +---- + +- feat: 增加 `jap-http-api` 模块。 (Gitee Issue [#I43ZS7](https://gitee.com/fujieid/jap/issues/I43ZS7)) +- feat: 增加 `jap-ids-web` 模块。 将 `jap-ids` 的过滤器打包为一个单独的组件。 +- feat: 添加 HTTP servlet 适配器以解耦 jakarta servlet。**注[1]** +- feat: [jap-social] 支持绑定第三方平台账号,该版本将社会化登录和绑定账号独立开来,以使其更加使用与多场景。 (Gitee Issue [#I46J6W](https://gitee.com/fujieid/jap/issues/I46J6W)) +- change: [jap-ids] `scope` 在各个流程中都更改为可选,遵循 RFC6749 规范。 +- change: [jap-sso] 升级 `kisso` 的版本为 3.7.7, **解决 jackson 的漏洞**。 +- change: [jap-mfa] 升级 `googleauth` 的版本为 1.5.0, **解决 apache httpclient 的漏洞**。 +- change: 升级 `simple-http` 的版本为 1.0.5. +- change: 升级 `JustAuth` 的版本为 1.16.4. +- change: 优化代码。 + +**注[1]:** + +在 1.0.5 以前版本,jap 中依赖 `jakarta-servlet` 中 `javax.servlet.http` 包下的 `HttpServletRequest`、`Cookie`、`HttpServletResponse`、`HttpSession`,比如: + +```java +// jap 提供的接口 +public interface JapStrategy { + default JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + return null; + } +} +``` + +```java +// 在spring框架中使用 jap +XxJapStrategy.authenticate(config, request, response); +``` + +为了提高框架适配性,自 1.0.5 版本开始,JAP 去掉了 `jakarta-servlet` 依赖,采用了一套全新的接口(参考:[jap-http](https://gitee.com/fujieid/jap-http)),开发者在调用 JAP 接口时需要对原 request 进行适配。 + +比如,开发者使用了 `jakarta-servlet`,那么需要对 `HttpServletRequest` 进行适配处理: + +```java +// 在spring框架中使用 1.0.5 或更高级版本的 jap +XxJapStrategy.authenticate(config, new JakartaRequestAdapter(request), new JakartaResponseAdapter(response)); +``` + + +## v1.0.4 (2021-08-15) - fix: [jap-ids] Support to generate custom token. (Gitee[#I3U1ON](https://gitee.com/fujieid/jap/issues/I3U1ON)) - fix: [jap-ids] Support custom verification of client_secret, such as: BCrypt, etc. (Gitee[#I44032](https://gitee.com/fujieid/jap/issues/I44032)) @@ -10,6 +93,7 @@ - doc: change the template of issue and PR ---- + - fix: [jap-ids] 支持生成自定义 token(包含 access_token 和 refresh_token)。 (Gitee[#I3U1ON](https://gitee.com/fujieid/jap/issues/I3U1ON)) - fix: [jap-ids] 支持自定义验证 `client_secret`,适配多种场景,如:BCrypt 等。 (Gitee[#I44032](https://gitee.com/fujieid/jap/issues/I44032)) - feat: [jap-ids] 当启用 `IdsConfig#enableDynamicIssuer` 时,支持自定义 `context-path` diff --git a/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java b/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java index 318260f..8e19b36 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/JapUserService.java @@ -86,6 +86,18 @@ public interface JapUserService { return null; } + + /** + * After logging in, bind the account of the third-party platform + * + * @param japUser User information of the third-party platform after successful login + * @param bindUserId The user id that needs to be bound, this is a user of the business system, not a user of the third-party platform. + * @return When binding successfully, return {@code true}, otherwise return {@code false} + */ + default boolean bindSocialUser(JapUser japUser, String bindUserId) { + return false; + } + /** * Save the oauth login user information to the database and return JapUser *

@@ -101,4 +113,15 @@ public interface JapUserService { return null; } + /** + * Save the http authed user information to the database and return JapUser + *

+ * It is suitable for the {@code jap-http-api} module + * @param userinfo user information + * @return When saving successfully, return {@code JapUser}, otherwise return {@code null} + */ + default JapUser createAndGetHttpApiUser(Object userinfo){ + return null; + } + } diff --git a/jap-core/src/main/java/com/fujieid/jap/core/cache/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/cache/package-info.java new file mode 100644 index 0000000..694858b --- /dev/null +++ b/jap-core/src/main/java/com/fujieid/jap/core/cache/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * The cache component of jap has a built-in local cache based on MAP. + *

+ * Developers can implement specific cache solutions based on the {@link com.fujieid.jap.core.cache.JapCache} interface, + * such as redis cache. + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.core.cache; diff --git a/jap-core/src/main/java/com/fujieid/jap/core/config/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/config/package-info.java new file mode 100644 index 0000000..c6046b8 --- /dev/null +++ b/jap-core/src/main/java/com/fujieid/jap/core/config/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * JAP configuration file, including common configuration and common configuration classes. + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.core.config; diff --git a/jap-core/src/main/java/com/fujieid/jap/core/context/JapAuthentication.java b/jap-core/src/main/java/com/fujieid/jap/core/context/JapAuthentication.java index 02d2bfe..5da4729 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/context/JapAuthentication.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/context/JapAuthentication.java @@ -25,12 +25,12 @@ import com.fujieid.jap.core.result.JapErrorCode; import com.fujieid.jap.core.result.JapResponse; import com.fujieid.jap.core.store.JapUserStore; import com.fujieid.jap.core.util.JapTokenHelper; -import com.fujieid.jap.core.util.RequestUtil; +import com.fujieid.jap.http.JapHttpCookie; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.RequestUtil; import com.xkcoding.json.util.Kv; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.Serializable; import java.util.Date; import java.util.Map; @@ -74,7 +74,7 @@ public class JapAuthentication implements Serializable { * @param response current HTTP response * @return JapUser */ - public static JapUser getUser(HttpServletRequest request, HttpServletResponse response) { + public static JapUser getUser(JapHttpRequest request, JapHttpResponse response) { if (null == context) { return null; } @@ -107,7 +107,7 @@ public class JapAuthentication implements Serializable { * @param response current HTTP response * @return JapResponse */ - public static JapResponse checkUser(HttpServletRequest request, HttpServletResponse response) { + public static JapResponse checkUser(JapHttpRequest request, JapHttpResponse response) { JapUser japUser = getUser(request, response); if (null == japUser) { return JapResponse.error(JapErrorCode.NOT_LOGGED_IN); @@ -154,7 +154,7 @@ public class JapAuthentication implements Serializable { * @param response current HTTP response * @return boolean */ - public static boolean logout(HttpServletRequest request, HttpServletResponse response) { + public static boolean logout(JapHttpRequest request, JapHttpResponse response) { JapUserStore japUserStore = context.getUserStore(); if (null == japUserStore) { return false; @@ -162,7 +162,7 @@ public class JapAuthentication implements Serializable { japUserStore.remove(request, response); // Clear all cookie information - Map cookieMap = RequestUtil.getCookieMap(request); + Map cookieMap = RequestUtil.getCookieMap(request); if (CollectionUtil.isNotEmpty(cookieMap)) { cookieMap.forEach((key, cookie) -> { cookie.setMaxAge(0); diff --git a/jap-core/src/main/java/com/fujieid/jap/core/context/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/context/package-info.java new file mode 100644 index 0000000..68f808b --- /dev/null +++ b/jap-core/src/main/java/com/fujieid/jap/core/context/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * JAP context + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.core.context; diff --git a/jap-core/src/main/java/com/fujieid/jap/core/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/package-info.java index 97b8dcd..5fbce4d 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/package-info.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/package-info.java @@ -14,7 +14,7 @@ * limitations under the License. */ /** - * Jap core module, external specification interface standard + * Jap core module, Provide external standard interface support * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0.0 diff --git a/jap-core/src/main/java/com/fujieid/jap/core/result/JapErrorCode.java b/jap-core/src/main/java/com/fujieid/jap/core/result/JapErrorCode.java index c2e0358..d3ec034 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/result/JapErrorCode.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/result/JapErrorCode.java @@ -38,6 +38,7 @@ public enum JapErrorCode { MISS_ISSUER(1006, "OidcStrategy requires a issuer option."), MISS_CREDENTIALS(1007, "Missing credentials"), INVALID_GRANT_TYPE(1008, "The grant type is not supported by the authorization server, or the current client is not authorized for the grant type."), + ERROR_HTTP_API_CONFIG(1008,"http api config error,please check") ; private final int errroCode; diff --git a/jap-core/src/main/java/com/fujieid/jap/core/result/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/result/package-info.java new file mode 100644 index 0000000..a13bab3 --- /dev/null +++ b/jap-core/src/main/java/com/fujieid/jap/core/result/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * Used to standardize the return value and exception status code of the JAP interface. + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.core.result; diff --git a/jap-core/src/main/java/com/fujieid/jap/core/spi/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/spi/package-info.java new file mode 100644 index 0000000..1ffd6be --- /dev/null +++ b/jap-core/src/main/java/com/fujieid/jap/core/spi/package-info.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * Supports SPI features. When implementing an interface, developers can pass parameters through the setXx method, + * or through the SPI specification. for example: + *

+ * For the JapUserService interface provided by jap, developers can set the interface implementation class through xx.setUserService, + * You can also configure the implementation class of the interface in a file named after the interface name in the resources/META-INF/services folder, such as: + * resources/META-INF/services/com.fujieid.jap.core.JapUserService, the content of the file is xx.xxx.xx.JapUserServiceImpl + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.core.spi; diff --git a/jap-core/src/main/java/com/fujieid/jap/core/store/JapUserStore.java b/jap-core/src/main/java/com/fujieid/jap/core/store/JapUserStore.java index 552cd65..e4366db 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/store/JapUserStore.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/store/JapUserStore.java @@ -16,9 +16,8 @@ package com.fujieid.jap.core.store; import com.fujieid.jap.core.JapUser; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; /** * Save, delete and obtain the login user information.By default, based on local caching, @@ -38,7 +37,7 @@ public interface JapUserStore { * @param japUser User information after successful login * @return JapUser */ - JapUser save(HttpServletRequest request, HttpServletResponse response, JapUser japUser); + JapUser save(JapHttpRequest request, JapHttpResponse response, JapUser japUser); /** * Clear user information from cache @@ -46,7 +45,7 @@ public interface JapUserStore { * @param request current HTTP request * @param response current HTTP response */ - void remove(HttpServletRequest request, HttpServletResponse response); + void remove(JapHttpRequest request, JapHttpResponse response); /** * Get the login user information from the cache, return {@code JapUser} if it exists, @@ -56,5 +55,5 @@ public interface JapUserStore { * @param response current HTTP response * @return JapUser */ - JapUser get(HttpServletRequest request, HttpServletResponse response); + JapUser get(JapHttpRequest request, JapHttpResponse response); } diff --git a/jap-core/src/main/java/com/fujieid/jap/core/store/SessionJapUserStore.java b/jap-core/src/main/java/com/fujieid/jap/core/store/SessionJapUserStore.java index bfa2544..c96c9f5 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/store/SessionJapUserStore.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/store/SessionJapUserStore.java @@ -22,10 +22,9 @@ import com.fujieid.jap.core.config.JapConfig; import com.fujieid.jap.core.context.JapAuthentication; import com.fujieid.jap.core.util.JapTokenHelper; import com.fujieid.jap.core.util.JapUtil; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.JapHttpSession; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) @@ -46,8 +45,8 @@ public class SessionJapUserStore implements JapUserStore { * @return JapUser */ @Override - public JapUser save(HttpServletRequest request, HttpServletResponse response, JapUser japUser) { - HttpSession session = request.getSession(); + public JapUser save(JapHttpRequest request, JapHttpResponse response, JapUser japUser) { + JapHttpSession session = request.getSession(); JapUser newUser = BeanUtil.copyProperties(japUser, JapUser.class); newUser.setPassword(null); session.setAttribute(JapConst.SESSION_USER_KEY, newUser); @@ -68,7 +67,7 @@ public class SessionJapUserStore implements JapUserStore { * @param response current HTTP response */ @Override - public void remove(HttpServletRequest request, HttpServletResponse response) { + public void remove(JapHttpRequest request, JapHttpResponse response) { JapConfig japConfig = JapAuthentication.getContext().getConfig(); if (!japConfig.isSso()) { @@ -78,7 +77,7 @@ public class SessionJapUserStore implements JapUserStore { } } - HttpSession session = request.getSession(); + JapHttpSession session = request.getSession(); session.removeAttribute(JapConst.SESSION_USER_KEY); session.invalidate(); } @@ -92,8 +91,8 @@ public class SessionJapUserStore implements JapUserStore { * @return JapUser */ @Override - public JapUser get(HttpServletRequest request, HttpServletResponse response) { - HttpSession session = request.getSession(); + public JapUser get(JapHttpRequest request, JapHttpResponse response) { + JapHttpSession session = request.getSession(); return (JapUser) session.getAttribute(JapConst.SESSION_USER_KEY); } } diff --git a/jap-core/src/main/java/com/fujieid/jap/core/store/SsoJapUserStore.java b/jap-core/src/main/java/com/fujieid/jap/core/store/SsoJapUserStore.java index b4cb762..2010664 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/store/SsoJapUserStore.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/store/SsoJapUserStore.java @@ -19,12 +19,11 @@ import cn.hutool.core.util.StrUtil; import com.fujieid.jap.core.JapUser; import com.fujieid.jap.core.JapUserService; import com.fujieid.jap.core.util.JapTokenHelper; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.sso.JapSsoHelper; import com.fujieid.jap.sso.config.JapSsoConfig; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * Operation on users in SSO mode (cookie) * @@ -57,7 +56,7 @@ public class SsoJapUserStore extends SessionJapUserStore { * @return JapUser */ @Override - public JapUser save(HttpServletRequest request, HttpServletResponse response, JapUser japUser) { + public JapUser save(JapHttpRequest request, JapHttpResponse response, JapUser japUser) { String token = JapSsoHelper.login(japUser.getUserId(), japUser.getUsername(), this.japSsoConfig, request, response); super.save(request, response, japUser); JapTokenHelper.saveUserToken(japUser.getUserId(), token); @@ -71,7 +70,7 @@ public class SsoJapUserStore extends SessionJapUserStore { * @param response current HTTP response */ @Override - public void remove(HttpServletRequest request, HttpServletResponse response) { + public void remove(JapHttpRequest request, JapHttpResponse response) { JapUser japUser = this.get(request, response); if (null != japUser) { JapTokenHelper.removeUserToken(japUser.getUserId()); @@ -89,7 +88,7 @@ public class SsoJapUserStore extends SessionJapUserStore { * @return JapUser */ @Override - public JapUser get(HttpServletRequest request, HttpServletResponse response) { + public JapUser get(JapHttpRequest request, JapHttpResponse response) { String userId = JapSsoHelper.checkLogin(request); if (StrUtil.isBlank(userId)) { // The cookie has expired. Clear session content diff --git a/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java b/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java index 93e60d9..defe2a7 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/strategy/AbstractJapStrategy.java @@ -32,11 +32,10 @@ import com.fujieid.jap.core.result.JapResponse; import com.fujieid.jap.core.store.JapUserStore; import com.fujieid.jap.core.store.SessionJapUserStore; import com.fujieid.jap.core.store.SsoJapUserStore; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.sso.JapSsoHelper; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * General policy handling methods and parameters, policies of other platforms can inherit * {@code AbstractJapStrategy}, and override the constructor @@ -106,11 +105,11 @@ public abstract class AbstractJapStrategy implements JapStrategy { * @param response current HTTP response * @return boolean */ - protected JapUser checkSession(HttpServletRequest request, HttpServletResponse response) { + protected JapUser checkSession(JapHttpRequest request, JapHttpResponse response) { return japContext.getUserStore().get(request, response); } - protected JapResponse loginSuccess(JapUser japUser, HttpServletRequest request, HttpServletResponse response) { + protected JapResponse loginSuccess(JapUser japUser, JapHttpRequest request, JapHttpResponse response) { japContext.getUserStore().save(request, response, japUser); return JapResponse.success(japUser); } diff --git a/jap-core/src/main/java/com/fujieid/jap/core/strategy/JapStrategy.java b/jap-core/src/main/java/com/fujieid/jap/core/strategy/JapStrategy.java index f9ed83b..ca10345 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/strategy/JapStrategy.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/strategy/JapStrategy.java @@ -18,9 +18,8 @@ package com.fujieid.jap.core.strategy; import com.fujieid.jap.core.config.AuthenticateConfig; import com.fujieid.jap.core.result.JapErrorCode; import com.fujieid.jap.core.result.JapResponse; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; /** * The unified implementation interface of JAP Strategy, which must be implemented for all specific business policies. @@ -39,7 +38,7 @@ public interface JapStrategy { * @param response The response to authenticate * @return JapResponse */ - default JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + default JapResponse authenticate(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { return JapResponse.error(JapErrorCode.ERROR.getErrroCode(), "JapStrategy#authenticate(AuthenticateConfig, HttpServletRequest, HttpServletResponse) must be overridden by subclass"); } } diff --git a/jap-core/src/main/java/com/fujieid/jap/core/util/JapUtil.java b/jap-core/src/main/java/com/fujieid/jap/core/util/JapUtil.java index f564df3..4b829a1 100644 --- a/jap-core/src/main/java/com/fujieid/jap/core/util/JapUtil.java +++ b/jap-core/src/main/java/com/fujieid/jap/core/util/JapUtil.java @@ -16,13 +16,10 @@ package com.fujieid.jap.core.util; import com.fujieid.jap.core.JapUser; -import com.fujieid.jap.core.exception.JapException; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.sso.JapSsoUtil; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - /** * The tool class of Jap only provides static methods common to all modules * @@ -35,20 +32,16 @@ public class JapUtil extends com.xkcoding.json.util.ObjectUtil { private static final String REDIRECT_ERROR = "JAP failed to redirect via HttpServletResponse."; @Deprecated - public static void redirect(String url, HttpServletResponse response) { + public static void redirect(String url, JapHttpResponse response) { redirect(url, REDIRECT_ERROR, response); } @Deprecated - public static void redirect(String url, String errorMessage, HttpServletResponse response) { - try { - response.sendRedirect(url); - } catch (IOException ex) { - throw new JapException(errorMessage, ex); - } + public static void redirect(String url, String errorMessage, JapHttpResponse response) { + response.redirect(url); } - public static String createToken(JapUser japUser, HttpServletRequest request) { + public static String createToken(JapUser japUser, JapHttpRequest request) { return JapSsoUtil.createToken(japUser.getUserId(), japUser.getUsername(), request); } } diff --git a/jap-core/src/main/java/com/fujieid/jap/core/util/RequestUtil.java b/jap-core/src/main/java/com/fujieid/jap/core/util/RequestUtil.java deleted file mode 100644 index 82d0af6..0000000 --- a/jap-core/src/main/java/com/fujieid/jap/core/util/RequestUtil.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). - *

- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.gnu.org/licenses/lgpl.html - *

- * 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 com.fujieid.jap.core.util; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; -import com.xkcoding.json.util.StringUtil; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Arrays; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * http servlet request util - * - * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0.0 - * @since 1.0.1 - */ -public class RequestUtil { - - /** - * Get the url parameter value of the request through {@code request.getParameter(paramName)} - * - * @param paramName parameter name - * @param request current HTTP request - * @return string - */ - public static String getParam(String paramName, HttpServletRequest request) { - if (null == request) { - return null; - } - return request.getParameter(paramName); - } - - /** - * Get request header - * - * @param headerName request header name - * @param request current HTTP request - * @return string - */ - public static String getHeader(String headerName, HttpServletRequest request) { - if (null == request) { - return ""; - } - return request.getHeader(headerName); - } - - /** - * Get the referer of the current HTTP request - * - * @param request current HTTP request - * @return string - */ - public static String getReferer(HttpServletRequest request) { - return getHeader("Referer", request); - } - - /** - * Get subdomain name - * - * @param request current HTTP request - * @return string - */ - public static String getFullDomainName(HttpServletRequest request) { - StringBuffer url = request.getRequestURL(); - return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString(); - } - - /** - * Get the User-Agent of the current HTTP request - * - * @param request current HTTP request - * @return string - */ - public static String getUa(HttpServletRequest request) { - return getHeader("User-Agent", request); - } - - /** - * Get the IP of the current HTTP request - * - * @param request current HTTP request - * @return string - */ - public static String getIp(HttpServletRequest request) { - if (null == request) { - return ""; - } - String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; - String ip; - for (String header : headers) { - ip = request.getHeader(header); - if (isValidIp(ip)) { - return getMultistageReverseProxyIp(ip); - } - } - ip = request.getRemoteAddr(); - return getMultistageReverseProxyIp(ip); - } - - /** - * Obtain the first non-unknown ip address from the multi-level reverse proxy - * - * @param ip IP - * @return The first non-unknown ip address - */ - private static String getMultistageReverseProxyIp(String ip) { - if (ip != null && ip.indexOf(",") > 0) { - final String[] ips = ip.trim().split(","); - for (String subIp : ips) { - if (isValidIp(subIp)) { - ip = subIp; - break; - } - } - } - return ip; - } - - /** - * Verify ip legitimacy - * - * @param ip ip - * @return boolean - */ - private static boolean isValidIp(String ip) { - return !StringUtil.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip); - } - - /** - * Get the request url - * - * @param encode Whether to encode url - * @param request current HTTP request - * @return string - */ - public static String getRequestUrl(boolean encode, HttpServletRequest request) { - if (null == request) { - return ""; - } - String currentUrl = request.getRequestURL().toString(); - String queryString = request.getQueryString(); - if (!StringUtil.isEmpty(queryString)) { - currentUrl = currentUrl + "?" + queryString; - } - - if (encode) { - String result = ""; - try { - result = URLEncoder.encode(currentUrl, "UTF-8"); - } catch (UnsupportedEncodingException e) { - //ignore - } - return result; - } - - return currentUrl; - } - - /** - * Get the value of the cookie - * - * @param request current HTTP request - * @param name cookie name - * @return String - */ - public static String getCookieVal(HttpServletRequest request, String name) { - Cookie cookie = getCookie(request, name); - return cookie != null ? cookie.getValue() : null; - } - - /** - * Get cookie - * - * @param request current HTTP request - * @param name cookie name - * @return Cookie - */ - public static Cookie getCookie(HttpServletRequest request, String name) { - Cookie[] cookies = request.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (name.equals(cookie.getName())) { - return cookie; - } - } - } - return null; - } - - /** - * Get all the cookies, and use the cookie name as the key to form a map - * - * @param request current HTTP request - * @return Map - */ - public static Map getCookieMap(HttpServletRequest request) { - final Cookie[] cookies = request.getCookies(); - if (ArrayUtil.isEmpty(cookies)) { - return MapUtil.empty(); - } - - return Arrays.stream(cookies).collect(Collectors.toMap(Cookie::getName, v -> v, (k1, k2) -> k1)); - } - - /** - * Set cookie - * - * @param response current HTTP response - * @param name cookie name - * @param value cookie value - * @param maxAge maxAge - * @param path path - * @param domain domain - */ - public static void setCookie(HttpServletResponse response, String name, String value, int maxAge, String path, String domain) { - Cookie cookie = new Cookie(name, value); - cookie.setPath(path); - if (null != domain) { - cookie.setDomain(domain); - } - cookie.setMaxAge(maxAge); - cookie.setHttpOnly(false); - response.addCookie(cookie); - } -} diff --git a/jap-core/src/main/java/com/fujieid/jap/core/util/package-info.java b/jap-core/src/main/java/com/fujieid/jap/core/util/package-info.java new file mode 100644 index 0000000..ab2b5b0 --- /dev/null +++ b/jap-core/src/main/java/com/fujieid/jap/core/util/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * jap general tools + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.core.util; diff --git a/jap-core/src/test/java/com/fujieid/jap/core/context/JapAuthenticationTest.java b/jap-core/src/test/java/com/fujieid/jap/core/context/JapAuthenticationTest.java index 144e9cb..a7a925d 100644 --- a/jap-core/src/test/java/com/fujieid/jap/core/context/JapAuthenticationTest.java +++ b/jap-core/src/test/java/com/fujieid/jap/core/context/JapAuthenticationTest.java @@ -22,6 +22,10 @@ import com.fujieid.jap.core.cache.JapLocalCache; import com.fujieid.jap.core.config.JapConfig; import com.fujieid.jap.core.result.JapResponse; import com.fujieid.jap.core.store.JapUserStore; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; +import com.fujieid.jap.http.jakarta.JakartaResponseAdapter; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -39,9 +43,14 @@ public class JapAuthenticationTest { @Mock public HttpServletResponse httpServletResponseMock; + public JapHttpRequest request; + public JapHttpResponse response; + @Before public void init() { MockitoAnnotations.initMocks(this); + this.request = new JakartaRequestAdapter(httpServletRequestMock); + this.response = new JakartaResponseAdapter(httpServletResponseMock); } @Test @@ -71,7 +80,7 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - JapUser japUser = JapAuthentication.getContext().getUserStore().get(httpServletRequestMock, httpServletResponseMock); + JapUser japUser = JapAuthentication.getContext().getUserStore().get(request, response); Assert.assertNull(japUser); } @@ -83,8 +92,8 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - JapAuthentication.getContext().getUserStore().save(httpServletRequestMock, httpServletResponseMock, new JapUser()); - JapUser japUser = JapAuthentication.getContext().getUserStore().get(httpServletRequestMock, httpServletResponseMock); + JapAuthentication.getContext().getUserStore().save(request, response, new JapUser()); + JapUser japUser = JapAuthentication.getContext().getUserStore().get(request, response); Assert.assertNotNull(japUser); } @@ -96,7 +105,7 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - JapUser japUser = JapAuthentication.getUser(httpServletRequestMock, httpServletResponseMock); + JapUser japUser = JapAuthentication.getUser(request, response); Assert.assertNull(japUser); } @@ -108,8 +117,8 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - JapResponse response = JapAuthentication.checkUser(httpServletRequestMock, httpServletResponseMock); - Assert.assertEquals(response.getCode(), 401); + JapResponse japResponse = JapAuthentication.checkUser(request, response); + Assert.assertEquals(japResponse.getCode(), 401); } @Test @@ -120,9 +129,9 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - JapAuthentication.getContext().getUserStore().save(httpServletRequestMock, httpServletResponseMock, new JapUser()); - JapResponse response = JapAuthentication.checkUser(httpServletRequestMock, httpServletResponseMock); - Assert.assertEquals(response.getCode(), 200); + JapAuthentication.getContext().getUserStore().save(request, response, new JapUser()); + JapResponse japResponse = JapAuthentication.checkUser(request, response); + Assert.assertEquals(japResponse.getCode(), 200); } @Test @@ -195,7 +204,7 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - boolean result = JapAuthentication.logout(httpServletRequestMock, httpServletResponseMock); + boolean result = JapAuthentication.logout(request, response); Assert.assertTrue(result); } @@ -207,7 +216,7 @@ public class JapAuthenticationTest { JapContext japContext = new JapContext(japUserStore, japCache, japConfig); JapAuthentication.setContext(japContext); - boolean result = JapAuthentication.logout(httpServletRequestMock, httpServletResponseMock); + boolean result = JapAuthentication.logout(request, response); Assert.assertFalse(result); } @@ -224,7 +233,7 @@ public class JapAuthenticationTest { * @return JapUser */ @Override - public JapUser save(HttpServletRequest request, HttpServletResponse response, JapUser japUser) { + public JapUser save(JapHttpRequest request, JapHttpResponse response, JapUser japUser) { STORE.put(USER_KEY, japUser); return japUser; } @@ -236,7 +245,7 @@ public class JapAuthenticationTest { * @param response current HTTP response */ @Override - public void remove(HttpServletRequest request, HttpServletResponse response) { + public void remove(JapHttpRequest request, JapHttpResponse response) { STORE.remove(USER_KEY); } @@ -249,7 +258,7 @@ public class JapAuthenticationTest { * @return JapUser */ @Override - public JapUser get(HttpServletRequest request, HttpServletResponse response) { + public JapUser get(JapHttpRequest request, JapHttpResponse response) { return (JapUser) STORE.get(USER_KEY); } } diff --git a/jap-core/src/test/java/com/fujieid/jap/core/util/JapUtilTest.java b/jap-core/src/test/java/com/fujieid/jap/core/util/JapUtilTest.java index e0f607b..a248f07 100644 --- a/jap-core/src/test/java/com/fujieid/jap/core/util/JapUtilTest.java +++ b/jap-core/src/test/java/com/fujieid/jap/core/util/JapUtilTest.java @@ -16,6 +16,8 @@ package com.fujieid.jap.core.util; import com.fujieid.jap.core.JapUser; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -30,9 +32,12 @@ public class JapUtilTest { @Mock public HttpServletRequest httpServletRequestMock; + public JapHttpRequest request; + @Before public void init() { MockitoAnnotations.initMocks(this); + this.request = new JakartaRequestAdapter(httpServletRequestMock); } @Test @@ -41,7 +46,7 @@ public class JapUtilTest { when(httpServletRequestMock.getHeader("user-agent")).thenReturn("ua"); String token = JapUtil.createToken(new JapUser() .setUserId("11111") - .setUsername("username"), httpServletRequestMock); + .setUsername("username"), request); Assert.assertNotNull(token); } } diff --git a/jap-http-api/pom.xml b/jap-http-api/pom.xml new file mode 100644 index 0000000..92aa3de --- /dev/null +++ b/jap-http-api/pom.xml @@ -0,0 +1,29 @@ + + + + com.fujieid + jap + ${revision} + + 4.0.0 + + jap-http-api + jap-http-api + + jap-http-api + + + + + com.fujieid + jap-core + + + com.xkcoding.http + simple-http + + + + diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/HttpApiConfig.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/HttpApiConfig.java new file mode 100644 index 0000000..250a8d5 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/HttpApiConfig.java @@ -0,0 +1,228 @@ +package com.fujieid.jap.httpapi; + +import com.fujieid.jap.core.JapUser; +import com.fujieid.jap.core.config.AuthenticateConfig; +import com.fujieid.jap.httpapi.enums.AuthInfoFieldEnum; +import com.fujieid.jap.httpapi.enums.AuthSchemaEnum; +import com.fujieid.jap.httpapi.enums.ForBearerTokenEnum; +import com.fujieid.jap.httpapi.enums.HttpMethodEnum; +import com.fujieid.jap.httpapi.strategy.GetTokenFromResponseStrategy; +import com.fujieid.jap.httpapi.strategy.RequestBodyToJapUserStrategy; +import com.xkcoding.json.JsonUtil; + +import java.io.BufferedReader; +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration file for http api authorization module + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public class HttpApiConfig extends AuthenticateConfig { + + /** + * Schema for http authorization,defined by third-party system. eg:BASIC、DIGEST、BEARER. + */ + private AuthSchemaEnum authSchema; + + /** + * the http method for us to send request to third-party system, which is given by third-party system. + */ + private HttpMethodEnum httpMethod; + + /** + * the login url given by the third-party system. + */ + private String loginUrl; + + /** + * params/header/body + */ + private AuthInfoFieldEnum authInfoField; + + /** + * custom headers will be carried when request third-party system. + */ + private Map customHeaders; + + /** + * custom params will be carried when request third-party system. + */ + private Map customParams; + + /** + * custom body will be carried when request third-party system in json format. + */ + private Map customBody; + + /** + * define this field when authSchema is BEARER + */ + private String bearerTokenIssueUrl; + + /** + * if user auth info field is "body", by default, analyze user auth info by json format. ex: + * { + * "username":"admin", + * "password":"123456" + * } + * Developer's system should customize requestBodyToJapUserStrategy to get userinfo form request if user auth info is not this format. + */ + private RequestBodyToJapUserStrategy requestBodyToJapUserStrategy = request -> { + BufferedReader reader = request.getReader(); + StringBuilder body = new StringBuilder(); + String str = null; + while ((str = reader.readLine()) != null) { + body.append(str); + } + return JsonUtil.toBean(body.toString(), JapUser.class); + }; + + /** + * As we know Bearer auth need a token, but how to get this token? we should do a pre-auth to get this token, then store this token + * in developer's system. when user request for bearer auth, http-api module will query for user's token in our system. + * This field is used to define how to do pre-auth, how we get the token from third-party serve. + * When authSchema is BEARER you must define this field. + */ + private ForBearerTokenEnum forBearerTokenEnum; + + /** + * When do pre-auth for bearer auth, we get the response from third-party server which contains the token. + * Use this strategy to extract the token from response body. + * By default, search for field like "token":"xxxxxxxx" in response. + */ + private GetTokenFromResponseStrategy getTokenFromResponseStrategy = body -> { + try { + int i1 = body.indexOf("\"token\"") + 7; + int i2 = body.indexOf(':', i1); + int i3 = body.indexOf("\"", i2) + 1; + int i4 = body.indexOf("\"", i3 + 1); + return body.substring(i3, i4); + } catch (Exception e) { + return null; + } + }; + + public HttpApiConfig() { + } + + public HttpApiConfig(HttpApiConfig httpApiConfig, String loginUrl) { + this.authSchema = httpApiConfig.authSchema; + this.httpMethod = httpApiConfig.httpMethod; + this.loginUrl = loginUrl; + this.authInfoField = httpApiConfig.authInfoField; + this.customHeaders = httpApiConfig.customHeaders; + this.customParams = httpApiConfig.customParams; + this.customBody = httpApiConfig.customBody; + this.bearerTokenIssueUrl = httpApiConfig.bearerTokenIssueUrl; + this.requestBodyToJapUserStrategy = httpApiConfig.requestBodyToJapUserStrategy; + this.forBearerTokenEnum = httpApiConfig.forBearerTokenEnum; + this.getTokenFromResponseStrategy = httpApiConfig.getTokenFromResponseStrategy; + } + + public AuthSchemaEnum getAuthSchema() { + return authSchema; + } + + public HttpApiConfig setAuthSchema(AuthSchemaEnum authSchema) { + this.authSchema = authSchema; + return this; + } + + public HttpMethodEnum getHttpMethod() { + return httpMethod; + } + + public HttpApiConfig setHttpMethod(HttpMethodEnum httpMethod) { + this.httpMethod = httpMethod; + return this; + } + + public String getLoginUrl() { + return loginUrl; + } + + public HttpApiConfig setLoginUrl(String loginUrl) { + this.loginUrl = loginUrl; + return this; + } + + public AuthInfoFieldEnum getAuthInfoField() { + return authInfoField; + } + + public HttpApiConfig setAuthInfoField(AuthInfoFieldEnum authInfoField) { + this.authInfoField = authInfoField; + return this; + } + + public Map getCustomHeaders() { + return customHeaders; + } + + public HttpApiConfig setCustomHeaders(Map customHeaders) { + this.customHeaders = customHeaders; + return this; + } + + public RequestBodyToJapUserStrategy getRequestBodyToJapUserStrategy() { + return requestBodyToJapUserStrategy; + } + + public HttpApiConfig setRequestBodyToJapUserStrategy(RequestBodyToJapUserStrategy requestBodyToJapUserStrategy) { + this.requestBodyToJapUserStrategy = requestBodyToJapUserStrategy; + return this; + } + + public Map getCustomParams() { + return customParams; + } + + public HttpApiConfig setCustomParams(Map customParams) { + this.customParams = customParams; + return this; + } + + public Map getCustomParamsObjects() { + return new HashMap<>(customParams); + } + + public Map getCustomBody() { + return customBody; + } + + public HttpApiConfig setCustomBody(Map customBody) { + this.customBody = customBody; + return this; + } + + public ForBearerTokenEnum getForBearerTokenEnum() { + return forBearerTokenEnum; + } + + public HttpApiConfig setForBearerTokenEnum(ForBearerTokenEnum forBearerTokenEnum) { + this.forBearerTokenEnum = forBearerTokenEnum; + return this; + } + + public GetTokenFromResponseStrategy getGetTokenFromResponseStrategy() { + return getTokenFromResponseStrategy; + } + + public HttpApiConfig setGetTokenFromResponseStrategy(GetTokenFromResponseStrategy getTokenFromResponseStrategy) { + this.getTokenFromResponseStrategy = getTokenFromResponseStrategy; + return this; + } + + public String getBearerTokenIssueUrl() { + return bearerTokenIssueUrl; + } + + public HttpApiConfig setBearerTokenIssueUrl(String bearerTokenIssueUrl) { + this.bearerTokenIssueUrl = bearerTokenIssueUrl; + return this; + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/HttpApiStrategy.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/HttpApiStrategy.java new file mode 100644 index 0000000..b0296b6 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/HttpApiStrategy.java @@ -0,0 +1,335 @@ +package com.fujieid.jap.httpapi; + +import cn.hutool.core.codec.Base64; +import com.fujieid.jap.core.JapUser; +import com.fujieid.jap.core.JapUserService; +import com.fujieid.jap.core.cache.JapCache; +import com.fujieid.jap.core.config.AuthenticateConfig; +import com.fujieid.jap.core.config.JapConfig; +import com.fujieid.jap.core.exception.JapException; +import com.fujieid.jap.core.result.JapErrorCode; +import com.fujieid.jap.core.result.JapResponse; +import com.fujieid.jap.core.strategy.AbstractJapStrategy; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.httpapi.enums.AuthSchemaEnum; +import com.fujieid.jap.httpapi.enums.HttpMethodEnum; +import com.fujieid.jap.httpapi.subject.DigestAuthorizationSubject; +import com.fujieid.jap.httpapi.subject.DigestWwwAuthenticateSubject; +import com.fujieid.jap.httpapi.subject.HttpAuthResponse; +import com.fujieid.jap.httpapi.util.*; + +/** + * The strategy for jap-http-api module to request third party system. + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public class HttpApiStrategy extends AbstractJapStrategy { + + + public HttpApiStrategy(JapUserService japUserService, JapConfig japConfig, JapCache japCache) { + super(japUserService, japConfig, japCache); + } + + @Override + public JapResponse authenticate(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { + try { + checkAuthenticateConfig(config, HttpApiConfig.class); + } catch (JapException e) { + e.printStackTrace(); + return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); + } + HttpApiConfig httpApiConfig = (HttpApiConfig) config; + + return this.doAuthenticate(httpApiConfig, request, response); + } + + private JapResponse doAuthenticate(HttpApiConfig config, JapHttpRequest request, JapHttpResponse response) { + + HttpAuthResponse authResponse = null; + JapUser japUser = null; + + try { + japUser = getJapUser(request, config); + + if (japUser == null) { + return JapResponse.error(JapErrorCode.MISS_CREDENTIALS); + } + + switch (config.getAuthSchema()) { + case BASIC: + authResponse = doBasicAuth(japUser, config, response); + break; + case DIGEST: + authResponse = doDigestAuth(japUser, config, response); + break; + case BEARER: + authResponse = doBearerAuth(japUser, config, response); + break; + default: + break; + } + } catch (JapException e) { + return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + + if (authResponse != null && authResponse.isSuccess()) { + if (config.getAuthSchema() == AuthSchemaEnum.BASIC || config.getAuthSchema() == AuthSchemaEnum.DIGEST) { + japUserService.createAndGetHttpApiUser(japUser); + } + return JapResponse.success(authResponse.getBody()); + } else { + return JapResponse.error(JapErrorCode.INVALID_PASSWORD); + } + } + + private HttpAuthResponse doDigestAuth(JapUser japUser, HttpApiConfig config, JapHttpResponse response) { + + /* + * send a request to third-party server to get a random number and encryption algorithm + * see: https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.1 + */ + HttpAuthResponse responseForWWWAuth = HttpAuthUtil.sendRequest(config.getLoginUrl(), + HttpMethodEnum.GET, + config.getCustomHeaders(), + config.getCustomParams(), + SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody())); + + if (responseForWWWAuth == null || responseForWWWAuth.getHeaders() == null) { + return null; + } + HttpAuthResponse result = null; + String wwwAuthenticate = null; + + try { + wwwAuthenticate = responseForWWWAuth.getHeaders().get("WWW-Authenticate").get(0).replaceFirst("Digest ", ""); + DigestWwwAuthenticateSubject wwwAuthenticateSubject = SubjectSerializeUtil.deserialize(wwwAuthenticate, DigestWwwAuthenticateSubject.class); + + String username = japUser.getUsername(); + String realm = wwwAuthenticateSubject.getRealm(); + String password = japUser.getPassword(); + String method = config.getHttpMethod().toString().toUpperCase(); + String digestUri = URLUtil.getRelativeUri(config.getLoginUrl()); + String nonce = wwwAuthenticateSubject.getNonce(); + String qop = wwwAuthenticateSubject.getQop(); + String nc = DigestUtil.getNc(); + String cnonce = DigestUtil.getCnonce(); + String opaque = wwwAuthenticateSubject.getOpaque(); + + String ha1 = DigestMD5Util.encode(username, realm, password); + String ha2 = DigestUtil.getHa2ByQop(qop, method, digestUri); + String qopResponse = DigestUtil.getResponseByQop(ha1, nonce, nc, cnonce, qop, ha2); + + DigestAuthorizationSubject authorizationSubject = new DigestAuthorizationSubject() + .setCnonce(cnonce) + .setQop(qop) + .setNc(nc) + .setNonce(nonce) + .setRealm(realm) + .setUsername(username) + .setUri(digestUri) + .setResponse(qopResponse) + .setAlgorithm(wwwAuthenticateSubject.getAlgorithm()) + .setOpaque(opaque); + + String authStr = SubjectSerializeUtil.serialize(authorizationSubject); + + // send authorization request + config.getCustomHeaders().put("Authorization", "Digest " + authStr); + result = HttpAuthUtil.sendRequest(config.getLoginUrl(), + config.getHttpMethod(), + config.getCustomHeaders(), + config.getCustomParams(), + SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody())); + return result; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private HttpAuthResponse doBasicAuth(JapUser japUser, HttpApiConfig config, JapHttpResponse response) { + + /* + * see: The 'Basic' HTTP Authentication Scheme:https://datatracker.ietf.org/doc/html/rfc7617 + */ + String basicAuth = "Basic " + Base64.encode(japUser.getUsername() + ":" + japUser.getPassword()); + config.getCustomHeaders().put("authorization", basicAuth); + + return HttpAuthUtil.sendRequest(config.getLoginUrl(), + config.getHttpMethod(), + config.getCustomHeaders(), + config.getCustomParams(), + SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody())); + } + + private HttpAuthResponse sendBearerTokenAuthRequest(String token, HttpApiConfig config) { + String bearerToken = "Bearer " + token; + config.getCustomHeaders().put("Authorization", bearerToken); + return HttpAuthUtil.sendRequest(config.getLoginUrl(), + config.getHttpMethod(), + config.getCustomHeaders(), + config.getCustomParams(), + SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody())); + } + + private HttpAuthResponse doBearerAuth(JapUser japUser, HttpApiConfig config, JapHttpResponse response) { + + JapUser japUserInDb = japUserService.getByName(japUser.getUsername()); + String token = null; + if (japUserInDb == null || japUserInDb.getToken() == null) { + token = doPreAuthForBearerToken(japUser, config, response); + } else { + token = japUserInDb.getToken(); + } + + HttpAuthResponse result = sendBearerTokenAuthRequest(token, config); + + // old token expired, request for new token + if (result == null || !result.isSuccess()) { + String newToken = doPreAuthForBearerToken(japUser, config, response); + result = sendBearerTokenAuthRequest(newToken, config); + } + return result; + } + + /** + * do a pre-auth in Bearer auth to get token for this user + * + * @param japUser jap user + * @param config Http api config + * @param response current http response + */ + private String doPreAuthForBearerToken(JapUser japUser, HttpApiConfig config, JapHttpResponse response) { + HttpAuthResponse httpAuthResponse = null; + // clean old auth token + config.getCustomHeaders().remove("Authorization"); + config.getCustomHeaders().remove("authorization"); + switch (config.getForBearerTokenEnum()) { + case BY_BASIC: + httpAuthResponse = this.doBasicAuth(japUser, new HttpApiConfig(config, config.getBearerTokenIssueUrl()), response); + break; + case BY_DIGEST: + httpAuthResponse = this.doDigestAuth(japUser, new HttpApiConfig(config, config.getBearerTokenIssueUrl()), response); + break; + case BY_HEADER: + config.getCustomHeaders().put("username", japUser.getUsername()); + config.getCustomHeaders().put("password", japUser.getPassword()); + httpAuthResponse = HttpAuthUtil.sendRequest(config.getLoginUrl(), + config.getHttpMethod(), + config.getCustomHeaders(), + config.getCustomParams(), + SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody())); + break; + case BY_PARAMS: + config.getCustomParams().put("username", japUser.getUsername()); + config.getCustomParams().put("password", japUser.getPassword()); + httpAuthResponse = HttpAuthUtil.sendRequest(config.getLoginUrl(), + config.getHttpMethod(), + config.getCustomHeaders(), + config.getCustomParams(), + SimpleAuthJsonUtil.getJsonStrByParams(config.getCustomBody())); + break; + case BY_BODY: + String body = SimpleAuthJsonUtil.getJsonStrByJapUserAndParams(japUser, config.getCustomBody()); + httpAuthResponse = HttpAuthUtil.sendRequest(config.getLoginUrl(), + config.getHttpMethod(), + config.getCustomHeaders(), + config.getCustomParams(), + body); + break; + default: + break; + } + + // pre-auth field + if (httpAuthResponse == null || !httpAuthResponse.isSuccess()) { + return null; + } + + // get token from httpAuthResponse body + String token = config.getGetTokenFromResponseStrategy().getToken(httpAuthResponse.getBody()); + if (token == null || token.length() == 0) { + return null; + } + + // save jap user to db + japUser.setToken(token); + japUserService.createAndGetHttpApiUser(japUser); + return token; + } + + /** + * get user information from request according to http api config. + * + * @param request current http request + * @param config http api config + * @return jap user contains username and password + */ + public JapUser getJapUser(JapHttpRequest request, HttpApiConfig config) { + JapUser japUser = null; + String username = null; + String password = null; + + switch (config.getAuthInfoField()) { + case PARAMS: + username = request.getParameter("username"); + password = request.getParameter("password"); + japUser = new JapUser().setUsername(username).setPassword(password); + break; + + case HEADER: + username = request.getHeader("username"); + password = request.getHeader("password"); + japUser = new JapUser().setUsername(username).setPassword(password); + break; + + case BODY: + try { + // get auth info body from request body. + japUser = config.getRequestBodyToJapUserStrategy().decode(request); + } catch (Exception e) { + e.printStackTrace(); + } + break; + default: + break; + } + if (null == japUser || null == japUser.getUsername()) { + return null; + } + + return japUser; + } + + + /** + * check http api config. + * NOTICE: + * 1. When use "GET" as request method, custom body is not supported. + * 2. Can not custom body and params at the same time. + * + * @param sourceConfig The parameters passed in by the caller + * @param targetConfigClazz The actual parameter class type + * @throws JapException Jap exception will be thrown when http api config configuration error + */ + @Override + protected void checkAuthenticateConfig(AuthenticateConfig sourceConfig, Class targetConfigClazz) throws JapException { + super.checkAuthenticateConfig(sourceConfig, targetConfigClazz); + HttpApiConfig config = (HttpApiConfig) sourceConfig; + if (config == null || + config.getAuthSchema() == null || + config.getHttpMethod() == null || + config.getLoginUrl() == null || + config.getAuthInfoField() == null || + (config.getHttpMethod() == HttpMethodEnum.GET && (config.getCustomBody() != null && config.getCustomBody().size() != 0)) || + ((config.getCustomBody() != null && config.getCustomBody().size() != 0) && (config.getCustomParams() != null && config.getCustomParams().size() != 0)) + ) { + throw new JapException(JapErrorCode.ERROR_HTTP_API_CONFIG); + } + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/AuthInfoFieldEnum.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/AuthInfoFieldEnum.java new file mode 100644 index 0000000..4b55c3e --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/AuthInfoFieldEnum.java @@ -0,0 +1,12 @@ +package com.fujieid.jap.httpapi.enums; + +/** + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public enum AuthInfoFieldEnum { + HEADER, + PARAMS, + BODY +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/AuthSchemaEnum.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/AuthSchemaEnum.java new file mode 100644 index 0000000..7d63b61 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/AuthSchemaEnum.java @@ -0,0 +1,12 @@ +package com.fujieid.jap.httpapi.enums; + +/** + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public enum AuthSchemaEnum { + BASIC, + DIGEST, + BEARER +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/ForBearerTokenEnum.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/ForBearerTokenEnum.java new file mode 100644 index 0000000..9c1d92e --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/ForBearerTokenEnum.java @@ -0,0 +1,14 @@ +package com.fujieid.jap.httpapi.enums; + +/** + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public enum ForBearerTokenEnum { + BY_HEADER, + BY_PARAMS, + BY_BODY, + BY_BASIC, + BY_DIGEST +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/HttpMethodEnum.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/HttpMethodEnum.java new file mode 100644 index 0000000..ebbde32 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/enums/HttpMethodEnum.java @@ -0,0 +1,11 @@ +package com.fujieid.jap.httpapi.enums; + +/** + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public enum HttpMethodEnum { + POST, + GET +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/strategy/GetTokenFromResponseStrategy.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/strategy/GetTokenFromResponseStrategy.java new file mode 100644 index 0000000..1e31e6c --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/strategy/GetTokenFromResponseStrategy.java @@ -0,0 +1,20 @@ +package com.fujieid.jap.httpapi.strategy; + +/** + * Define how to get token from the third-party server's response when request to issue a bearer token + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +@FunctionalInterface +public interface GetTokenFromResponseStrategy { + + /** + * get token from body + * + * @param body response body + * @return token + */ + String getToken(String body); +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/strategy/RequestBodyToJapUserStrategy.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/strategy/RequestBodyToJapUserStrategy.java new file mode 100644 index 0000000..43feefc --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/strategy/RequestBodyToJapUserStrategy.java @@ -0,0 +1,23 @@ +package com.fujieid.jap.httpapi.strategy; + +import com.fujieid.jap.core.JapUser; +import com.fujieid.jap.http.JapHttpRequest; + +/** + * Implement this functional interface if your auth info in the request body is not standard json format. + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +@FunctionalInterface +public interface RequestBodyToJapUserStrategy { + /** + * get a JapUser from request + * + * @param request request + * @return JapUser + * @throws Exception When the data in the request cannot be decoded, an exception will be thrown + */ + JapUser decode(JapHttpRequest request) throws Exception; +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/DigestAuthorizationSubject.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/DigestAuthorizationSubject.java new file mode 100644 index 0000000..aa1c151 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/DigestAuthorizationSubject.java @@ -0,0 +1,133 @@ +package com.fujieid.jap.httpapi.subject; + +/** + * The Authorization Request Header + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @see https://en.wikipedia.org/wiki/Digest_access_authentication + * @see https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.2 + * @since 1.0.5 + */ +public class DigestAuthorizationSubject { + private String username; + private String realm; + private String nonce; + private String uri; + private String response; + private String qop; + private String nc; + private String cnonce; + private String pop; + private String opaque; + private String digest; + private String algorithm; + + public String getQop() { + return qop; + } + + public DigestAuthorizationSubject setQop(String qop) { + this.qop = qop; + return this; + } + + public String getUsername() { + return username; + } + + public DigestAuthorizationSubject setUsername(String username) { + this.username = username; + return this; + } + + public String getRealm() { + return realm; + } + + public DigestAuthorizationSubject setRealm(String realm) { + this.realm = realm; + return this; + } + + public String getNonce() { + return nonce; + } + + public DigestAuthorizationSubject setNonce(String nonce) { + this.nonce = nonce; + return this; + } + + public String getUri() { + return uri; + } + + public DigestAuthorizationSubject setUri(String uri) { + this.uri = uri; + return this; + } + + public String getPop() { + return pop; + } + + public DigestAuthorizationSubject setPop(String pop) { + this.pop = pop; + return this; + } + + public String getNc() { + return nc; + } + + public DigestAuthorizationSubject setNc(String nc) { + this.nc = nc; + return this; + } + + public String getCnonce() { + return cnonce; + } + + public DigestAuthorizationSubject setCnonce(String cnonce) { + this.cnonce = cnonce; + return this; + } + + public String getResponse() { + return response; + } + + public DigestAuthorizationSubject setResponse(String response) { + this.response = response; + return this; + } + + public String getOpaque() { + return opaque; + } + + public DigestAuthorizationSubject setOpaque(String opaque) { + this.opaque = opaque; + return this; + } + + public String getDigest() { + return digest; + } + + public DigestAuthorizationSubject setDigest(String digest) { + this.digest = digest; + return this; + } + + public String getAlgorithm() { + return algorithm; + } + + public DigestAuthorizationSubject setAlgorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/DigestWwwAuthenticateSubject.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/DigestWwwAuthenticateSubject.java new file mode 100644 index 0000000..2661408 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/DigestWwwAuthenticateSubject.java @@ -0,0 +1,85 @@ +package com.fujieid.jap.httpapi.subject; + +/** + * The WWW-Authenticate Response Header + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @see https://datatracker.ietf.org/doc/html/rfc2069#section-2.1.1 + * @since 1.0.5 + */ +public class DigestWwwAuthenticateSubject { + private String realm; + private String domain; + private String nonce; + private String opaque; + private String algorithm; + private String stale; + private String qop; + + public DigestWwwAuthenticateSubject() { + } + + public String getQop() { + return qop; + } + + public DigestWwwAuthenticateSubject setQop(String qop) { + this.qop = qop; + return this; + } + + public String getRealm() { + return realm; + } + + public DigestWwwAuthenticateSubject setRealm(String realm) { + this.realm = realm; + return this; + } + + public String getDomain() { + return domain; + } + + public DigestWwwAuthenticateSubject setDomain(String domain) { + this.domain = domain; + return this; + } + + public String getNonce() { + return nonce; + } + + public DigestWwwAuthenticateSubject setNonce(String nonce) { + this.nonce = nonce; + return this; + } + + public String getOpaque() { + return opaque; + } + + public DigestWwwAuthenticateSubject setOpaque(String opaque) { + this.opaque = opaque; + return this; + } + + public String getAlgorithm() { + return algorithm; + } + + public DigestWwwAuthenticateSubject setAlgorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + public String getStale() { + return stale; + } + + public DigestWwwAuthenticateSubject setStale(String stale) { + this.stale = stale; + return this; + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/HttpAuthResponse.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/HttpAuthResponse.java new file mode 100644 index 0000000..3e7391d --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/subject/HttpAuthResponse.java @@ -0,0 +1,50 @@ +package com.fujieid.jap.httpapi.subject; + +import java.util.List; +import java.util.Map; + +/** + * packaging auth result + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public class HttpAuthResponse { + private boolean success; + private String body; + private Map> headers; + + public HttpAuthResponse(boolean success, String body, Map> headers) { + this.success = success; + this.body = body; + this.headers = headers; + } + + public boolean isSuccess() { + return success; + } + + public HttpAuthResponse setSuccess(boolean success) { + this.success = success; + return this; + } + + public String getBody() { + return body; + } + + public HttpAuthResponse setBody(String body) { + this.body = body; + return this; + } + + public Map> getHeaders() { + return headers; + } + + public HttpAuthResponse setHeaders(Map> headers) { + this.headers = headers; + return this; + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestMD5Util.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestMD5Util.java new file mode 100644 index 0000000..0dc2f9c --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestMD5Util.java @@ -0,0 +1,62 @@ +package com.fujieid.jap.httpapi.util; + +import com.fujieid.jap.core.exception.JapException; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Splice strings by ":", and encode using md5. eg:"MD5(MD5(username:realm:password):nonce:cnonce)" + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public final class DigestMD5Util { + + private final static String[] HEX_ARRAY = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; + + public static String encode(String... strs) { + StringBuilder stringBuffer = new StringBuilder(); + boolean isFirst = true; + for (String str : strs) { + if (isFirst) { + stringBuffer.append(str); + isFirst = false; + } else { + stringBuffer.append(":").append(str); + } + } + + // Create an information summary with MD5 algorithm + MessageDigest md = null; + String algorithm = "MD5"; + try { + md = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + throw new JapException(algorithm + " not found"); + } + byte[] bytes = md.digest(stringBuffer.toString().getBytes()); + return byteArrayToHex(bytes); + + } + + private static String byteArrayToHex(byte[] b) { + StringBuilder sb = new StringBuilder(); + for (byte value : b) { + sb.append(byteToHex(value)); + } + return sb.toString(); + } + + public static String byteToHex(byte b) { + int n = b; + if (n < 0) { + n = n + 256; + } + int d1 = n / 16; + int d2 = n % 16; + return HEX_ARRAY[d1] + HEX_ARRAY[d2]; + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestSha256Util.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestSha256Util.java new file mode 100644 index 0000000..936f15b --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestSha256Util.java @@ -0,0 +1,164 @@ +package com.fujieid.jap.httpapi.util; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +/** + * refer: https://github.com/meyfa/java-sha256/blob/master/src/main/java/net/meyfa/sha256/Sha256.java + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public class DigestSha256Util { + private static final int[] K = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + }; + + private static final int[] H0 = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + }; + + private static final int BLOCK_BITS = 512; + private static final int BLOCK_BYTES = BLOCK_BITS / 8; + + // working arrays + private static final int[] W = new int[64]; + private static final int[] H = new int[8]; + private static final int[] TEMP = new int[8]; + + /** + * Hashes the given message with SHA-256 and returns the hash. + * + * @param message The bytes to hash. + * @return The hash's bytes. + */ + public static byte[] hash(byte[] message) { + // let H = H0 + System.arraycopy(H0, 0, H, 0, H0.length); + + // initialize all words + int[] words = pad(message); + + // enumerate all blocks (each containing 16 words) + for (int i = 0, n = words.length / 16; i < n; ++i) { + + // initialize W from the block's words + System.arraycopy(words, i * 16, W, 0, 16); + for (int t = 16; t < W.length; ++t) { + W[t] = smallSig1(W[t - 2]) + W[t - 7] + smallSig0(W[t - 15]) + W[t - 16]; + } + + // let TEMP = H + System.arraycopy(H, 0, TEMP, 0, H.length); + + // operate on TEMP + for (int t = 0; t < W.length; ++t) { + int t1 = TEMP[7] + bigSig1(TEMP[4]) + ch(TEMP[4], TEMP[5], TEMP[6]) + K[t] + W[t]; + int t2 = bigSig0(TEMP[0]) + maj(TEMP[0], TEMP[1], TEMP[2]); + System.arraycopy(TEMP, 0, TEMP, 1, TEMP.length - 1); + TEMP[4] += t1; + TEMP[0] = t1 + t2; + } + + // add values in TEMP to values in H + for (int t = 0; t < H.length; ++t) { + H[t] += TEMP[t]; + } + + } + + return toByteArray(H); + } + + /** + * Internal method, no need to call. Pads the given message to have a length + * that is a multiple of 512 bits (64 bytes), including the addition of a + * 1-bit, k 0-bits, and the message length as a 64-bit integer. + * The result is a 32-bit integer array with big-endian byte representation. + * + * @param message The message to pad. + * @return A new array with the padded message bytes. + */ + public static int[] pad(byte[] message) { + // new message length: original + 1-bit and padding + 8-byte length + // --> block count: whole blocks + (padding + length rounded up) + int finalBlockLength = message.length % BLOCK_BYTES; + int blockCount = message.length / BLOCK_BYTES + (finalBlockLength + 1 + 8 > BLOCK_BYTES ? 2 : 1); + + final IntBuffer result = IntBuffer.allocate(blockCount * (BLOCK_BYTES / Integer.BYTES)); + + // copy as much of the message as possible + ByteBuffer buf = ByteBuffer.wrap(message); + for (int i = 0, n = message.length / Integer.BYTES; i < n; ++i) { + result.put(buf.getInt()); + } + // copy the remaining bytes (less than 4) and append 1 bit (rest is zero) + ByteBuffer remainder = ByteBuffer.allocate(4); + remainder.put(buf).put((byte) 0b10000000).rewind(); + result.put(remainder.getInt()); + + // ignore however many pad bytes (implicitly calculated in the beginning) + result.position(result.capacity() - 2); + // place original message length as 64-bit integer at the end + long msgLength = message.length * 8L; + result.put((int) (msgLength >>> 32)); + result.put((int) msgLength); + + return result.array(); + } + + /** + * Converts the given int array into a byte array via big-endian conversion + * (1 int becomes 4 bytes). + * + * @param ints The source array. + * @return The converted array. + */ + private static byte[] toByteArray(int[] ints) { + ByteBuffer buf = ByteBuffer.allocate(ints.length * Integer.BYTES); + for (int i : ints) { + buf.putInt(i); + } + return buf.array(); + } + + private static int ch(int x, int y, int z) { + return (x & y) | ((~x) & z); + } + + private static int maj(int x, int y, int z) { + return (x & y) | (x & z) | (y & z); + } + + private static int bigSig0(int x) { + return Integer.rotateRight(x, 2) + ^ Integer.rotateRight(x, 13) + ^ Integer.rotateRight(x, 22); + } + + private static int bigSig1(int x) { + return Integer.rotateRight(x, 6) + ^ Integer.rotateRight(x, 11) + ^ Integer.rotateRight(x, 25); + } + + private static int smallSig0(int x) { + return Integer.rotateRight(x, 7) + ^ Integer.rotateRight(x, 18) + ^ (x >>> 3); + } + + private static int smallSig1(int x) { + return Integer.rotateRight(x, 17) + ^ Integer.rotateRight(x, 19) + ^ (x >>> 10); + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestUtil.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestUtil.java new file mode 100644 index 0000000..d40b0e4 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/DigestUtil.java @@ -0,0 +1,42 @@ +package com.fujieid.jap.httpapi.util; + + +import cn.hutool.core.lang.UUID; + +import java.util.concurrent.atomic.LongAdder; + +/** + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public final class DigestUtil { + + private static final LongAdder LONG_ADDER = new LongAdder(); + + public static String getCnonce() { + return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 16); + } + + public static String getNc() { + LONG_ADDER.increment(); + return LONG_ADDER.toString(); + } + + public static String getResponseByQop(String ha1, String nonce, String nc, String cnonce, String qop, String ha2) { + if (qop != null && (qop.contains("auth") || qop.contains("auth-init"))) { + return DigestMD5Util.encode(ha1, nonce, nc, cnonce, qop, ha2); + } else { + return DigestMD5Util.encode(ha1, nonce, ha2); + } + } + + public static String getHa2ByQop(String qop, String method, String digestUri) { + if (qop == null || !qop.contains("auth-init")) { + return DigestMD5Util.encode(method, digestUri); + } else { + // TODO Support "auth-init" ha2 compute method + return null; + } + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/HttpAuthUtil.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/HttpAuthUtil.java new file mode 100644 index 0000000..61e96cd --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/HttpAuthUtil.java @@ -0,0 +1,47 @@ +package com.fujieid.jap.httpapi.util; + +import com.fujieid.jap.httpapi.enums.HttpMethodEnum; +import com.fujieid.jap.httpapi.subject.HttpAuthResponse; +import com.xkcoding.http.HttpUtil; +import com.xkcoding.http.support.HttpHeader; +import com.xkcoding.http.support.SimpleHttpResponse; + +import java.util.List; +import java.util.Map; + +/** + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public final class HttpAuthUtil { + + public static HttpAuthResponse sendRequest(String url, + HttpMethodEnum method, + Map header, + Map params, + String body) { + SimpleHttpResponse response = null; + switch (method) { + case GET: + response = HttpUtil.get(url, params, new HttpHeader(header), false); + break; + case POST: + if (body == null || "".equals(body)) { + response = HttpUtil.post(url, params, new HttpHeader(header), false); + } else { + response = HttpUtil.post(url, body, new HttpHeader(header)); + } + break; + default: + break; + } + if (response == null) { + return null; + } + String responseBody = response.getBody(); + Map> headers = response.getHeaders(); + return new HttpAuthResponse(response.isSuccess(), responseBody, headers); + } +} + diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/SimpleAuthJsonUtil.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/SimpleAuthJsonUtil.java new file mode 100644 index 0000000..759e552 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/SimpleAuthJsonUtil.java @@ -0,0 +1,42 @@ +package com.fujieid.jap.httpapi.util; + +import com.fujieid.jap.core.JapUser; + +import java.util.Map; + +/** + * Convert between json and auth info + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public final class SimpleAuthJsonUtil { + + public static String getJsonStrByParams(Map params) { + return getJsonStrByJapUserAndParams(null, params); + } + + public static String getJsonStrByJapUserAndParams(JapUser japUser, Map params) { + if (japUser == null && params == null) { + return null; + } + StringBuilder str = new StringBuilder(); + str.append("{"); + + if (japUser != null) { + str.append("\"username\":\"").append(japUser.getUsername()).append("\",\"password:\"").append(japUser.getPassword()).append("\","); + } + + if (params != null) { + params.forEach((key, value) -> str.append("\"").append(key).append("\":\"").append(value).append("\",")); + } + + if (str.charAt(str.length() - 1) == ',') { + str.deleteCharAt(str.length() - 1); + } + + str.append("}"); + return str.toString(); + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/SubjectSerializeUtil.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/SubjectSerializeUtil.java new file mode 100644 index 0000000..5880f16 --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/SubjectSerializeUtil.java @@ -0,0 +1,80 @@ +package com.fujieid.jap.httpapi.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Objects; + +/** + * used to convert "Authorization Request Header" and "WWW-Authenticate Response Header" between string and object + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public final class SubjectSerializeUtil { + + /** + * Deserialize string to object + * + * @param str a String to be deserialized + * @param clazz The target type to deserialize + * @param Actual class type + * @return Return an object + * @throws NoSuchMethodException no such method + * @throws IllegalAccessException illegal access + * @throws InvocationTargetException invocation target + * @throws InstantiationException instantiation + */ + public static T deserialize(String str, Class clazz) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + str = str.replace("\\", "").replace("\"", "").trim(); + String[] split = str.split(","); + HashMap params = new HashMap<>(8); + for (String s : split) { + String[] param = s.split("="); + String paramKey = param[0].trim(); + String paramValue = param[1].trim(); + params.put(paramKey, paramValue); + } + + Field[] fields = clazz.getDeclaredFields(); + + T t = clazz.getDeclaredConstructor().newInstance(); + + for (Field field : fields) { + field.setAccessible(true); + String name = field.getName(); + field.set(t, params.get(name)); + } + + return t; + } + + /** + * Serialize object to string + * + * @param t Objects that need to be serialized + * @return return a string after serialization + * @throws IllegalAccessException illegal access + */ + public static String serialize(Object t) throws IllegalAccessException { + Class clazz = t.getClass(); + StringBuilder sb = new StringBuilder(); + Field[] fields = clazz.getDeclaredFields(); + boolean firstParam = true; + for (Field field : fields) { + field.setAccessible(true); + String paramName = field.getName(); + String paramVal = ((String) field.get(t)); + if (Objects.isNull(paramVal)) { + continue; + } + if (!firstParam) { + sb.append(","); + } + firstParam = false; + sb.append(paramName).append("=\"").append(paramVal).append("\""); + } + return sb.toString(); + } +} diff --git a/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/URLUtil.java b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/URLUtil.java new file mode 100644 index 0000000..a287acc --- /dev/null +++ b/jap-http-api/src/main/java/com/fujieid/jap/httpapi/util/URLUtil.java @@ -0,0 +1,41 @@ +package com.fujieid.jap.httpapi.util; + +/** + * URLUtil + * + * @author zhihai.yu (mvbbb(a)foxmail.com) + * @version 1.0.0 + * @since 1.0.5 + */ +public class URLUtil { + + private static final String HTTPS_PREFIX = "https://"; + private static final String HTTP_PREFIX = "http://"; + + /** + * Get relative uri from uri. + * for example: + * + * + * https://www.justauth.plus return to www.justauth.plus + * + * + * @param uri URI with protocol. For example: https://www.justauth.plus + * @return Return only the domain name part in the uri + */ + public static String getRelativeUri(String uri) { + if (uri == null) { + return null; + } + String relativeUri = null; + if (uri.startsWith(HTTPS_PREFIX)) { + relativeUri = uri.replaceFirst(HTTPS_PREFIX, ""); + } else if (uri.startsWith(HTTP_PREFIX)) { + relativeUri = uri.replaceFirst(HTTP_PREFIX, ""); + } else { + relativeUri = uri; + } + relativeUri = relativeUri.substring(relativeUri.indexOf('/')); + return relativeUri; + } +} diff --git a/jap-ids-web/README.md b/jap-ids-web/README.md new file mode 100644 index 0000000..8b9a3cf --- /dev/null +++ b/jap-ids-web/README.md @@ -0,0 +1,67 @@ +> 本项目主要为了配合 `jap-ids` 使用,内置了 `IdsAccessTokenFilter`(Access Token 验权过滤器) 和 `IdsUserStatusFilter`(用户登录状态过滤器) +> 如果不需要该项目中的过滤器,开发者可以自己实现。 + +完整示例代码参考:[https://gitee.com/fujieid/jap-ids-demo](https://gitee.com/fujieid/jap-ids-demo) + + +## 配置过滤器 + +`jap-ids` 默认提供了两类过滤器: +- Access Token 验权过滤器 +- 用户登录状态过滤器 + +以本项目为例,配置以下两个过滤器: + +### Access Token 验权过滤器 +```java +@Bean +public FilterRegistrationBean registeraccessTokenFilter() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new IdsAccessTokenFilter()); + registration.addUrlPatterns("/*"); + registration.addInitParameter("ignoreUrl", + "/," + + "/oauth/login," + + "/oauth/error," + + "/oauth/confirm," + + "/oauth/authorize," + + "/oauth/token," + + "/oauth/check_session," + + "/oauth/registration," + + "/.well-known/jwks.json," + + "/.well-known/openid-configuration" + ); + registration.setName("IdsAccessTokenFilter"); + registration.setOrder(1); + return registration; +} +``` + +### 用户登录状态过滤器 +```java +@Bean +public FilterRegistrationBean registerUserStatusFilter() { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(new IdsUserStatusFilter()); + registration.addUrlPatterns("/*"); + registration.addInitParameter("ignoreUrl", + "/," + + "/oauth/login," + + "/oauth/error," + + "/oauth/confirm," + + "/oauth/authorize," + + "/oauth/token," + + "/oauth/check_session," + + "/oauth/registration," + + "/.well-known/jwks.json," + + "/.well-known/openid-configuration" + ); + registration.setName("IdsUserStatusFilter"); + registration.setOrder(1); + return registration; +} +``` + +## 总结 + +基于以上步骤, 就可快速搭建起来一套本地化的 OAuth2.0 服务。更多功能,请参考 [帮助文档](https://justauth.plus/) diff --git a/jap-ids-web/pom.xml b/jap-ids-web/pom.xml new file mode 100644 index 0000000..00c6016 --- /dev/null +++ b/jap-ids-web/pom.xml @@ -0,0 +1,22 @@ + + + + com.fujieid + jap + ${revision} + + 4.0.0 + + jap-ids-web + jap-ids-web + jap-ids-web + + + + com.fujieid + jap-ids + provided + + + diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/AbstractIdsFilter.java b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/AbstractIdsFilter.java similarity index 90% rename from jap-ids/src/main/java/com/fujieid/jap/ids/filter/AbstractIdsFilter.java rename to jap-ids-web/src/main/java/com/fujieid/jap/web/filter/AbstractIdsFilter.java index 88a47d1..446db06 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/AbstractIdsFilter.java +++ b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/AbstractIdsFilter.java @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.fujieid.jap.ids.filter; +package com.fujieid.jap.web.filter; -import cn.hutool.log.Log; -import cn.hutool.log.LogFactory; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.config.IdsConfig; import com.fujieid.jap.ids.pipeline.IdsPipeline; import com.xkcoding.json.util.StringUtil; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Arrays; @@ -37,7 +35,6 @@ import java.util.List; * @since 1.0.0 */ public class AbstractIdsFilter { - protected static final Log log = LogFactory.get(); protected final List ignoreUrls = new ArrayList<>(); /** @@ -111,8 +108,8 @@ public class AbstractIdsFilter { if (null == idsFilterErrorPipeline) { idsFilterErrorPipeline = new IdsPipeline() { @Override - public void errorHandle(ServletRequest servletRequest, ServletResponse servletResponse, Throwable throwable) { - IdsPipeline.super.errorHandle(servletRequest, servletResponse, throwable); + public void errorHandle(JapHttpRequest request, JapHttpResponse response, Throwable throwable) { + IdsPipeline.super.errorHandle(request, response, throwable); } }; } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/IdsAccessTokenFilter.java b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/IdsAccessTokenFilter.java similarity index 81% rename from jap-ids/src/main/java/com/fujieid/jap/ids/filter/IdsAccessTokenFilter.java rename to jap-ids-web/src/main/java/com/fujieid/jap/web/filter/IdsAccessTokenFilter.java index 877bbd0..e64ae8c 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/IdsAccessTokenFilter.java +++ b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/IdsAccessTokenFilter.java @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.fujieid.jap.ids.filter; +package com.fujieid.jap.web.filter; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; +import com.fujieid.jap.http.jakarta.JakartaResponseAdapter; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.pipeline.IdsPipeline; import com.fujieid.jap.ids.util.TokenUtil; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** @@ -47,12 +50,14 @@ public class IdsAccessTokenFilter extends AbstractIdsFilter implements Filter { return; } log.debug("{} - {}", request.getMethod(), request.getRequestURI()); - String accessToken = TokenUtil.getAccessToken(request); + String accessToken = TokenUtil.getAccessToken(new JakartaRequestAdapter(request)); try { TokenUtil.validateAccessToken(accessToken); filterChain.doFilter(servletRequest, servletResponse); } catch (Exception e) { - this.getFilterErrorPipeline(idsFilterErrorPipeline).errorHandle(servletRequest, servletResponse, e); + this.getFilterErrorPipeline(idsFilterErrorPipeline) + .errorHandle(new JakartaRequestAdapter((HttpServletRequest) servletRequest), + new JakartaResponseAdapter((HttpServletResponse) servletResponse), e); } } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/IdsUserStatusFilter.java b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/IdsUserStatusFilter.java similarity index 84% rename from jap-ids/src/main/java/com/fujieid/jap/ids/filter/IdsUserStatusFilter.java rename to jap-ids-web/src/main/java/com/fujieid/jap/web/filter/IdsUserStatusFilter.java index 5126a26..c877c05 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/IdsUserStatusFilter.java +++ b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/IdsUserStatusFilter.java @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.fujieid.jap.ids.filter; +package com.fujieid.jap.web.filter; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; +import com.fujieid.jap.http.jakarta.JakartaResponseAdapter; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.IdsException; import com.fujieid.jap.ids.model.enums.ErrorResponse; @@ -22,6 +24,7 @@ import com.fujieid.jap.ids.pipeline.IdsPipeline; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** @@ -36,17 +39,17 @@ public class IdsUserStatusFilter extends AbstractIdsFilter implements Filter { public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { IdsPipeline idsFilterErrorPipeline = JapIds.getContext().getFilterPipeline(); HttpServletRequest request = (HttpServletRequest) servletRequest; - boolean ignored = this.isIgnoredServletPath(request); if (ignored) { filterChain.doFilter(servletRequest, servletResponse); return; } - if (JapIds.isAuthenticated(request)) { + if (JapIds.isAuthenticated(new JakartaRequestAdapter(request))) { filterChain.doFilter(servletRequest, servletResponse); return; } - this.getFilterErrorPipeline(idsFilterErrorPipeline).errorHandle(servletRequest, servletResponse, + this.getFilterErrorPipeline(idsFilterErrorPipeline).errorHandle(new JakartaRequestAdapter((HttpServletRequest) servletRequest), + new JakartaResponseAdapter((HttpServletResponse) servletResponse), new IdsException(ErrorResponse.INVALID_USER_STATUS)); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/package-info.java b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/package-info.java similarity index 95% rename from jap-ids/src/main/java/com/fujieid/jap/ids/filter/package-info.java rename to jap-ids-web/src/main/java/com/fujieid/jap/web/filter/package-info.java index a28c35b..84ed47b 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/filter/package-info.java +++ b/jap-ids-web/src/main/java/com/fujieid/jap/web/filter/package-info.java @@ -20,4 +20,4 @@ * @version 1.0.0 * @since 1.0.0 */ -package com.fujieid.jap.ids.filter; +package com.fujieid.jap.web.filter; diff --git a/jap-ids-web/src/main/java/com/fujieid/jap/web/package-info.java b/jap-ids-web/src/main/java/com/fujieid/jap/web/package-info.java new file mode 100644 index 0000000..50e8a92 --- /dev/null +++ b/jap-ids-web/src/main/java/com/fujieid/jap/web/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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. + */ +/** + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.web; diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/JapIds.java b/jap-ids/src/main/java/com/fujieid/jap/ids/JapIds.java index c1010f1..0b38e46 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/JapIds.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/JapIds.java @@ -16,6 +16,7 @@ package com.fujieid.jap.ids; import com.fujieid.jap.core.spi.JapServiceLoader; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.config.IdsConfig; import com.fujieid.jap.ids.context.IdsContext; import com.fujieid.jap.ids.exception.IdsException; @@ -25,7 +26,6 @@ import com.fujieid.jap.ids.pipeline.IdsLogoutPipeline; import com.fujieid.jap.ids.pipeline.IdsSignInPipeline; import com.fujieid.jap.ids.service.*; -import javax.servlet.http.HttpServletRequest; import java.io.Serializable; /** @@ -90,21 +90,21 @@ public class JapIds implements Serializable { return context; } - public static boolean isAuthenticated(HttpServletRequest request) { + public static boolean isAuthenticated(JapHttpRequest request) { return null != getUserInfo(request); } - public static void saveUserInfo(UserInfo userInfo, HttpServletRequest request) { + public static void saveUserInfo(UserInfo userInfo, JapHttpRequest request) { IdsContext context = getContext(); context.getUserStoreService().save(userInfo, request); } - public static UserInfo getUserInfo(HttpServletRequest request) { + public static UserInfo getUserInfo(JapHttpRequest request) { IdsContext context = getContext(); return context.getUserStoreService().get(request); } - public static void removeUserInfo(HttpServletRequest request) { + public static void removeUserInfo(JapHttpRequest request) { IdsContext context = getContext(); context.getUserStoreService().remove(request); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AbstractEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AbstractEndpoint.java index 0a05af6..5115155 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AbstractEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AbstractEndpoint.java @@ -15,14 +15,13 @@ */ package com.fujieid.jap.ids.endpoint; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.model.UserInfo; import com.fujieid.jap.ids.pipeline.IdsPipeline; import com.fujieid.jap.ids.service.Oauth2Service; import com.fujieid.jap.ids.service.Oauth2ServiceImpl; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - /** * Abstract classes common to various endpoints * @@ -41,13 +40,13 @@ public abstract class AbstractEndpoint { if (null == idsSigninPipeline) { idsSigninPipeline = new IdsPipeline() { @Override - public boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) { - return IdsPipeline.super.preHandle(servletRequest, servletResponse); + public boolean preHandle(JapHttpRequest httpRequest, JapHttpResponse httpResponse) { + return IdsPipeline.super.preHandle(httpRequest, httpResponse); } @Override - public UserInfo postHandle(ServletRequest servletRequest, ServletResponse servletResponse) { - return IdsPipeline.super.postHandle(servletRequest, servletResponse); + public UserInfo postHandle(JapHttpRequest httpRequest, JapHttpResponse httpResponse) { + return IdsPipeline.super.postHandle(httpRequest, httpResponse); } }; } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ApprovalEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ApprovalEndpoint.java index 45b623e..842cb1d 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ApprovalEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ApprovalEndpoint.java @@ -15,6 +15,8 @@ */ package com.fujieid.jap.ids.endpoint; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.model.ClientDetail; import com.fujieid.jap.ids.model.IdsRequestParam; @@ -26,9 +28,8 @@ import com.fujieid.jap.ids.util.EndpointUtil; import com.fujieid.jap.ids.util.OauthUtil; import com.fujieid.jap.ids.util.ObjectUtils; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.*; /** @@ -47,10 +48,11 @@ public class ApprovalEndpoint extends AbstractEndpoint { * @param response current HTTP response * @throws IOException IOException */ - public void showConfirmPage(HttpServletRequest request, HttpServletResponse response) throws IOException { + public void showConfirmPage(JapHttpRequest request, JapHttpResponse response) throws IOException { final String approvalContent = createConfirmPageHtml(request); response.setContentType("text/html;charset=UTF-8"); - response.getWriter().append(approvalContent); + response.setContentLength(approvalContent.getBytes(StandardCharsets.UTF_8).length); + response.write(approvalContent); } /** @@ -59,7 +61,7 @@ public class ApprovalEndpoint extends AbstractEndpoint { * @param request HttpServletRequest * @return IdsResponse */ - public IdsResponse> getAuthClientInfo(HttpServletRequest request) { + public IdsResponse> getAuthClientInfo(JapHttpRequest request) { IdsRequestParam param = IdsRequestParamProvider.parseRequest(request); ClientDetail clientDetail = JapIds.getContext().getClientDetailService().getByClientId(param.getClientId()); OauthUtil.validClientDetail(clientDetail); @@ -80,7 +82,7 @@ public class ApprovalEndpoint extends AbstractEndpoint { * @param request current HTTP request * @return Confirm the html of the authorization page */ - private String createConfirmPageHtml(HttpServletRequest request) { + private String createConfirmPageHtml(JapHttpRequest request) { IdsRequestParam param = IdsRequestParamProvider.parseRequest(request); String clientId = param.getClientId(); ClientDetail clientDetail = JapIds.getContext().getClientDetailService().getByClientId(clientId); @@ -132,7 +134,7 @@ public class ApprovalEndpoint extends AbstractEndpoint { * @param request current HTTP request * @return the scope list of the authorization confirmation page */ - private String createScopes(IdsRequestParam param, HttpServletRequest request) { + private String createScopes(IdsRequestParam param, JapHttpRequest request) { StringBuilder builder = new StringBuilder("

    "); List> scopeInfo = getScopeInfo(param); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AuthorizationEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AuthorizationEndpoint.java index 5cd44c0..858209f 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AuthorizationEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/AuthorizationEndpoint.java @@ -16,6 +16,7 @@ package com.fujieid.jap.ids.endpoint; import cn.hutool.core.util.ArrayUtil; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.InvalidScopeException; import com.fujieid.jap.ids.model.ClientDetail; @@ -30,7 +31,6 @@ import com.fujieid.jap.ids.util.EndpointUtil; import com.fujieid.jap.ids.util.OauthUtil; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Arrays; import java.util.Set; @@ -59,7 +59,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint { * @return Callback url or authorization url * @throws IOException IOException */ - public IdsResponse authorize(HttpServletRequest request) throws IOException { + public IdsResponse authorize(JapHttpRequest request) throws IOException { IdsRequestParam param = IdsRequestParamProvider.parseRequest(request); ClientDetail clientDetail = JapIds.getContext().getClientDetailService().getByClientId(param.getClientId()); @@ -85,7 +85,7 @@ public class AuthorizationEndpoint extends AbstractEndpoint { * @param request current HTTP request * @return Return the callback url (with parameters such as code) */ - public IdsResponse agree(HttpServletRequest request) { + public IdsResponse agree(JapHttpRequest request) { IdsRequestParam param = IdsRequestParamProvider.parseRequest(request); // The scope checked by the user may be inconsistent with the scope passed in the current HTTP request diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/DiscoveryEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/DiscoveryEndpoint.java index 0596f3f..321aec1 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/DiscoveryEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/DiscoveryEndpoint.java @@ -15,11 +15,10 @@ */ package com.fujieid.jap.ids.endpoint; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.model.OidcDiscoveryDto; import com.fujieid.jap.ids.oidc.OidcUtil; -import javax.servlet.http.HttpServletRequest; - /** * OpenID Provider Endpoint * @@ -46,7 +45,7 @@ public class DiscoveryEndpoint extends AbstractEndpoint { * @see OpenID Provider Metadata * @see OpenID Provider Configuration Response */ - public OidcDiscoveryDto getOidcDiscoveryInfo(HttpServletRequest request) { + public OidcDiscoveryDto getOidcDiscoveryInfo(JapHttpRequest request) { return OidcUtil.getOidcDiscoveryInfo(request); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ErrorEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ErrorEndpoint.java index c4777c5..198d6f4 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ErrorEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/ErrorEndpoint.java @@ -15,12 +15,12 @@ */ package com.fujieid.jap.ids.endpoint; -import com.fujieid.jap.core.util.RequestUtil; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.RequestUtil; import com.fujieid.jap.ids.model.enums.ErrorResponse; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -51,12 +51,12 @@ public class ErrorEndpoint extends AbstractEndpoint { * @param response current HTTP response * @throws IOException IOException */ - public void showErrorPage(HttpServletRequest request, HttpServletResponse response) throws IOException { + public void showErrorPage(JapHttpRequest request, JapHttpResponse response) throws IOException { ErrorResponse errorResponse = ErrorResponse.getByError(RequestUtil.getParam("error", request)); String errorPageHtml = createErrorPageHtml(errorResponse.getError(), errorResponse.getErrorDescription()); response.setContentType("text/html;charset=UTF-8"); response.setContentLength(errorPageHtml.getBytes(StandardCharsets.UTF_8).length); - response.getWriter().write(errorPageHtml); + response.write(errorPageHtml); } /** @@ -67,11 +67,11 @@ public class ErrorEndpoint extends AbstractEndpoint { * @param response current HTTP response * @throws IOException IOException */ - public void showErrorPage(String error, String errorDescription, HttpServletResponse response) throws IOException { + public void showErrorPage(String error, String errorDescription, JapHttpResponse response) throws IOException { String errorPageHtml = createErrorPageHtml(error, errorDescription); response.setContentType("text/html;charset=UTF-8"); response.setContentLength(errorPageHtml.getBytes(StandardCharsets.UTF_8).length); - response.getWriter().write(errorPageHtml); + response.write(errorPageHtml); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LoginEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LoginEndpoint.java index 3c79afe..f776f0e 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LoginEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LoginEndpoint.java @@ -17,6 +17,8 @@ package com.fujieid.jap.ids.endpoint; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.IdsException; import com.fujieid.jap.ids.model.ClientDetail; @@ -30,10 +32,6 @@ import com.fujieid.jap.ids.util.EndpointUtil; import com.fujieid.jap.ids.util.OauthUtil; import com.fujieid.jap.ids.util.ObjectUtils; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -53,14 +51,14 @@ public class LoginEndpoint extends AbstractEndpoint { * @param response current HTTP response * @throws IOException IOException */ - public void showLoginPage(HttpServletRequest request, HttpServletResponse response) throws IOException { + public void showLoginPage(JapHttpRequest request, JapHttpResponse response) throws IOException { String loginPageHtml = generateLoginPageHtml(request); response.setContentType("text/html;charset=UTF-8"); response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length); - response.getWriter().write(loginPageHtml); + response.write(loginPageHtml); } - private String generateLoginPageHtml(HttpServletRequest request) { + private String generateLoginPageHtml(JapHttpRequest request) { StringBuilder sb = new StringBuilder(); sb.append("\n" + "\n" @@ -98,19 +96,18 @@ public class LoginEndpoint extends AbstractEndpoint { /** * Login with account password * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return Confirm authorization page */ - public IdsResponse signin(ServletRequest servletRequest, ServletResponse servletResponse) { - HttpServletRequest request = (HttpServletRequest) servletRequest; + public IdsResponse signin(JapHttpRequest request, JapHttpResponse response) { IdsPipeline idsSigninPipeline = JapIds.getContext().getSigninPipeline(); idsSigninPipeline = this.getUserInfoIdsPipeline(idsSigninPipeline); - if (!idsSigninPipeline.preHandle(request, servletResponse)) { + if (!idsSigninPipeline.preHandle(request, response)) { throw new IdsException("IdsSigninPipeline.preHandle returns false, the process is blocked."); } IdsRequestParam param = IdsRequestParamProvider.parseRequest(request); - UserInfo userInfo = idsSigninPipeline.postHandle(request, servletResponse); + UserInfo userInfo = idsSigninPipeline.postHandle(request, response); if (null == userInfo) { String username = param.getUsername(); String password = param.getPassword(); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LogoutEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LogoutEndpoint.java index 76b9358..01f704a 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LogoutEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/LogoutEndpoint.java @@ -15,6 +15,8 @@ */ package com.fujieid.jap.ids.endpoint; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.IdsException; import com.fujieid.jap.ids.model.IdsResponse; @@ -22,9 +24,6 @@ import com.fujieid.jap.ids.model.UserInfo; import com.fujieid.jap.ids.pipeline.IdsPipeline; import com.fujieid.jap.ids.util.EndpointUtil; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; - /** * Logout Endpoint * @@ -34,7 +33,7 @@ import javax.servlet.http.HttpServletRequest; */ public class LogoutEndpoint extends AbstractEndpoint { - public IdsResponse logout(HttpServletRequest request, ServletResponse response) { + public IdsResponse logout(JapHttpRequest request, JapHttpResponse response) { IdsPipeline logoutPipeline = JapIds.getContext().getLogoutPipeline(); logoutPipeline = this.getUserInfoIdsPipeline(logoutPipeline); if (!logoutPipeline.preHandle(request, response)) { diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/TokenEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/TokenEndpoint.java index a967963..d730f9c 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/TokenEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/TokenEndpoint.java @@ -15,6 +15,7 @@ */ package com.fujieid.jap.ids.endpoint; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.exception.UnsupportedGrantTypeException; import com.fujieid.jap.ids.model.IdsRequestParam; import com.fujieid.jap.ids.model.IdsResponse; @@ -25,8 +26,6 @@ import com.fujieid.jap.ids.provider.IdsTokenProvider; import com.fujieid.jap.ids.util.TokenUtil; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; - /** * Token Endpoint. According to the request parameters, to obtain different types of access tokens, refer to: *

    @@ -44,7 +43,7 @@ public class TokenEndpoint extends AbstractEndpoint { private final IdsTokenProvider idsTokenProvider = new IdsTokenProvider(oauth2Service); - public IdsResponse getToken(HttpServletRequest request) { + public IdsResponse getToken(JapHttpRequest request) { IdsRequestParam param = IdsRequestParamProvider.parseRequest(request); if (StringUtil.isEmpty(param.getGrantType())) { @@ -65,7 +64,7 @@ public class TokenEndpoint extends AbstractEndpoint { throw new UnsupportedGrantTypeException(ErrorResponse.UNSUPPORTED_GRANT_TYPE); } - public IdsResponse revokeToken(HttpServletRequest request) { + public IdsResponse revokeToken(JapHttpRequest request) { TokenUtil.invalidateToken(request); return new IdsResponse<>(); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/UserInfoEndpoint.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/UserInfoEndpoint.java index ff37731..b67a62c 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/UserInfoEndpoint.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/UserInfoEndpoint.java @@ -15,6 +15,7 @@ */ package com.fujieid.jap.ids.endpoint; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.IdsException; import com.fujieid.jap.ids.exception.InvalidTokenException; @@ -27,7 +28,6 @@ import com.fujieid.jap.ids.util.OauthUtil; import com.fujieid.jap.ids.util.TokenUtil; import com.xkcoding.json.JsonUtil; -import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Map; import java.util.Set; @@ -51,7 +51,7 @@ public class UserInfoEndpoint extends AbstractEndpoint { * @see 5.3.2. Successful UserInfo Response * @see 5.4. Requesting Claims using Scope Values */ - public IdsResponse getCurrentUserInfo(HttpServletRequest request) { + public IdsResponse getCurrentUserInfo(JapHttpRequest request) { String accessTokenStr = TokenUtil.getAccessToken(request); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/package-info.java b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/package-info.java index a4006a1..e9df3e6 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/package-info.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/endpoint/package-info.java @@ -14,7 +14,9 @@ * limitations under the License. */ /** - * ids The service endpoint that needs to be provided externally + * API methods that need to be provided externally, + *

    + * such as: obtaining authorization, obtaining AccessToken, refreshing AccessToken, obtaining IDS service discovery configuration, etc. * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0.0 diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/model/enums/package-info.java b/jap-ids/src/main/java/com/fujieid/jap/ids/model/enums/package-info.java new file mode 100644 index 0000000..ffcc6e6 --- /dev/null +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/model/enums/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

    + * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

    + * http://www.gnu.org/licenses/lgpl.html + *

    + * 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. + */ +/** + * enum in ids + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +package com.fujieid.jap.ids.model.enums; diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/OidcUtil.java b/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/OidcUtil.java index 538fd0b..c89038c 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/OidcUtil.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/OidcUtil.java @@ -18,6 +18,7 @@ package com.fujieid.jap.ids.oidc; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ObjectUtil; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.config.IdsConfig; import com.fujieid.jap.ids.model.IdsConsts; @@ -32,7 +33,6 @@ import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKeySet; import org.jose4j.jwt.ReservedClaimNames; -import javax.servlet.http.HttpServletRequest; import java.util.*; import java.util.stream.Collectors; @@ -43,14 +43,14 @@ import java.util.stream.Collectors; */ public class OidcUtil { - public static OidcDiscoveryDto getOidcDiscoveryInfo(HttpServletRequest request) { + public static OidcDiscoveryDto getOidcDiscoveryInfo(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); List scopes = IdsScopeProvider.getScopeCodes(); String issuer = EndpointUtil.getIssuer(request); - Map model = new HashMap<>(); + Map model = new HashMap<>(33); model.put("issuer", issuer); model.put("authorization_endpoint", EndpointUtil.getAuthorizeUrl(request)); model.put("token_endpoint", EndpointUtil.getTokenUrl(request)); @@ -80,14 +80,14 @@ public class OidcUtil { } model.put("request_object_signing_alg_values_supported", Arrays.asList( - "none", - "RS256", - "ES256" + "none", + "RS256", + "ES256" ) ); model.put("userinfo_signing_alg_values_supported", Arrays.asList( - "RS256", - "ES256" + "RS256", + "ES256" ) ); model.put("request_parameter_supported", true); @@ -95,8 +95,8 @@ public class OidcUtil { model.put("require_request_uri_registration", false); model.put("claims_parameter_supported", true); model.put("id_token_signing_alg_values_supported", Arrays.asList( - "RS256", - "ES256" + "RS256", + "ES256" ) ); model.put("subject_types_supported", Collections.singletonList("public")); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/package-info.java b/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/package-info.java index d2308c0..de4db1a 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/package-info.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/oidc/package-info.java @@ -14,7 +14,7 @@ * limitations under the License. */ /** - * Support openid connect + * Support for openid connect, including Id Token and OIDC tools * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0.0 diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsFilterPipeline.java b/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsFilterPipeline.java index b7ba7d5..1a6dd29 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsFilterPipeline.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsFilterPipeline.java @@ -15,13 +15,14 @@ */ package com.fujieid.jap.ids.pipeline; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; + +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; /** * For the filter of jap-ids, a pipeline interface is provided. When an exception occurs in the filter, *

    - * you can use {@link com.fujieid.jap.ids.pipeline.IdsFilterPipeline#errorHandle(ServletRequest, ServletResponse, Throwable)} to handle the exception information . + * you can use {@link com.fujieid.jap.ids.pipeline.IdsFilterPipeline#errorHandle(JapHttpRequest, JapHttpResponse, Throwable)} to handle the exception information . *

    * The data in json format is returned by default: * @@ -32,7 +33,7 @@ import javax.servlet.ServletResponse; * } * *

    - * Note: Only need to implement the {@link com.fujieid.jap.ids.pipeline.IdsFilterPipeline#errorHandle(ServletRequest, ServletResponse, Throwable)} method of this interface. + * Note: Only need to implement the {@link com.fujieid.jap.ids.pipeline.IdsFilterPipeline#errorHandle(JapHttpRequest, JapHttpResponse, Throwable)} method of this interface. * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0.0 @@ -42,50 +43,50 @@ public interface IdsFilterPipeline extends IdsPipeline { /** * Callback when the program is abnormal * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response - * @param throwable any exception thrown on handler execution, if any. + * @param request current HTTP request + * @param response current HTTP response + * @param throwable any exception thrown on handler execution, if any. */ @Override - default void errorHandle(ServletRequest servletRequest, ServletResponse servletResponse, Throwable throwable) { - IdsPipeline.super.errorHandle(servletRequest, servletResponse, throwable); + default void errorHandle(JapHttpRequest request, JapHttpResponse response, Throwable throwable) { + IdsPipeline.super.errorHandle(request, response, throwable); } /** * Operations before business process processing, such as initializing resources, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return boolean */ @Deprecated @Override - default boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) { - return IdsPipeline.super.preHandle(servletRequest, servletResponse); + default boolean preHandle(JapHttpRequest request, JapHttpResponse response) { + return IdsPipeline.super.preHandle(request, response); } /** * Intercept the execution of a handler * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return Object */ @Deprecated @Override - default Object postHandle(ServletRequest servletRequest, ServletResponse servletResponse) { - return IdsPipeline.super.postHandle(servletRequest, servletResponse); + default Object postHandle(JapHttpRequest request, JapHttpResponse response) { + return IdsPipeline.super.postHandle(request, response); } /** * Callback after business process processing is completed, such as recycling resources, recording status, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response */ @Deprecated @Override - default void afterHandle(ServletRequest servletRequest, ServletResponse servletResponse) { - IdsPipeline.super.afterHandle(servletRequest, servletResponse); + default void afterHandle(JapHttpRequest request, JapHttpResponse response) { + IdsPipeline.super.afterHandle(request, response); } } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsPipeline.java b/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsPipeline.java index a088d66..874d0b4 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsPipeline.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/pipeline/IdsPipeline.java @@ -15,13 +15,12 @@ */ package com.fujieid.jap.ids.pipeline; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.exception.IdsException; import com.fujieid.jap.ids.model.IdsResponse; import com.xkcoding.json.JsonUtil; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import java.io.IOException; import java.nio.charset.StandardCharsets; /** @@ -41,57 +40,53 @@ public interface IdsPipeline { /** * Callback when the program is abnormal * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response - * @param throwable any exception thrown on handler execution, if any. + * @param request current HTTP request + * @param response current HTTP response + * @param throwable any exception thrown on handler execution, if any. */ - default void errorHandle(ServletRequest servletRequest, ServletResponse servletResponse, Throwable throwable) { - IdsResponse response = new IdsResponse<>(); + default void errorHandle(JapHttpRequest request, JapHttpResponse response, Throwable throwable) { + IdsResponse idsResponse = new IdsResponse<>(); if (throwable instanceof IdsException) { IdsException idsException = (IdsException) throwable; - response.error(idsException.getError()) + idsResponse.error(idsException.getError()) .errorDescription(idsException.getErrorDescription()); } else { - response.errorDescription(throwable.getMessage()); - } - String errorResponseStr = JsonUtil.toJsonString(response); - servletResponse.setContentType("text/html;charset=UTF-8"); - servletResponse.setContentLength(errorResponseStr.getBytes(StandardCharsets.UTF_8).length); - try { - servletResponse.getWriter().write(errorResponseStr); - } catch (IOException e) { - e.printStackTrace(); + idsResponse.errorDescription(throwable.getMessage()); } + String errorResponseStr = JsonUtil.toJsonString(idsResponse); + response.setContentType("text/html;charset=UTF-8"); + response.setContentLength(errorResponseStr.getBytes(StandardCharsets.UTF_8).length); + response.write(errorResponseStr); } /** * Operations before business process processing, such as initializing resources, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return boolean */ - default boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + default boolean preHandle(JapHttpRequest request, JapHttpResponse response) { return true; } /** * Intercept the execution of a handler * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return Object */ - default T postHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + default T postHandle(JapHttpRequest request, JapHttpResponse response) { return null; } /** * Callback after business process processing is completed, such as recycling resources, recording status, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response */ - default void afterHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + default void afterHandle(JapHttpRequest request, JapHttpResponse response) { } } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsRequestParamProvider.java b/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsRequestParamProvider.java index 15b742a..991af5e 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsRequestParamProvider.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsRequestParamProvider.java @@ -16,6 +16,7 @@ package com.fujieid.jap.ids.provider; import cn.hutool.core.util.ObjectUtil; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.config.IdsConfig; import com.fujieid.jap.ids.exception.InvalidRequestException; @@ -25,8 +26,6 @@ import com.fujieid.jap.ids.model.IdsRequestParam; import com.fujieid.jap.ids.model.enums.ErrorResponse; import com.fujieid.jap.ids.util.ClientCertificateUtil; -import javax.servlet.http.HttpServletRequest; - /** * Parameter parser for oauth request * @@ -36,7 +35,7 @@ import javax.servlet.http.HttpServletRequest; */ public class IdsRequestParamProvider { - public static IdsRequestParam parseRequest(HttpServletRequest request) { + public static IdsRequestParam parseRequest(JapHttpRequest request) { if (ObjectUtil.isEmpty(request.getParameterMap())) { throw new InvalidRequestException(ErrorResponse.INVALID_REQUEST); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsTokenProvider.java b/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsTokenProvider.java index cbc3564..72ededf 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsTokenProvider.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/provider/IdsTokenProvider.java @@ -17,6 +17,7 @@ package com.fujieid.jap.ids.provider; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.IdsException; import com.fujieid.jap.ids.model.*; @@ -28,8 +29,6 @@ import com.fujieid.jap.ids.util.OauthUtil; import com.fujieid.jap.ids.util.TokenUtil; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; - /** * The token endpoint creates a token, and returns different token information for different authorization types * @@ -54,7 +53,7 @@ public class IdsTokenProvider { * @return IdsResponse * @see 4.1. Authorization Code Grant */ - public IdsResponse generateAuthorizationCodeResponse(IdsRequestParam param, HttpServletRequest request) { + public IdsResponse generateAuthorizationCodeResponse(IdsRequestParam param, JapHttpRequest request) { AuthCode codeInfo = oauth2Service.validateAndGetAuthrizationCode(param.getGrantType(), param.getCode()); String scope = codeInfo.getScope(); @@ -93,7 +92,7 @@ public class IdsTokenProvider { * @return IdsResponse * @see 4.3. Resource Owner Password Credentials Grant */ - public IdsResponse generatePasswordResponse(IdsRequestParam param, HttpServletRequest request) { + public IdsResponse generatePasswordResponse(IdsRequestParam param, JapHttpRequest request) { String username = param.getUsername(); String password = param.getPassword(); String clientId = param.getClientId(); @@ -135,7 +134,7 @@ public class IdsTokenProvider { * @return IdsResponse * @see 4.4. Client Credentials Grant */ - public IdsResponse generateClientCredentialsResponse(IdsRequestParam param, HttpServletRequest request) { + public IdsResponse generateClientCredentialsResponse(IdsRequestParam param, JapHttpRequest request) { String clientId = param.getClientId(); ClientDetail clientDetail = JapIds.getContext().getClientDetailService().getByClientId(clientId); @@ -170,7 +169,7 @@ public class IdsTokenProvider { * @return IdsResponse * @see 6. Refreshing an Access Token */ - public IdsResponse generateRefreshTokenResponse(IdsRequestParam param, HttpServletRequest request) { + public IdsResponse generateRefreshTokenResponse(IdsRequestParam param, JapHttpRequest request) { TokenUtil.validateRefreshToken(param.getRefreshToken()); AccessToken token = TokenUtil.getByRefreshToken(param.getRefreshToken()); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreService.java b/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreService.java index c968f27..93268ea 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreService.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreService.java @@ -15,10 +15,9 @@ */ package com.fujieid.jap.ids.service; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.model.UserInfo; -import javax.servlet.http.HttpServletRequest; - /** * When the user logs in, store and operate the user's login information * @@ -29,14 +28,14 @@ import javax.servlet.http.HttpServletRequest; public interface IdsUserStoreService { /** - * Save user data, and store user information in {@link javax.servlet.http.HttpSession} by default. + * Save user data, and store user information in {@link com.fujieid.jap.http.JapHttpSession} by default. *

    * Developers can implement this method to save user information in other media, such as cache, database, etc. * * @param userInfo User information after login * @param request current HTTP request */ - void save(UserInfo userInfo, HttpServletRequest request); + void save(UserInfo userInfo, JapHttpRequest request); /** * Get logged-in user information @@ -44,12 +43,12 @@ public interface IdsUserStoreService { * @param request current HTTP request * @return UserInfo */ - UserInfo get(HttpServletRequest request); + UserInfo get(JapHttpRequest request); /** * Delete logged-in user information * * @param request current HTTP request */ - void remove(HttpServletRequest request); + void remove(JapHttpRequest request); } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreServiceImpl.java b/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreServiceImpl.java index cebd5f6..1704c8f 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreServiceImpl.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/service/IdsUserStoreServiceImpl.java @@ -15,12 +15,10 @@ */ package com.fujieid.jap.ids.service; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.ids.model.IdsConsts; import com.fujieid.jap.ids.model.UserInfo; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0.0 @@ -29,7 +27,7 @@ import javax.servlet.http.HttpSession; public class IdsUserStoreServiceImpl implements IdsUserStoreService { /** - * Save user data, and store user information in {@link HttpSession} by default. + * Save user data, and store user information in {@link com.fujieid.jap.http.JapHttpSession} by default. *

    * Developers can implement this method to save user information in other media, such as cache, database, etc. * @@ -37,7 +35,7 @@ public class IdsUserStoreServiceImpl implements IdsUserStoreService { * @param request current HTTP request */ @Override - public void save(UserInfo userInfo, HttpServletRequest request) { + public void save(UserInfo userInfo, JapHttpRequest request) { request.getSession().setAttribute(IdsConsts.OAUTH_USERINFO_CACHE_KEY, userInfo); } @@ -48,7 +46,7 @@ public class IdsUserStoreServiceImpl implements IdsUserStoreService { * @return UserInfo */ @Override - public UserInfo get(HttpServletRequest request) { + public UserInfo get(JapHttpRequest request) { return (UserInfo) request.getSession().getAttribute(IdsConsts.OAUTH_USERINFO_CACHE_KEY); } @@ -58,7 +56,7 @@ public class IdsUserStoreServiceImpl implements IdsUserStoreService { * @param request current HTTP request */ @Override - public void remove(HttpServletRequest request) { + public void remove(JapHttpRequest request) { request.getSession().removeAttribute(IdsConsts.OAUTH_USERINFO_CACHE_KEY); } } diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/util/ClientCertificateUtil.java b/jap-ids/src/main/java/com/fujieid/jap/ids/util/ClientCertificateUtil.java index 10a0973..9725a61 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/util/ClientCertificateUtil.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/util/ClientCertificateUtil.java @@ -16,14 +16,14 @@ package com.fujieid.jap.ids.util; import cn.hutool.core.util.ObjectUtil; -import com.fujieid.jap.core.util.RequestUtil; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.RequestUtil; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.model.ClientCertificate; import com.fujieid.jap.ids.model.IdsConsts; import com.fujieid.jap.ids.model.enums.ClientSecretAuthMethod; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; @@ -36,7 +36,7 @@ import java.util.List; */ public class ClientCertificateUtil { - public static ClientCertificate getClientCertificate(HttpServletRequest request) { + public static ClientCertificate getClientCertificate(JapHttpRequest request) { List clientSecretAuthMethods = JapIds.getIdsConfig().getClientSecretAuthMethods(); if (ObjectUtil.isEmpty(clientSecretAuthMethods)) { clientSecretAuthMethods = Collections.singletonList(ClientSecretAuthMethod.ALL); @@ -63,13 +63,13 @@ public class ClientCertificateUtil { return new ClientCertificate(); } - private static ClientCertificate getClientCertificateFromRequestParameter(HttpServletRequest request) { + private static ClientCertificate getClientCertificateFromRequestParameter(JapHttpRequest request) { String clientId = RequestUtil.getParam(IdsConsts.CLIENT_ID, request); String clientSecret = RequestUtil.getParam(IdsConsts.CLIENT_SECRET, request); return new ClientCertificate(clientId, clientSecret); } - private static ClientCertificate getClientCertificateFromHeader(HttpServletRequest request) { + private static ClientCertificate getClientCertificateFromHeader(JapHttpRequest request) { String authorizationHeader = RequestUtil.getHeader(IdsConsts.AUTHORIZATION_HEADER_NAME, request); if (StringUtil.isNotEmpty(authorizationHeader)) { BasicCredentials credentials = BasicCredentials.parse(authorizationHeader); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/util/EndpointUtil.java b/jap-ids/src/main/java/com/fujieid/jap/ids/util/EndpointUtil.java index c0083f2..d6034a7 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/util/EndpointUtil.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/util/EndpointUtil.java @@ -15,12 +15,12 @@ */ package com.fujieid.jap.ids.util; -import com.fujieid.jap.core.util.RequestUtil; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.RequestUtil; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.config.IdsConfig; import com.fujieid.jap.ids.exception.IdsException; -import javax.servlet.http.HttpServletRequest; import java.util.Optional; /** @@ -32,7 +32,7 @@ import java.util.Optional; */ public class EndpointUtil { - public static String getIssuer(HttpServletRequest request) { + public static String getIssuer(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); if (config.isEnableDynamicIssuer() && null == request) { throw new IdsException("The second-level domain name verification has been enabled, the HTTP request cannot be empty"); @@ -46,67 +46,67 @@ public class EndpointUtil { } - public static String getLoginUrl(HttpServletRequest request) { + public static String getLoginUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getLoginUrl(); } - public static String getErrorUrl(HttpServletRequest request) { + public static String getErrorUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getErrorUrl(); } - public static String getAuthorizeUrl(HttpServletRequest request) { + public static String getAuthorizeUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getAuthorizeUrl(); } - public static String getAuthorizeAutoApproveUrl(HttpServletRequest request) { + public static String getAuthorizeAutoApproveUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getAuthorizeAutoApproveUrl(); } - public static String getTokenUrl(HttpServletRequest request) { + public static String getTokenUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getTokenUrl(); } - public static String getUserinfoUrl(HttpServletRequest request) { + public static String getUserinfoUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getUserinfoUrl(); } - public static String getRegistrationUrl(HttpServletRequest request) { + public static String getRegistrationUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getRegistrationUrl(); } - public static String getEndSessionUrl(HttpServletRequest request) { + public static String getEndSessionUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getEndSessionUrl(); } - public static String getCheckSessionUrl(HttpServletRequest request) { + public static String getCheckSessionUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getCheckSessionUrl(); } - public static String getLogoutRedirectUrl(HttpServletRequest request) { + public static String getLogoutRedirectUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getLogoutRedirectUrl(); } - public static String getJwksUrl(HttpServletRequest request) { + public static String getJwksUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getJwksUrl(); } - public static String getDiscoveryUrl(HttpServletRequest request) { + public static String getDiscoveryUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); return getIssuer(request) + config.getDiscoveryUrl(); } - public static String getLoginPageUrl(HttpServletRequest request) { + public static String getLoginPageUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); if (config.isExternalLoginPageUrl()) { return config.getLoginPageUrl(); @@ -114,7 +114,7 @@ public class EndpointUtil { return getIssuer(request) + config.getLoginPageUrl(); } - public static String getConfirmPageUrl(HttpServletRequest request) { + public static String getConfirmPageUrl(JapHttpRequest request) { IdsConfig config = JapIds.getIdsConfig(); if (config.isExternalConfirmPageUrl()) { return config.getConfirmPageUrl(); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/util/OauthUtil.java b/jap-ids/src/main/java/com/fujieid/jap/ids/util/OauthUtil.java index a767728..2e2cd66 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/util/OauthUtil.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/util/OauthUtil.java @@ -78,7 +78,13 @@ public class OauthUtil { public static Set validateScope(String requestScopes, String clientScopes) { if (StringUtil.isEmpty(requestScopes)) { - throw new InvalidScopeException(ErrorResponse.INVALID_SCOPE); + // OPTIONAL. The scope of the access request. + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1 + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1 + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.2 + // https://datatracker.ietf.org/doc/html/rfc6749#section-4.4.2 + // https://datatracker.ietf.org/doc/html/rfc6749#section-6 + return new HashSet<>(); } Set scopes = OauthUtil.convertStrToList(requestScopes); diff --git a/jap-ids/src/main/java/com/fujieid/jap/ids/util/TokenUtil.java b/jap-ids/src/main/java/com/fujieid/jap/ids/util/TokenUtil.java index 8ae4605..8bd7cf1 100644 --- a/jap-ids/src/main/java/com/fujieid/jap/ids/util/TokenUtil.java +++ b/jap-ids/src/main/java/com/fujieid/jap/ids/util/TokenUtil.java @@ -16,7 +16,8 @@ package com.fujieid.jap.ids.util; import cn.hutool.core.util.ObjectUtil; -import com.fujieid.jap.core.util.RequestUtil; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.RequestUtil; import com.fujieid.jap.ids.JapIds; import com.fujieid.jap.ids.exception.IdsTokenException; import com.fujieid.jap.ids.exception.InvalidTokenException; @@ -26,7 +27,6 @@ import com.fujieid.jap.ids.model.enums.TokenAuthMethod; import com.fujieid.jap.ids.service.IdsTokenService; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.util.Collections; import java.util.List; @@ -44,7 +44,7 @@ public class TokenUtil { * @param request request * @return String */ - public static String getAccessToken(HttpServletRequest request) { + public static String getAccessToken(JapHttpRequest request) { List tokenAuthMethods = JapIds.getIdsConfig().getTokenAuthMethods(); if (ObjectUtil.isEmpty(tokenAuthMethods)) { tokenAuthMethods = Collections.singletonList(TokenAuthMethod.ALL); @@ -79,7 +79,7 @@ public class TokenUtil { return null; } - private static String getAccessTokenFromUrl(HttpServletRequest request) { + private static String getAccessTokenFromUrl(JapHttpRequest request) { String accessToken = RequestUtil.getParam(IdsConsts.ACCESS_TOKEN, request); if (StringUtil.isNotEmpty(accessToken)) { return accessToken; @@ -87,12 +87,12 @@ public class TokenUtil { return null; } - private static String getAccessTokenFromHeader(HttpServletRequest request) { + private static String getAccessTokenFromHeader(JapHttpRequest request) { String accessToken = RequestUtil.getHeader(IdsConsts.AUTHORIZATION_HEADER_NAME, request); return BearerToken.parse(accessToken); } - private static String getAccessTokenFromCookie(HttpServletRequest request) { + private static String getAccessTokenFromCookie(JapHttpRequest request) { return RequestUtil.getCookieVal(request, IdsConsts.ACCESS_TOKEN); } @@ -170,7 +170,7 @@ public class TokenUtil { } - public static void invalidateToken(HttpServletRequest request) { + public static void invalidateToken(JapHttpRequest request) { String accessTokenStr = TokenUtil.getAccessToken(request); AccessToken accessToken = TokenUtil.getByAccessToken(accessTokenStr); if (null != accessToken) { diff --git a/jap-ids/src/test/java/com/fujieid/jap/ids/BaseIdsTest.java b/jap-ids/src/test/java/com/fujieid/jap/ids/BaseIdsTest.java index 681a9f0..77be1ce 100644 --- a/jap-ids/src/test/java/com/fujieid/jap/ids/BaseIdsTest.java +++ b/jap-ids/src/test/java/com/fujieid/jap/ids/BaseIdsTest.java @@ -1,15 +1,15 @@ package com.fujieid.jap.ids; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; import com.fujieid.jap.ids.config.IdsConfig; import com.fujieid.jap.ids.config.JwtConfig; import com.fujieid.jap.ids.context.IdsContext; -import com.xkcoding.json.JsonUtil; import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import static org.mockito.Mockito.when; @@ -23,11 +23,10 @@ import static org.mockito.Mockito.when; public class BaseIdsTest { + public JapHttpRequest request; @Mock protected HttpServletRequest httpServletRequestMock; @Mock - protected HttpServletResponse httpServletResponseMock; - @Mock protected HttpSession httpsSessionMock; protected String issuer = "http://www.baidu.com"; @@ -36,6 +35,7 @@ public class BaseIdsTest { MockitoAnnotations.initMocks(this); // Arrange when(httpServletRequestMock.getSession()).thenReturn(httpsSessionMock); + this.request = new JakartaRequestAdapter(httpServletRequestMock); // 注册 JAP IDS 上下文 JapIds.registerContext(new IdsContext() diff --git a/jap-ids/src/test/java/com/fujieid/jap/ids/endpoint/ApprovalEndpointTest.java b/jap-ids/src/test/java/com/fujieid/jap/ids/endpoint/ApprovalEndpointTest.java deleted file mode 100644 index 936b9aa..0000000 --- a/jap-ids/src/test/java/com/fujieid/jap/ids/endpoint/ApprovalEndpointTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.fujieid.jap.ids.endpoint; - -import com.fujieid.jap.ids.BaseIdsTest; -import com.fujieid.jap.ids.exception.InvalidRequestException; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; - -public class ApprovalEndpointTest extends BaseIdsTest { - - @Test - public void showConfirmPage() throws IOException { - Assert.assertThrows(InvalidRequestException.class, () -> new ApprovalEndpoint().showConfirmPage(httpServletRequestMock, httpServletResponseMock)); - } - - @Test - public void getAuthClientInfo() { - Assert.assertThrows(InvalidRequestException.class, () -> new ApprovalEndpoint().getAuthClientInfo(httpServletRequestMock)); - } -} diff --git a/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeFilterPipeline.java b/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeFilterPipeline.java index 44944ec..f6d2ab1 100644 --- a/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeFilterPipeline.java +++ b/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeFilterPipeline.java @@ -15,8 +15,8 @@ */ package com.fujieid.jap.ids.pipeline; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) @@ -27,24 +27,24 @@ public class CustomizeFilterPipeline implements IdsFilterPipeline { /** * Callback when the program is abnormal * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response - * @param throwable any exception thrown on handler execution, if any. + * @param request current HTTP request + * @param response current HTTP response + * @param throwable any exception thrown on handler execution, if any. */ @Override - public void errorHandle(ServletRequest servletRequest, ServletResponse servletResponse, Throwable throwable) { + public void errorHandle(JapHttpRequest request, JapHttpResponse response, Throwable throwable) { System.out.println("CustomizeFilterPipeline >> errorHandle"); } /** * Operations before business process processing, such as initializing resources, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return boolean */ @Override - public boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + public boolean preHandle(JapHttpRequest request, JapHttpResponse response) { System.out.println("CustomizeFilterPipeline >> preHandle"); return true; } @@ -52,12 +52,12 @@ public class CustomizeFilterPipeline implements IdsFilterPipeline { /** * Intercept the execution of a handler * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return Object */ @Override - public Object postHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + public Object postHandle(JapHttpRequest request, JapHttpResponse response) { System.out.println("CustomizeFilterPipeline >> postHandle"); return null; } @@ -65,11 +65,11 @@ public class CustomizeFilterPipeline implements IdsFilterPipeline { /** * Callback after business process processing is completed, such as recycling resources, recording status, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response */ @Override - public void afterHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + public void afterHandle(JapHttpRequest request, JapHttpResponse response) { System.out.println("CustomizeFilterPipeline >> afterHandle"); } } diff --git a/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeSignInPipeline.java b/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeSignInPipeline.java index 33e9963..06f111a 100644 --- a/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeSignInPipeline.java +++ b/jap-ids/src/test/java/com/fujieid/jap/ids/pipeline/CustomizeSignInPipeline.java @@ -15,11 +15,10 @@ */ package com.fujieid.jap.ids.pipeline; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.ids.model.UserInfo; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; - /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0.0 @@ -29,24 +28,24 @@ public class CustomizeSignInPipeline implements IdsSignInPipeline { /** * Callback when the program is abnormal * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response - * @param throwable any exception thrown on handler execution, if any. + * @param request current HTTP request + * @param response current HTTP response + * @param throwable any exception thrown on handler execution, if any. */ @Override - public void errorHandle(ServletRequest servletRequest, ServletResponse servletResponse, Throwable throwable) { + public void errorHandle(JapHttpRequest request, JapHttpResponse response, Throwable throwable) { System.out.println("CustomizeSignInPipeline >> errorHandle"); } /** * Operations before business process processing, such as initializing resources, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return boolean */ @Override - public boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + public boolean preHandle(JapHttpRequest request, JapHttpResponse response) { System.out.println("CustomizeSignInPipeline >> preHandle"); return true; } @@ -54,12 +53,12 @@ public class CustomizeSignInPipeline implements IdsSignInPipeline { /** * Intercept the execution of a handler * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response * @return UserInfo */ @Override - public UserInfo postHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + public UserInfo postHandle(JapHttpRequest request, JapHttpResponse response) { System.out.println("CustomizeSignInPipeline >> postHandle"); return null; } @@ -67,11 +66,11 @@ public class CustomizeSignInPipeline implements IdsSignInPipeline { /** * Callback after business process processing is completed, such as recycling resources, recording status, etc. * - * @param servletRequest current HTTP request - * @param servletResponse current HTTP response + * @param request current HTTP request + * @param response current HTTP response */ @Override - public void afterHandle(ServletRequest servletRequest, ServletResponse servletResponse) { + public void afterHandle(JapHttpRequest request, JapHttpResponse response) { System.out.println("CustomizeSignInPipeline >> afterHandle"); } } diff --git a/jap-ids/src/test/java/com/fujieid/jap/ids/provider/IdsTokenProviderTest.java b/jap-ids/src/test/java/com/fujieid/jap/ids/provider/IdsTokenProviderTest.java index 7b089bc..afb4eb0 100644 --- a/jap-ids/src/test/java/com/fujieid/jap/ids/provider/IdsTokenProviderTest.java +++ b/jap-ids/src/test/java/com/fujieid/jap/ids/provider/IdsTokenProviderTest.java @@ -39,7 +39,7 @@ public class IdsTokenProviderTest extends BaseIdsTest { @Test public void generateAuthorizationCodeResponse() { this.initParam(); - Assert.assertThrows(InvalidCodeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock)); + Assert.assertThrows(InvalidCodeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, request)); } @Test @@ -47,7 +47,7 @@ public class IdsTokenProviderTest extends BaseIdsTest { this.initParam(); String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L); idsRequestParam.setCode(code); - IdsResponse response = idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock); + IdsResponse response = idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, request); System.out.println(response); Assert.assertNotNull(response); } @@ -58,7 +58,7 @@ public class IdsTokenProviderTest extends BaseIdsTest { String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L); idsRequestParam.setCode(code); idsRequestParam.setClientId("asdasd"); - Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock)); + Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, request)); } @Test @@ -67,7 +67,7 @@ public class IdsTokenProviderTest extends BaseIdsTest { String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L); idsRequestParam.setCode(code); idsRequestParam.setGrantType("asdasd"); - Assert.assertThrows(UnsupportedGrantTypeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock)); + Assert.assertThrows(UnsupportedGrantTypeException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, request)); } @Test @@ -76,7 +76,7 @@ public class IdsTokenProviderTest extends BaseIdsTest { String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L); idsRequestParam.setCode(code); idsRequestParam.setRedirectUri("asdasd"); - Assert.assertThrows(InvalidRedirectUriException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock)); + Assert.assertThrows(InvalidRedirectUriException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, request)); } @Test @@ -85,7 +85,7 @@ public class IdsTokenProviderTest extends BaseIdsTest { String code = oauth2Service.createAuthorizationCode(idsRequestParam, new UserInfo(), 100000L); idsRequestParam.setCode(code); idsRequestParam.setClientSecret("asdasd"); - Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, httpServletRequestMock)); + Assert.assertThrows(InvalidClientException.class, () -> idsTokenProvider.generateAuthorizationCodeResponse(idsRequestParam, request)); } @Test diff --git a/jap-ids/src/test/java/com/fujieid/jap/ids/spi/ServiceLoaderTest.java b/jap-ids/src/test/java/com/fujieid/jap/ids/spi/ServiceLoaderTest.java index 142e867..33b9961 100644 --- a/jap-ids/src/test/java/com/fujieid/jap/ids/spi/ServiceLoaderTest.java +++ b/jap-ids/src/test/java/com/fujieid/jap/ids/spi/ServiceLoaderTest.java @@ -19,6 +19,7 @@ import com.fujieid.jap.core.spi.JapServiceLoader; import com.fujieid.jap.ids.pipeline.IdsFilterPipeline; import com.fujieid.jap.ids.pipeline.IdsPipeline; import com.fujieid.jap.ids.pipeline.IdsSignInPipeline; +import com.fujieid.jap.ids.service.IdsUserService; import org.junit.Test; import java.util.List; @@ -30,6 +31,16 @@ import java.util.List; */ public class ServiceLoaderTest { + @Test + public void idsUserService() { + // initialize + List list = JapServiceLoader.load(IdsUserService.class); + list.forEach(idsPipeline -> { + System.out.println(idsPipeline); + System.out.println(idsPipeline.getClass()); + }); + } + @Test public void loadIdsPipeline() { List list = JapServiceLoader.load(IdsPipeline.class); diff --git a/jap-mfa/pom.xml b/jap-mfa/pom.xml index b2b7334..99e9564 100644 --- a/jap-mfa/pom.xml +++ b/jap-mfa/pom.xml @@ -32,6 +32,16 @@ com.warrenstrange googleauth + + + httpclient + org.apache.httpcomponents + + + + + org.apache.httpcomponents + httpclient diff --git a/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfa.java b/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfa.java index 4567a60..244b03c 100644 --- a/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfa.java +++ b/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfa.java @@ -19,10 +19,10 @@ import cn.hutool.core.img.ImgUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.log.Log; import cn.hutool.log.LogFactory; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.sso.util.QrCodeUtil; import com.warrenstrange.googleauth.*; -import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.io.File; import java.io.IOException; @@ -122,7 +122,7 @@ public class JapMfa { * @param issuer The issuer name. This parameter cannot contain the colon (:) character. * @param response HttpServletResponse */ - public void createOtpQrcode(String username, String issuer, HttpServletResponse response) { + public void createOtpQrcode(String username, String issuer, JapHttpResponse response) { try { QrCodeUtil.generate(getTotpUrl(username, issuer), mfaConfig.getQrcodeWidth(), mfaConfig.getQrcodeHeight(), diff --git a/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfaConfig.java b/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfaConfig.java index 5baa199..0e760e2 100644 --- a/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfaConfig.java +++ b/jap-mfa/src/main/java/com/fujieid/jap/sso/JapMfaConfig.java @@ -36,7 +36,7 @@ public class JapMfaConfig { */ private long period = 30000; /** - * the crypto algorithm (HmacSHA1, HmacSHA256, HmacSHA512) + * the crypto algorithm (HMACSHA1, HMACSHA256, HMACSHA512) */ private JapMfaAlgorithm algorithm = JapMfaAlgorithm.HmacSHA1; diff --git a/jap-mfa/src/main/java/com/fujieid/jap/sso/util/QrCodeUtil.java b/jap-mfa/src/main/java/com/fujieid/jap/sso/util/QrCodeUtil.java index ac5cdc5..5397633 100644 --- a/jap-mfa/src/main/java/com/fujieid/jap/sso/util/QrCodeUtil.java +++ b/jap-mfa/src/main/java/com/fujieid/jap/sso/util/QrCodeUtil.java @@ -262,7 +262,7 @@ public class QrCodeUtil { */ public HashMap toHints() { // 配置 - final HashMap hints = new HashMap<>(); + final HashMap hints = new HashMap<>(7); if (null != this.charset) { hints.put(EncodeHintType.CHARACTER_SET, charset.toString().toLowerCase()); } diff --git a/jap-mfa/src/test/java/com/fujieid/jap/sso/JapMfaTest.java b/jap-mfa/src/test/java/com/fujieid/jap/sso/JapMfaTest.java index bcae1af..351aa88 100644 --- a/jap-mfa/src/test/java/com/fujieid/jap/sso/JapMfaTest.java +++ b/jap-mfa/src/test/java/com/fujieid/jap/sso/JapMfaTest.java @@ -46,8 +46,8 @@ public class JapMfaTest { private static void varifyCode(JapMfa japMfa) { String secretKey = japMfa.getSecretKey(username); - System.out.println("1. 你需要打开生成的文件(或者将 Base64 字符串直接粘贴到浏览器地址会回车)"); - System.out.println("2. 然后使用 OTP 工具扫描二维码"); + System.out.println("1. 点击上方打印的URL链接"); + System.out.println("2. 然后使用 OTP 工具扫描二维码(如果无法识别,请保存到本地后打开本地文件重新进行识别)"); System.out.println("3. 在控制台输入 code"); Scanner scanner = new Scanner(System.in); Integer consoleInput = null; diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java index af5f830..7e94c96 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/OAuthConfig.java @@ -77,12 +77,12 @@ public class OAuthConfig extends AuthenticateConfig { * "token" for requesting an access token (implicit grant) as described by Section 4.2.1 (https://tools.ietf.org/html/rfc6749#section-4.2.1), * or a registered extension value as described by Section 8.4 (https://tools.ietf.org/html/rfc6749#section-8.4). */ - private Oauth2ResponseType responseType = Oauth2ResponseType.none; + private Oauth2ResponseType responseType = Oauth2ResponseType.NONE; /** - * The optional value is: {@code authorization_code}, {@code password}, {@code client_credentials} + * The optional value is: {@code AUTHORIZATION_CODE}, {@code PASSWORD}, {@code CLIENT_CREDENTIALS} */ - private Oauth2GrantType grantType = Oauth2GrantType.authorization_code; + private Oauth2GrantType grantType = Oauth2GrantType.AUTHORIZATION_CODE; /** * The scope supported by the OAuth platform @@ -98,7 +98,7 @@ public class OAuthConfig extends AuthenticateConfig { private String state; /** - * The scope supported by the OAuth platform + * Whether to enable PKCE mode */ private boolean enablePkce; @@ -110,6 +110,11 @@ public class OAuthConfig extends AuthenticateConfig { */ private PkceCodeChallengeMethod codeChallengeMethod = PkceCodeChallengeMethod.S256; + /** + * In pkce mode, the expiration time of codeverifier, in milliseconds, default is 3 minutes + */ + private long codeVerifierTimeout = 180000; + /** * The username in `Resource Owner Password Credentials Grant` * @@ -124,11 +129,6 @@ public class OAuthConfig extends AuthenticateConfig { */ private String password; - /** - * In pkce mode, the expiration time of codeverifier, in milliseconds, default is 3 minutes - */ - private long codeVerifierTimeout = 180000; - /** * When {@code verifyState} is true, it will check whether the state in authorization request is consistent with that in callback request */ diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2GrantType.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2GrantType.java index 6edf9e1..a5902b2 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2GrantType.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2GrantType.java @@ -25,17 +25,17 @@ public enum Oauth2GrantType { /** * Authorization Code Grant */ - authorization_code, + AUTHORIZATION_CODE, /** * Resource Owner Password Credentials Grant */ - password, + PASSWORD, /** * Client Credentials Grant */ - client_credentials, + CLIENT_CREDENTIALS, /** * Refreshing an Access Token */ - refresh_token + REFRESH_TOKEN } diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2ResponseType.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2ResponseType.java index af4e0fb..5fb5610 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2ResponseType.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2ResponseType.java @@ -25,13 +25,13 @@ public enum Oauth2ResponseType { /** * When authorization code mode or implicit authorization mode is not used, ResponseType needs to be set to {@code none} */ - none, + NONE, /** * Authorization Code Grant */ - code, + CODE, /** * Implicit Grant */ - token + TOKEN } diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java index 3e45f8c..c5cf1f3 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Strategy.java @@ -27,14 +27,14 @@ import com.fujieid.jap.core.exception.JapOauth2Exception; import com.fujieid.jap.core.result.JapErrorCode; import com.fujieid.jap.core.result.JapResponse; import com.fujieid.jap.core.strategy.AbstractJapStrategy; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.oauth2.pkce.PkceHelper; import com.fujieid.jap.oauth2.token.AccessToken; import com.fujieid.jap.oauth2.token.AccessTokenHelper; import com.xkcoding.json.util.Kv; import com.xkcoding.json.util.StringUtil; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -82,7 +82,7 @@ public class Oauth2Strategy extends AbstractJapStrategy { * @param response The response to authenticate */ @Override - public JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + public JapResponse authenticate(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { try { Oauth2Util.checkOauthCallbackRequest(request.getParameter("error"), request.getParameter("error_description"), @@ -109,8 +109,8 @@ public class Oauth2Strategy extends AbstractJapStrategy { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); } - boolean isPasswordOrClientMode = authConfig.getGrantType() == Oauth2GrantType.password - || authConfig.getGrantType() == Oauth2GrantType.client_credentials; + boolean isPasswordOrClientMode = authConfig.getGrantType() == Oauth2GrantType.PASSWORD + || authConfig.getGrantType() == Oauth2GrantType.CLIENT_CREDENTIALS; // If it is not a callback request, it must be a request to jump to the authorization link // If it is a password authorization request or a client authorization request, the token will be obtained directly @@ -152,7 +152,7 @@ public class Oauth2Strategy extends AbstractJapStrategy { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); } OAuthConfig authConfig = (OAuthConfig) config; - if (authConfig.getGrantType() != Oauth2GrantType.refresh_token) { + if (authConfig.getGrantType() != Oauth2GrantType.REFRESH_TOKEN) { return JapResponse.error(JapErrorCode.INVALID_GRANT_TYPE); } AccessToken accessToken = null; @@ -233,8 +233,8 @@ public class Oauth2Strategy extends AbstractJapStrategy { String url = null; // 4.1. Authorization Code Grant https://tools.ietf.org/html/rfc6749#section-4.1 // 4.2. Implicit Grant https://tools.ietf.org/html/rfc6749#section-4.2 - if (authConfig.getResponseType() == Oauth2ResponseType.code || - authConfig.getResponseType() == Oauth2ResponseType.token) { + if (authConfig.getResponseType() == Oauth2ResponseType.CODE || + authConfig.getResponseType() == Oauth2ResponseType.TOKEN) { url = generateAuthorizationCodeGrantUrl(authConfig); } return url; @@ -267,7 +267,7 @@ public class Oauth2Strategy extends AbstractJapStrategy { params.put("state", authConfig.getState()); JapAuthentication.getContext().getCache().set(Oauth2Const.STATE_CACHE_KEY.concat(authConfig.getClientId()), state); // Pkce is only applicable to authorization code mode - if (Oauth2ResponseType.code == authConfig.getResponseType() && authConfig.isEnablePkce()) { + if (Oauth2ResponseType.CODE == authConfig.getResponseType() && authConfig.isEnablePkce()) { params.putAll(PkceHelper.generatePkceParameters(authConfig)); } String query = URLUtil.buildQuery(params, StandardCharsets.UTF_8); diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java index de84124..68bf53b 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/Oauth2Util.java @@ -22,12 +22,14 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import com.fujieid.jap.core.context.JapAuthentication; import com.fujieid.jap.core.exception.JapOauth2Exception; +import com.fujieid.jap.core.exception.OidcException; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.oauth2.pkce.PkceCodeChallengeMethod; import com.xkcoding.http.HttpUtil; +import com.xkcoding.http.support.SimpleHttpResponse; import com.xkcoding.json.JsonUtil; import com.xkcoding.json.util.Kv; -import javax.servlet.http.HttpServletRequest; import java.io.Serializable; import java.util.Map; import java.util.Optional; @@ -82,7 +84,7 @@ public class Oauth2Util { } if (responseKv.containsKey("error") && ObjectUtil.isNotEmpty(responseKv.get("error"))) { throw new JapOauth2Exception(Optional.ofNullable(errorMsg).orElse("") + - responseKv.get("error_description") + " " + responseKv.toString()); + responseKv.get("error_description") + " " + responseKv); } } @@ -131,11 +133,11 @@ public class Oauth2Util { // For authorization code mode and implicit authorization mode // refer to: https://tools.ietf.org/html/rfc6749#section-4.1 // refer to: https://tools.ietf.org/html/rfc6749#section-4.2 - if (oAuthConfig.getResponseType() == Oauth2ResponseType.code || - oAuthConfig.getResponseType() == Oauth2ResponseType.token) { + if (oAuthConfig.getResponseType() == Oauth2ResponseType.CODE || + oAuthConfig.getResponseType() == Oauth2ResponseType.TOKEN) { - if (oAuthConfig.getResponseType() == Oauth2ResponseType.code) { - if (oAuthConfig.getGrantType() != Oauth2GrantType.authorization_code) { + if (oAuthConfig.getResponseType() == Oauth2ResponseType.CODE) { + if (oAuthConfig.getGrantType() != Oauth2GrantType.AUTHORIZATION_CODE) { throw new JapOauth2Exception("Invalid grantType `" + oAuthConfig.getGrantType() + "`. " + "When using authorization code mode, grantType must be `authorization_code`"); } @@ -164,11 +166,11 @@ public class Oauth2Util { // For password mode // refer to: https://tools.ietf.org/html/rfc6749#section-4.3 else { - if (oAuthConfig.getGrantType() != Oauth2GrantType.password && oAuthConfig.getGrantType() != Oauth2GrantType.client_credentials) { + if (oAuthConfig.getGrantType() != Oauth2GrantType.PASSWORD && oAuthConfig.getGrantType() != Oauth2GrantType.CLIENT_CREDENTIALS) { throw new JapOauth2Exception("When the response type is none in the oauth2 strategy, a grant type other " + "than the authorization code must be used: " + oAuthConfig.getGrantType()); } - if (oAuthConfig.getGrantType() == Oauth2GrantType.password) { + if (oAuthConfig.getGrantType() == Oauth2GrantType.PASSWORD) { if (!StrUtil.isAllNotEmpty(oAuthConfig.getUsername(), oAuthConfig.getPassword())) { throw new JapOauth2Exception("Oauth2Strategy requires username and password in password certificate grant"); } @@ -186,11 +188,11 @@ public class Oauth2Util { * @param oAuthConfig OAuthConfig * @return When true is returned, the current HTTP request is a callback request */ - public static boolean isCallback(HttpServletRequest request, OAuthConfig oAuthConfig) { - if (oAuthConfig.getResponseType() == Oauth2ResponseType.code) { + public static boolean isCallback(JapHttpRequest request, OAuthConfig oAuthConfig) { + if (oAuthConfig.getResponseType() == Oauth2ResponseType.CODE) { String code = request.getParameter("code"); return !StrUtil.isEmpty(code); - } else if (oAuthConfig.getResponseType() == Oauth2ResponseType.token) { + } else if (oAuthConfig.getResponseType() == Oauth2ResponseType.TOKEN) { String accessToken = request.getParameter("access_token"); return !StrUtil.isEmpty(accessToken); } @@ -212,12 +214,20 @@ public class Oauth2Util { */ public static Kv request(Oauth2EndpointMethodType endpointMethodType, String url, Map params) { - String res = null; + SimpleHttpResponse res = null; if (null == endpointMethodType || Oauth2EndpointMethodType.GET == endpointMethodType) { res = HttpUtil.get(url, params, false); } else { res = HttpUtil.post(url, params, false); } - return JsonUtil.parseKv(res); + + if (!res.isSuccess()) { + throw new JapOauth2Exception("Cannot access url: " + url + + " , method: " + endpointMethodType + + " , params: " + params + + " , error details: " + res.getError() + ); + } + return JsonUtil.parseKv(res.getBody()); } } diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/AccessTokenHelper.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/AccessTokenHelper.java index e0b2189..9564ff8 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/AccessTokenHelper.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/AccessTokenHelper.java @@ -19,17 +19,17 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.fujieid.jap.core.exception.JapOauth2Exception; import com.fujieid.jap.core.util.JapUtil; +import com.fujieid.jap.http.JapHttpRequest; import com.fujieid.jap.oauth2.*; import com.fujieid.jap.oauth2.pkce.PkceHelper; import com.fujieid.jap.oauth2.pkce.PkceParams; import com.xkcoding.json.util.Kv; -import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; /** - * Access token helper. Provides a unified access token method {@link AccessTokenHelper#getToken(HttpServletRequest, OAuthConfig, Object[])} + * Access token helper. Provides a unified access token method {@link AccessTokenHelper#getToken(JapHttpRequest, OAuthConfig, Object[])} * for different authorization methods * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) @@ -49,23 +49,23 @@ public class AccessTokenHelper { * @param obj Optional parameters * @return AccessToken */ - public static AccessToken getToken(HttpServletRequest request, OAuthConfig oAuthConfig, Object... obj) throws JapOauth2Exception { + public static AccessToken getToken(JapHttpRequest request, OAuthConfig oAuthConfig, Object... obj) throws JapOauth2Exception { if (null == oAuthConfig) { throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken. OAuthConfig cannot be empty."); } - if (oAuthConfig.getResponseType() == Oauth2ResponseType.code) { + if (oAuthConfig.getResponseType() == Oauth2ResponseType.CODE) { return getAccessTokenOfAuthorizationCodeMode(request, oAuthConfig); } - if (oAuthConfig.getResponseType() == Oauth2ResponseType.token) { + if (oAuthConfig.getResponseType() == Oauth2ResponseType.TOKEN) { return getAccessTokenOfImplicitMode(request); } - if (oAuthConfig.getGrantType() == Oauth2GrantType.password) { + if (oAuthConfig.getGrantType() == Oauth2GrantType.PASSWORD) { return getAccessTokenOfPasswordMode(oAuthConfig); } - if (oAuthConfig.getGrantType() == Oauth2GrantType.client_credentials) { + if (oAuthConfig.getGrantType() == Oauth2GrantType.CLIENT_CREDENTIALS) { return getAccessTokenOfClientMode(request, oAuthConfig); } - if (oAuthConfig.getGrantType() == Oauth2GrantType.refresh_token) { + if (oAuthConfig.getGrantType() == Oauth2GrantType.REFRESH_TOKEN) { String refreshToken = null; if (null == obj || obj.length == 0 || null == obj[0] || (refreshToken = String.valueOf(obj[0])).isEmpty()) { throw new JapOauth2Exception("Failed to refresh token, refresh_token is empty."); @@ -84,13 +84,13 @@ public class AccessTokenHelper { * @return token request url * @see 4.1. Authorization Code Grant */ - private static AccessToken getAccessTokenOfAuthorizationCodeMode(HttpServletRequest request, OAuthConfig oAuthConfig) throws JapOauth2Exception { + private static AccessToken getAccessTokenOfAuthorizationCodeMode(JapHttpRequest request, OAuthConfig oAuthConfig) throws JapOauth2Exception { String state = request.getParameter("state"); Oauth2Util.checkState(state, oAuthConfig.getClientId(), oAuthConfig.isVerifyState()); String code = request.getParameter("code"); Map params = new HashMap<>(6); - params.put("grant_type", Oauth2GrantType.authorization_code.name()); + params.put("grant_type", Oauth2GrantType.AUTHORIZATION_CODE.name()); params.put("code", code); params.put("client_id", oAuthConfig.getClientId()); params.put("client_secret", oAuthConfig.getClientSecret()); @@ -101,7 +101,7 @@ public class AccessTokenHelper { params.put("scope", String.join(Oauth2Const.SCOPE_SEPARATOR, oAuthConfig.getScopes())); } // PKCE is only applicable to authorization code mode - if (Oauth2ResponseType.code == oAuthConfig.getResponseType() && oAuthConfig.isEnablePkce()) { + if (Oauth2ResponseType.CODE == oAuthConfig.getResponseType() && oAuthConfig.isEnablePkce()) { params.put(PkceParams.CODE_VERIFIER, PkceHelper.getCacheCodeVerifier(oAuthConfig.getClientId())); } @@ -122,7 +122,7 @@ public class AccessTokenHelper { * @return token request url * @see 4.2. Implicit Grant */ - private static AccessToken getAccessTokenOfImplicitMode(HttpServletRequest request) throws JapOauth2Exception { + private static AccessToken getAccessTokenOfImplicitMode(JapHttpRequest request) throws JapOauth2Exception { Oauth2Util.checkOauthCallbackRequest(request.getParameter("error"), request.getParameter("error_description"), "Oauth2Strategy failed to get AccessToken."); @@ -148,7 +148,7 @@ public class AccessTokenHelper { */ private static AccessToken getAccessTokenOfPasswordMode(OAuthConfig oAuthConfig) throws JapOauth2Exception { Map params = new HashMap<>(6); - params.put("grant_type", Oauth2GrantType.password.name()); + params.put("grant_type", Oauth2GrantType.PASSWORD.name()); params.put("username", oAuthConfig.getUsername()); params.put("password", oAuthConfig.getPassword()); params.put("client_id", oAuthConfig.getClientId()); @@ -172,7 +172,7 @@ public class AccessTokenHelper { * @return token request url * @see 4.4. Client Credentials Grant */ - private static AccessToken getAccessTokenOfClientMode(HttpServletRequest request, OAuthConfig oAuthConfig) throws JapOauth2Exception { + private static AccessToken getAccessTokenOfClientMode(JapHttpRequest request, OAuthConfig oAuthConfig) throws JapOauth2Exception { throw new JapOauth2Exception("Oauth2Strategy failed to get AccessToken. Grant type of client_credentials type is not supported."); // Map params = Maps.newHashMap(); // params.put("grant_type", Oauth2GrantType.client_credentials.name()); diff --git a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/package-info.java b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/package-info.java index ab7dd4a..439093d 100644 --- a/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/package-info.java +++ b/jap-oauth2/src/main/java/com/fujieid/jap/oauth2/token/package-info.java @@ -16,7 +16,7 @@ /** * To provide processing methods for business in OAuth authorization process, the usage is as follows: *

    - * {@link com.fujieid.jap.oauth2.token.AccessTokenHelper#getToken(HttpServletRequest, OAuthConfig, Object[])} + * {@link com.fujieid.jap.oauth2.token.AccessTokenHelper#getToken(com.fujieid.jap.http.JapHttpRequest, OAuthConfig, Object[])} * According to the parameters in {@link com.fujieid.jap.oauth2.OAuthConfig}, determine which authorization mode token data to obtain *

    * For OAuth's grant type, the following private methods are provided: @@ -36,6 +36,5 @@ */ package com.fujieid.jap.oauth2.token; -import com.fujieid.jap.oauth2.OAuthConfig; -import javax.servlet.http.HttpServletRequest; +import com.fujieid.jap.oauth2.OAuthConfig; diff --git a/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2StrategyTest.java b/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2StrategyTest.java index adb1acc..b308e62 100644 --- a/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2StrategyTest.java +++ b/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2StrategyTest.java @@ -21,6 +21,10 @@ import com.fujieid.jap.core.cache.JapCache; import com.fujieid.jap.core.config.AuthenticateConfig; import com.fujieid.jap.core.config.JapConfig; import com.fujieid.jap.core.result.JapResponse; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; +import com.fujieid.jap.http.jakarta.JakartaResponseAdapter; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -44,6 +48,8 @@ import static org.mockito.Mockito.when; */ public class Oauth2StrategyTest { + public JapHttpRequest request; + public JapHttpResponse response; @Mock private HttpServletRequest httpServletRequestMock; @Mock @@ -56,6 +62,8 @@ public class Oauth2StrategyTest { MockitoAnnotations.initMocks(this); // Arrange when(httpServletRequestMock.getSession()).thenReturn(httpsSessionMock); + this.request = new JakartaRequestAdapter(httpServletRequestMock); + this.response = new JakartaResponseAdapter(httpServletResponseMock); } @Test @@ -99,7 +107,7 @@ public class Oauth2StrategyTest { JapUserService japUserService = getJapUserService(); Oauth2Strategy oauth2Strategy = new Oauth2Strategy(japUserService, new JapConfig()); - JapResponse response = oauth2Strategy.authenticate(null, httpServletRequestMock, httpServletResponseMock); + JapResponse response = oauth2Strategy.authenticate(null, this.request, this.response); Assert.assertEquals(1005, response.getCode()); } @@ -108,7 +116,7 @@ public class Oauth2StrategyTest { JapUserService japUserService = getJapUserService(); Oauth2Strategy oauth2Strategy = new Oauth2Strategy(japUserService, new JapConfig()); - JapResponse response = oauth2Strategy.authenticate(new NotOAuthConfig(), httpServletRequestMock, httpServletResponseMock); + JapResponse response = oauth2Strategy.authenticate(new NotOAuthConfig(), this.request, this.response); Assert.assertEquals(500, response.getCode()); } @@ -119,11 +127,11 @@ public class Oauth2StrategyTest { // Redirect to authorization url oauth2Strategy.authenticate(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId("ClientId") .setAuthorizationUrl("AuthorizationUrl") - .setUserinfoUrl("UserinfoUrl"), httpServletRequestMock, httpServletResponseMock); + .setUserinfoUrl("UserinfoUrl"), this.request, this.response); } private JapUserService getJapUserService() { diff --git a/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2UtilTest.java b/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2UtilTest.java index 38a97cb..a52828b 100644 --- a/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2UtilTest.java +++ b/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/Oauth2UtilTest.java @@ -20,8 +20,9 @@ import com.fujieid.jap.core.cache.JapLocalCache; import com.fujieid.jap.core.context.JapAuthentication; import com.fujieid.jap.core.context.JapContext; import com.fujieid.jap.core.exception.JapOauth2Exception; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; import com.fujieid.jap.oauth2.pkce.PkceCodeChallengeMethod; -import com.fujieid.jap.oauth2.token.AccessTokenHelper; import com.xkcoding.json.util.Kv; import org.junit.Assert; import org.junit.Before; @@ -40,12 +41,14 @@ import javax.servlet.http.HttpServletRequest; */ public class Oauth2UtilTest { + public JapHttpRequest request; @Mock private HttpServletRequest httpServletRequestMock; @Before public void init() { MockitoAnnotations.initMocks(this); + this.request = new JakartaRequestAdapter(httpServletRequestMock); } @Test @@ -189,8 +192,8 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeResponseTypeAndGrantTypeIsNotAuthorizationCode() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.code) - .setGrantType(Oauth2GrantType.password))); + .setResponseType(Oauth2ResponseType.CODE) + .setGrantType(Oauth2GrantType.PASSWORD))); } @@ -198,8 +201,8 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeResponseTypeAndClientSecretIsNullWhenPkceIsNotEnabled() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.code) - .setGrantType(Oauth2GrantType.authorization_code) + .setResponseType(Oauth2ResponseType.CODE) + .setGrantType(Oauth2GrantType.AUTHORIZATION_CODE) .setClientSecret(null))); } @@ -208,8 +211,8 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeResponseTypeAndClientSecretIsEmptyWhenPkceIsNotEnabled() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.code) - .setGrantType(Oauth2GrantType.authorization_code) + .setResponseType(Oauth2ResponseType.CODE) + .setGrantType(Oauth2GrantType.AUTHORIZATION_CODE) .setClientSecret(""))); } @@ -218,8 +221,8 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeResponseTypeAndClientSecretIsNullWhenPkceIsEnabled() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.code) - .setGrantType(Oauth2GrantType.authorization_code) + .setResponseType(Oauth2ResponseType.CODE) + .setGrantType(Oauth2GrantType.AUTHORIZATION_CODE) .setEnablePkce(true) .setClientSecret(null))); } @@ -229,8 +232,8 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeResponseTypeAndClientSecretIsEmptyWhenPkceIsEnabled() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.code) - .setGrantType(Oauth2GrantType.authorization_code) + .setResponseType(Oauth2ResponseType.CODE) + .setGrantType(Oauth2GrantType.AUTHORIZATION_CODE) .setEnablePkce(true) .setClientSecret(""))); } @@ -240,8 +243,8 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeResponseTypeAndClientSecretIsNotEmptyWhenPkceIsEnabled() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.code) - .setGrantType(Oauth2GrantType.authorization_code) + .setResponseType(Oauth2ResponseType.CODE) + .setGrantType(Oauth2GrantType.AUTHORIZATION_CODE) .setEnablePkce(true) .setClientSecret("ClientSecret"))); } @@ -251,7 +254,7 @@ public class Oauth2UtilTest { public void checkOauthConfigTokenResponseTypeAndClientSecretIsEmpty() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret(""))); } @@ -260,7 +263,7 @@ public class Oauth2UtilTest { public void checkOauthConfigTokenResponseTypeAndClientSecretIsNull() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret(null))); } @@ -269,7 +272,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseTypeAndClientIdIsNull() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId(null))); } @@ -278,7 +281,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseTypeAndClientIdIsEmpty() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId(""))); } @@ -287,7 +290,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseTypeAndAuthorizationUrlIsEmpty() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId("ClientId") .setAuthorizationUrl(""))); @@ -297,7 +300,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseTypeAndAuthorizationUrlIsNull() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId("ClientId") .setAuthorizationUrl(null))); @@ -307,7 +310,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseTypeAndUserinfoUrlIsNull() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId("ClientId") .setAuthorizationUrl("AuthorizationUrl") @@ -318,7 +321,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseTypeAndUserinfoUrlIsEmpty() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId("ClientId") .setAuthorizationUrl("AuthorizationUrl") @@ -329,7 +332,7 @@ public class Oauth2UtilTest { public void checkOauthConfigCodeOrTokenResponseType() { Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setResponseType(Oauth2ResponseType.token) + .setResponseType(Oauth2ResponseType.TOKEN) .setClientSecret("ClientSecret") .setClientId("ClientId") .setAuthorizationUrl("AuthorizationUrl") @@ -340,43 +343,43 @@ public class Oauth2UtilTest { public void checkOauthConfigClientCredentialsGrantType() { Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setGrantType(Oauth2GrantType.client_credentials)); + .setGrantType(Oauth2GrantType.CLIENT_CREDENTIALS)); } @Test public void checkOauthConfigPasswordGrantTypeAndNullUsernameAndPassword() { Assert.assertThrows(JapOauth2Exception.class, () -> Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setGrantType(Oauth2GrantType.password))); + .setGrantType(Oauth2GrantType.PASSWORD))); } @Test public void checkOauthConfigPasswordGrantTypeAndHasUsernameAndPassword() { Oauth2Util.checkOauthConfig(new OAuthConfig() .setTokenUrl("TokenUrl") - .setGrantType(Oauth2GrantType.password) + .setGrantType(Oauth2GrantType.PASSWORD) .setUsername("username") .setPassword("password")); } @Test public void isCallbackCodeResponseType() { - boolean res = Oauth2Util.isCallback(httpServletRequestMock, new OAuthConfig() - .setResponseType(Oauth2ResponseType.code)); + boolean res = Oauth2Util.isCallback(request, new OAuthConfig() + .setResponseType(Oauth2ResponseType.CODE)); Assert.assertFalse(res); } @Test public void isCallbackTokenResponseType() { - boolean res = Oauth2Util.isCallback(httpServletRequestMock, new OAuthConfig() - .setResponseType(Oauth2ResponseType.token)); + boolean res = Oauth2Util.isCallback(request, new OAuthConfig() + .setResponseType(Oauth2ResponseType.TOKEN)); Assert.assertFalse(res); } @Test public void isCallbackNoneResponseType() { - boolean res = Oauth2Util.isCallback(httpServletRequestMock, new OAuthConfig() - .setResponseType(Oauth2ResponseType.none)); + boolean res = Oauth2Util.isCallback(request, new OAuthConfig() + .setResponseType(Oauth2ResponseType.NONE)); Assert.assertFalse(res); } } diff --git a/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/token/AccessTokenHelperTest.java b/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/token/AccessTokenHelperTest.java index ed7f261..92f39bc 100644 --- a/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/token/AccessTokenHelperTest.java +++ b/jap-oauth2/src/test/java/com/fujieid/jap/oauth2/token/AccessTokenHelperTest.java @@ -20,6 +20,9 @@ import com.fujieid.jap.core.cache.JapLocalCache; import com.fujieid.jap.core.context.JapAuthentication; import com.fujieid.jap.core.context.JapContext; import com.fujieid.jap.core.exception.JapOauth2Exception; +import com.fujieid.jap.core.exception.OidcException; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; import com.fujieid.jap.oauth2.OAuthConfig; import com.fujieid.jap.oauth2.Oauth2GrantType; import com.fujieid.jap.oauth2.Oauth2ResponseType; @@ -40,45 +43,47 @@ import javax.servlet.http.HttpServletRequest; */ public class AccessTokenHelperTest { + public JapHttpRequest request; @Mock private HttpServletRequest httpServletRequestMock; @Before public void init() { MockitoAnnotations.initMocks(this); + this.request = new JakartaRequestAdapter(httpServletRequestMock); } @Test public void getTokenNullOAuthConfig() { - Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, null)); + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, null)); } @Test public void getTokenEmptyOAuthConfig() { - Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig())); + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig())); } @Test public void getTokenCodeResponseType() { - Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() - .setResponseType(Oauth2ResponseType.code))); + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() + .setResponseType(Oauth2ResponseType.CODE))); } @Test public void getTokenCodeResponseTypeDoNotCheckState() { // Http url must be not blank! - Assert.assertThrows(IllegalArgumentException.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() + Assert.assertThrows(IllegalArgumentException.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() .setVerifyState(false) - .setResponseType(Oauth2ResponseType.code))); + .setResponseType(Oauth2ResponseType.CODE))); } @Test public void getTokenCodeResponseTypeNullCache() { JapAuthentication.setContext(new JapContext()); JapAuthentication.getContext().setCache(null); - Assert.assertThrows(NullPointerException.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() + Assert.assertThrows(NullPointerException.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() .setVerifyState(false) - .setResponseType(Oauth2ResponseType.code) + .setResponseType(Oauth2ResponseType.CODE) .setEnablePkce(true) .setCallbackUrl("setCallbackUrl") .setTokenUrl("setTokenUrl"))); @@ -89,9 +94,9 @@ public class AccessTokenHelperTest { // UnknownHostException: setTokenUrl JapAuthentication.setContext(new JapContext()); JapAuthentication.getContext().setCache(new JapLocalCache()); - Assert.assertThrows(IORuntimeException.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() .setVerifyState(false) - .setResponseType(Oauth2ResponseType.code) + .setResponseType(Oauth2ResponseType.CODE) .setEnablePkce(true) .setCallbackUrl("setCallbackUrl") .setTokenUrl("setTokenUrl"))); @@ -100,23 +105,23 @@ public class AccessTokenHelperTest { @Test public void getTokenTokenResponseType() { // Oauth2Strategy failed to get AccessToken. - Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() - .setResponseType(Oauth2ResponseType.token))); + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() + .setResponseType(Oauth2ResponseType.TOKEN))); } @Test public void getTokenPasswordGrantTypeNullTokenUrl() { // Http url must be not blank! - Assert.assertThrows(IllegalArgumentException.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() - .setGrantType(Oauth2GrantType.password) + Assert.assertThrows(IllegalArgumentException.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() + .setGrantType(Oauth2GrantType.PASSWORD) .setScopes(new String[]{"read"}))); } @Test public void getTokenPasswordGrantTypeErrorTokenUrl() { // UnknownHostException: setTokenUrl - Assert.assertThrows(IORuntimeException.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() - .setGrantType(Oauth2GrantType.password) + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() + .setGrantType(Oauth2GrantType.PASSWORD) .setScopes(new String[]{"read"}) .setTokenUrl("setTokenUrl"))); } @@ -124,8 +129,8 @@ public class AccessTokenHelperTest { @Test public void getTokenClientCredentialsGrantTypeErrorTokenUrl() { // UnknownHostException: setTokenUrl - Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(httpServletRequestMock, new OAuthConfig() - .setGrantType(Oauth2GrantType.client_credentials) + Assert.assertThrows(JapOauth2Exception.class, () -> AccessTokenHelper.getToken(request, new OAuthConfig() + .setGrantType(Oauth2GrantType.CLIENT_CREDENTIALS) .setScopes(new String[]{"read"}) .setTokenUrl("setTokenUrl"))); } diff --git a/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcStrategy.java b/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcStrategy.java index 1f50fcc..68adb0c 100644 --- a/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcStrategy.java +++ b/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcStrategy.java @@ -25,12 +25,11 @@ import com.fujieid.jap.core.exception.JapException; import com.fujieid.jap.core.exception.OidcException; import com.fujieid.jap.core.result.JapErrorCode; import com.fujieid.jap.core.result.JapResponse; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.oauth2.OAuthConfig; import com.fujieid.jap.oauth2.Oauth2Strategy; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - /** * OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. * It enables Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, @@ -72,7 +71,7 @@ public class OidcStrategy extends Oauth2Strategy { * @param response The response to authenticate */ @Override - public JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + public JapResponse authenticate(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { try { this.checkAuthenticateConfig(config, OidcConfig.class); diff --git a/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcUtil.java b/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcUtil.java index a52bfdb..65dec81 100644 --- a/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcUtil.java +++ b/jap-oidc/src/main/java/com/fujieid/jap/oidc/OidcUtil.java @@ -19,6 +19,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.fujieid.jap.core.exception.OidcException; import com.xkcoding.http.HttpUtil; +import com.xkcoding.http.support.SimpleHttpResponse; import com.xkcoding.json.JsonUtil; import com.xkcoding.json.util.Kv; @@ -43,13 +44,12 @@ public class OidcUtil { } String discoveryUrl = issuer.concat(DISCOVERY_URL); - String response = null; - try { - response = HttpUtil.get(discoveryUrl); - } catch (Exception e) { - throw new OidcException("Cannot access discovery url: " + discoveryUrl); + SimpleHttpResponse response = HttpUtil.get(discoveryUrl); + if(!response.isSuccess()) { + throw new OidcException("Cannot access discovery url: " + discoveryUrl + ", " + response.getError()); } - Kv oidcDiscoveryInfo = JsonUtil.parseKv(response); + + Kv oidcDiscoveryInfo = JsonUtil.parseKv(response.getBody()); if (CollectionUtil.isEmpty(oidcDiscoveryInfo)) { throw new OidcException("Unable to parse IDP service discovery configuration information."); } diff --git a/jap-simple/src/main/java/com/fujieid/jap/simple/JapAuthenticationDetails.java b/jap-simple/src/main/java/com/fujieid/jap/simple/JapAuthenticationDetails.java index 1cdf0f1..6d8bfda 100644 --- a/jap-simple/src/main/java/com/fujieid/jap/simple/JapAuthenticationDetails.java +++ b/jap-simple/src/main/java/com/fujieid/jap/simple/JapAuthenticationDetails.java @@ -15,10 +15,10 @@ */ package com.fujieid.jap.simple; -import com.fujieid.jap.core.util.RequestUtil; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpSession; +import com.fujieid.jap.http.RequestUtil; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.io.Serializable; /** @@ -38,13 +38,13 @@ public class JapAuthenticationDetails implements Serializable { private final String sessionId; - public JapAuthenticationDetails(HttpServletRequest request) { + public JapAuthenticationDetails(JapHttpRequest request) { this.clientIp = RequestUtil.getIp(request); this.remoteAddress = request.getRemoteAddr(); - this.userAgent = RequestUtil.getHeader("user-agent", request); + this.userAgent = RequestUtil.getUa(request); - HttpSession session = request.getSession(false); + JapHttpSession session = request.getSession(); this.sessionId = (session != null) ? session.getId() : null; } diff --git a/jap-simple/src/main/java/com/fujieid/jap/simple/RememberMeUtils.java b/jap-simple/src/main/java/com/fujieid/jap/simple/RememberMeUtils.java index 42322b9..404da43 100644 --- a/jap-simple/src/main/java/com/fujieid/jap/simple/RememberMeUtils.java +++ b/jap-simple/src/main/java/com/fujieid/jap/simple/RememberMeUtils.java @@ -22,8 +22,8 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.digest.MD5; import com.fujieid.jap.core.exception.JapException; import com.fujieid.jap.core.result.JapErrorCode; +import com.fujieid.jap.http.JapHttpRequest; -import javax.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import static com.fujieid.jap.core.JapConst.DEFAULT_DELIMITER; @@ -48,7 +48,7 @@ public class RememberMeUtils { * @param simpleConfig simpleConfig * @return boolean */ - public static boolean enableRememberMe(HttpServletRequest request, SimpleConfig simpleConfig) { + public static boolean enableRememberMe(JapHttpRequest request, SimpleConfig simpleConfig) { return BooleanUtil.toBoolean(request.getParameter(simpleConfig.getRememberMeField())); } diff --git a/jap-simple/src/main/java/com/fujieid/jap/simple/SimpleStrategy.java b/jap-simple/src/main/java/com/fujieid/jap/simple/SimpleStrategy.java index 315041b..3818469 100644 --- a/jap-simple/src/main/java/com/fujieid/jap/simple/SimpleStrategy.java +++ b/jap-simple/src/main/java/com/fujieid/jap/simple/SimpleStrategy.java @@ -26,11 +26,10 @@ import com.fujieid.jap.core.exception.JapException; import com.fujieid.jap.core.result.JapErrorCode; import com.fujieid.jap.core.result.JapResponse; import com.fujieid.jap.core.strategy.AbstractJapStrategy; -import com.fujieid.jap.core.util.RequestUtil; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import com.fujieid.jap.http.JapHttpCookie; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.RequestUtil; /** * The local authentication strategy authenticates requests based on the credentials submitted through an HTML-based @@ -64,7 +63,7 @@ public class SimpleStrategy extends AbstractJapStrategy { } @Override - public JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + public JapResponse authenticate(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { // Convert AuthenticateConfig to SimpleConfig try { this.checkAuthenticateConfig(config, SimpleConfig.class); @@ -109,7 +108,7 @@ public class SimpleStrategy extends AbstractJapStrategy { * @param request The request to authenticate * @param response The response to authenticate */ - private JapResponse loginSuccess(SimpleConfig simpleConfig, UsernamePasswordCredential credential, JapUser user, HttpServletRequest request, HttpServletResponse response) { + private JapResponse loginSuccess(SimpleConfig simpleConfig, UsernamePasswordCredential credential, JapUser user, JapHttpRequest request, JapHttpResponse response) { if (credential.isRememberMe()) { String cookieDomain = ObjectUtil.isNotEmpty(simpleConfig.getRememberMeCookieDomain()) ? simpleConfig.getRememberMeCookieDomain() : null; // add cookie @@ -132,7 +131,7 @@ public class SimpleStrategy extends AbstractJapStrategy { * @param response The response to authenticate * @return true to login success, false to login */ - private JapUser checkSessionAndCookie(SimpleConfig simpleConfig, HttpServletRequest request, HttpServletResponse response) throws JapException { + private JapUser checkSessionAndCookie(SimpleConfig simpleConfig, JapHttpRequest request, JapHttpResponse response) throws JapException { JapUser sessionUser = this.checkSession(request, response); if (null != sessionUser) { return sessionUser; @@ -141,7 +140,7 @@ public class SimpleStrategy extends AbstractJapStrategy { return null; } - Cookie cookie = RequestUtil.getCookie(request, simpleConfig.getRememberMeCookieKey()); + JapHttpCookie cookie = RequestUtil.getCookie(request, simpleConfig.getRememberMeCookieKey()); if (ObjectUtil.isNull(cookie)) { return null; } @@ -193,7 +192,7 @@ public class SimpleStrategy extends AbstractJapStrategy { * @param simpleConfig Authenticate Config * @return Username password credential */ - private UsernamePasswordCredential doResolveCredential(HttpServletRequest request, SimpleConfig simpleConfig) { + private UsernamePasswordCredential doResolveCredential(JapHttpRequest request, SimpleConfig simpleConfig) { String username = request.getParameter(simpleConfig.getUsernameField()); String password = request.getParameter(simpleConfig.getPasswordField()); if (null == username || null == password) { diff --git a/jap-social/src/main/java/com/fujieid/jap/social/JustAuthRequestContext.java b/jap-social/src/main/java/com/fujieid/jap/social/JustAuthRequestContext.java index df08641..6c5e099 100644 --- a/jap-social/src/main/java/com/fujieid/jap/social/JustAuthRequestContext.java +++ b/jap-social/src/main/java/com/fujieid/jap/social/JustAuthRequestContext.java @@ -63,8 +63,8 @@ public class JustAuthRequestContext { /** * Default classes that do not need to be registered */ - private static final String[] DEFAULT_EXCLUSION_CLASS_NAMES = {"AuthDefaultRequest", - "AbstractAuthWeChatEnterpriseRequest", "AuthRequest"}; + private static final String[] DEFAULT_EXCLUSION_CLASS_NAMES = {"AuthRequest", "AuthDefaultRequest", + "AbstractAuthWeChatEnterpriseRequest", "AbstractAuthMicrosoftRequest", "AbstractAuthDingtalkRequest"}; /** * When the value is {@code true}, the {@link JustAuthRequestContext#loadRequest(String[], String[])} @@ -154,15 +154,24 @@ public class JustAuthRequestContext { case "WeChatMp": source = "WECHAT_MP"; break; - case "StackOverflow": - source = "STACK_OVERFLOW"; - break; case "WeChatEnterpriseQrcode": source = "WECHAT_ENTERPRISE"; break; + case "WeChatEnterpriseThirdQrcode": + source = "WECHAT_ENTERPRISE_QRCODE_THIRD"; + break; case "WeChatEnterpriseWeb": source = "WECHAT_ENTERPRISE_WEB"; break; + case "StackOverflow": + source = "STACK_OVERFLOW"; + break; + case "DingTalkAccount": + source = "DINGTALK_ACCOUNT"; + break; + case "MicrosoftCn": + source = "MICROSOFT_CN"; + break; default: break; } diff --git a/jap-social/src/main/java/com/fujieid/jap/social/SocialConfig.java b/jap-social/src/main/java/com/fujieid/jap/social/SocialConfig.java index 323f392..9a6ff21 100644 --- a/jap-social/src/main/java/com/fujieid/jap/social/SocialConfig.java +++ b/jap-social/src/main/java/com/fujieid/jap/social/SocialConfig.java @@ -56,6 +56,18 @@ public class SocialConfig extends AuthenticateConfig { */ private String[] exclusionClassNames; + /** + * Whether the mark is an operation of binding a third-party user. + */ + private boolean bindUser; + + /** + * The user id that needs to be bound, this is a user of the business system, not a user of the third-party platform. + *

    + * When {@link com.fujieid.jap.social.SocialConfig#bindUser} is true, {@code bindUserId} cannot be empty + */ + private String bindUserId; + public String getPlatform() { return platform; } @@ -100,4 +112,22 @@ public class SocialConfig extends AuthenticateConfig { this.exclusionClassNames = exclusionClassNames; return this; } + + public boolean isBindUser() { + return bindUser; + } + + public SocialConfig setBindUser(boolean bindUser) { + this.bindUser = bindUser; + return this; + } + + public String getBindUserId() { + return bindUserId; + } + + public SocialConfig setBindUserId(String bindUserId) { + this.bindUserId = bindUserId; + return this; + } } diff --git a/jap-social/src/main/java/com/fujieid/jap/social/SocialFunc.java b/jap-social/src/main/java/com/fujieid/jap/social/SocialFunc.java new file mode 100644 index 0000000..8b82968 --- /dev/null +++ b/jap-social/src/main/java/com/fujieid/jap/social/SocialFunc.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2040, 北京符节科技有限公司 (support@fujieid.com & https://www.fujieid.com). + *

    + * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

    + * http://www.gnu.org/licenses/lgpl.html + *

    + * 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 com.fujieid.jap.social; + +import com.fujieid.jap.core.JapUser; +import com.fujieid.jap.core.result.JapResponse; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; + +/** + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0.0 + * @since 1.0.0 + */ +@FunctionalInterface +public interface SocialFunc { + + /** + * The function interface in social strategy is responsible for handling the business logic after the third-party user logs in successfully. + * + * @param japUser jap user + * @param request current request + * @param response current response + * @return Jap Response + */ + JapResponse exec(JapUser japUser, JapHttpRequest request, JapHttpResponse response); +} diff --git a/jap-social/src/main/java/com/fujieid/jap/social/SocialStrategy.java b/jap-social/src/main/java/com/fujieid/jap/social/SocialStrategy.java index 616fa47..cf66633 100644 --- a/jap-social/src/main/java/com/fujieid/jap/social/SocialStrategy.java +++ b/jap-social/src/main/java/com/fujieid/jap/social/SocialStrategy.java @@ -31,6 +31,10 @@ import com.fujieid.jap.core.exception.JapUserException; import com.fujieid.jap.core.result.JapErrorCode; import com.fujieid.jap.core.result.JapResponse; import com.fujieid.jap.core.strategy.AbstractJapStrategy; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; +import com.fujieid.jap.http.RequestUtil; +import com.xkcoding.json.JsonUtil; import me.zhyd.oauth.cache.AuthStateCache; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthDefaultSource; @@ -40,9 +44,8 @@ import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.request.AuthRequest; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; +import java.util.HashMap; import java.util.Map; /** @@ -60,6 +63,8 @@ import java.util.Map; */ public class SocialStrategy extends AbstractJapStrategy { + private static final String BIND_USERID_PREFIX_CACHE_KEY = "jap:social:bind:userid:"; + private static final String BIND_REFERER_PREFIX_CACHE_KEY = "jap:social:bind:referer:"; private AuthStateCache authStateCache; /** @@ -116,9 +121,49 @@ public class SocialStrategy extends AbstractJapStrategy { this.authStateCache = authStateCache; } + /** + * Log in through a third-party platform. + *

    + * 1. The user initiates a login request. + *

    + * 2. Judge whether the user is logged in, if it is logged in, go directly to step [9], otherwise go to step [3]. + *

    + * 3. Obtain {@link AuthRequest} according to {@link AuthenticateConfig}. + *

    + * 4. Determine whether the current request is a third-party callback request. + * If it is a third-party callback request, parse the callback parameters and skip to step [6] to log in. + * If it is not a third-party callback request, go to step [5]. + *

    + * 5. Create an authorization link and return to the front end to jump. + *

    + * 6. Use the third-party callback parameter code to exchange token, token to exchange user info. + *

    + * 7. Determine whether the current third-party user exists in the database, + * if it exists, go directly to step [9], otherwise go to step [8]. + *

    + * 8. Save third-party user information in the database. + *

    + * 9. Log in successfully. + * + * @param config {@link AuthenticateConfig}, the actual type is {@link SocialConfig} + * @param request The request to authenticate + * @param response The response to authenticate + * @return JapResponse + */ @Override - public JapResponse authenticate(AuthenticateConfig config, HttpServletRequest request, HttpServletResponse response) { + public JapResponse authenticate(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { + SocialConfig socialConfig = null; + try { + this.checkAuthenticateConfig(config, SocialConfig.class); + socialConfig = (SocialConfig) config; + } catch (JapException e) { + return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); + } + + if (socialConfig.isBindUser()) { + return this.bind(config, request, response); + } JapUser sessionUser = this.checkSession(request, response); if (null != sessionUser) { return JapResponse.success(sessionUser); @@ -130,27 +175,123 @@ public class SocialStrategy extends AbstractJapStrategy { } catch (JapException e) { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); } - SocialConfig socialConfig = (SocialConfig) config; + String source = socialConfig.getPlatform(); AuthCallback authCallback = this.parseRequest(request); - // If it is not a callback request, it must be a request to jump to the authorization link - if (!this.isCallback(source, authCallback)) { - String url = authRequest.authorize(socialConfig.getState()); - return JapResponse.success(url); + if (this.isCallback(source, authCallback)) { + try { + return this.login(request, response, source, authRequest, authCallback, this::loginSuccess); + } catch (JapUserException e) { + return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); + } } + // If it is not a callback request, it must be a request to jump to the authorization link + String url = authRequest.authorize(socialConfig.getState()); + return JapResponse.success(url); + } + + /** + * Bind the account of the third-party platform. + *

    + * 1. The user initiates a request to bind an account and caches the current referer address (optional, that is, + * the address when the binding is initiated, and can jump back to the address after the binding is successful). + *

    + * 2. Obtain {@link AuthRequest} according to {@link AuthenticateConfig}. + *

    + * 3. Determine whether the current request is a third-party callback request. + * If it is a third-party callback request, parse the callback parameters and skip to step [5] for binding. + * If it is not a third-party callback request, go to step [4]. + *

    + * 4. Create an authorized connection (need to mark the unique identifier of the currently logged in user), + * and return to the front end to jump. + *

    + * 4.1 You can put the user id in the state. + *

    + * 4.2 The user id and state can be cached correspondingly, the key value is state. + *

    + * 5. Use the third-party callback parameter code to exchange token, token to exchange user info, + * and parse state to obtain the system user ID to be bound. + *

    + * 6. Determine whether the current third-party user exists in the database, + * if it exists, go directly to step [8], otherwise go to step [7] + *

    + * 7. Save third-party user information in the database + *

    + * 8. The social userinfo exists in the database, and the binding relationship is established directly + * with the user id corresponding to the state + *

    + * 9.Bind successfully, + * + * @param config {@link AuthenticateConfig}, the actual type is {@link SocialConfig} + * @param request The request to bind + * @param response The response to bind + * @return JapResponse + */ + public JapResponse bind(AuthenticateConfig config, JapHttpRequest request, JapHttpResponse response) { + SocialConfig socialConfig = null; try { - return this.login(request, response, source, authRequest, authCallback); - } catch (JapUserException e) { + this.checkAuthenticateConfig(config, SocialConfig.class); + socialConfig = (SocialConfig) config; + } catch (JapException e) { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); } + + if (StrUtil.isEmpty(socialConfig.getBindUserId())) { + return JapResponse.error(JapErrorCode.ERROR.getErrroCode(), "Unable to bind the account of the third-party platform, the user id is empty."); + } + + AuthRequest authRequest = null; + try { + authRequest = this.getAuthRequest(config); + } catch (JapException e) { + return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); + } + + String source = socialConfig.getPlatform(); + + AuthCallback authCallback = this.parseRequest(request); + + if (this.isCallback(source, authCallback)) { + try { + return this.login(request, response, source, authRequest, authCallback, (japUser, currentRequest, currentResponse) -> { + String bindUserId = authStateCache.get(BIND_USERID_PREFIX_CACHE_KEY.concat(authCallback.getState())); + if (StrUtil.isEmpty(bindUserId)) { + throw new JapUserException("Unable to bind user information of " + source + + ". The operation has expired, please try again"); + } + + boolean bindResult = japUserService.bindSocialUser(japUser, bindUserId); + if (!bindResult) { + throw new JapUserException("Unable to bind user information of " + source + + ". bind user id: " + bindUserId + + ", social userinfo: " + JsonUtil.toJsonString(japUser)); + } + String referer = authStateCache.get(BIND_REFERER_PREFIX_CACHE_KEY.concat(authCallback.getState())); + + Map res = new HashMap<>(5); + res.put("bingUserId", bindUserId); + res.put("referer", referer); + return JapResponse.success(res); + }); + } catch (JapUserException e) { + return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); + } + } + + // If it is not a callback request, it must be a request to jump to the authorization link + authStateCache.cache(BIND_USERID_PREFIX_CACHE_KEY.concat(socialConfig.getState()), socialConfig.getBindUserId()); + authStateCache.cache(BIND_REFERER_PREFIX_CACHE_KEY.concat(socialConfig.getState()), RequestUtil.getReferer(request)); + String url = authRequest.authorize(socialConfig.getState()); + return JapResponse.success(url); } public JapResponse refreshToken(AuthenticateConfig config, AuthToken authToken) { AuthRequest authRequest = null; try { + this.checkAuthenticateConfig(config, SocialConfig.class); authRequest = this.getAuthRequest(config); } catch (JapException e) { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); @@ -175,6 +316,7 @@ public class SocialStrategy extends AbstractJapStrategy { public JapResponse revokeToken(AuthenticateConfig config, AuthToken authToken) { AuthRequest authRequest = null; try { + this.checkAuthenticateConfig(config, SocialConfig.class); authRequest = this.getAuthRequest(config); } catch (JapException e) { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); @@ -198,6 +340,7 @@ public class SocialStrategy extends AbstractJapStrategy { public JapResponse getUserInfo(AuthenticateConfig config, AuthToken authToken) { AuthRequest authRequest = null; try { + this.checkAuthenticateConfig(config, SocialConfig.class); authRequest = this.getAuthRequest(config); } catch (JapException e) { return JapResponse.error(e.getErrorCode(), e.getErrorMessage()); @@ -225,8 +368,6 @@ public class SocialStrategy extends AbstractJapStrategy { } private AuthRequest getAuthRequest(AuthenticateConfig config) { - // Convert AuthenticateConfig to SocialConfig - this.checkAuthenticateConfig(config, SocialConfig.class); SocialConfig socialConfig = (SocialConfig) config; String source = socialConfig.getPlatform(); @@ -248,8 +389,10 @@ public class SocialStrategy extends AbstractJapStrategy { * @param source Third party platform name * @param authRequest AuthRequest of justauth * @param authCallback Parse the parameters obtained by the third party callback request + * @param callback callback function */ - private JapResponse login(HttpServletRequest request, HttpServletResponse response, String source, AuthRequest authRequest, AuthCallback authCallback) throws JapUserException { + private JapResponse login(JapHttpRequest request, JapHttpResponse response, String source, + AuthRequest authRequest, AuthCallback authCallback, SocialFunc callback) throws JapUserException { AuthResponse authUserAuthResponse = null; try { authUserAuthResponse = authRequest.login(authCallback); @@ -270,7 +413,7 @@ public class SocialStrategy extends AbstractJapStrategy { } } - return this.loginSuccess(japUser, request, response); + return callback.exec(japUser, request, response); } /** @@ -301,7 +444,7 @@ public class SocialStrategy extends AbstractJapStrategy { * @param request Current callback request * @return AuthCallback */ - private AuthCallback parseRequest(HttpServletRequest request) { + private AuthCallback parseRequest(JapHttpRequest request) { Map params = request.getParameterMap(); if (CollectionUtil.isEmpty(params)) { return new AuthCallback(); diff --git a/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoHelper.java b/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoHelper.java index 94e5e32..5cc5817 100644 --- a/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoHelper.java +++ b/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoHelper.java @@ -19,6 +19,8 @@ import com.baomidou.kisso.SSOConfig; import com.baomidou.kisso.SSOHelper; import com.baomidou.kisso.security.token.SSOToken; import com.baomidou.kisso.service.ConfigurableAbstractKissoService; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.JapHttpResponse; import com.fujieid.jap.sso.config.JapSsoConfig; import javax.servlet.http.HttpServletRequest; @@ -43,14 +45,14 @@ public class JapSsoHelper { * @param response current HTTP response * @return String */ - public static String login(Object userId, String username, JapSsoConfig japSsoConfig, HttpServletRequest request, HttpServletResponse response) { + public static String login(Object userId, String username, JapSsoConfig japSsoConfig, JapHttpRequest request, JapHttpResponse response) { // Initialize Jap SSO config to prevent NPE japSsoConfig = null == japSsoConfig ? new JapSsoConfig() : japSsoConfig; // Reset kisso config resetKissoConfig(japSsoConfig); // set jap cookie SSOToken ssoToken = JapSsoUtil.createSsoToken(userId, username, request); - KiSsoHelper.setCookie(request, response, ssoToken, true); + KiSsoHelper.setCookie((javax.servlet.http.HttpServletRequest) request.getSource(), (HttpServletResponse) response.getSource(), ssoToken, true); return ssoToken.getToken(); } @@ -88,8 +90,8 @@ public class JapSsoHelper { * @param request current HTTP request * @return The ID of the current login user */ - public static String checkLogin(HttpServletRequest request) { - SSOToken ssoToken = KiSsoHelper.getSSOToken(request); + public static String checkLogin(JapHttpRequest request) { + SSOToken ssoToken = KiSsoHelper.getSSOToken((HttpServletRequest) request.getSource()); return null == ssoToken ? null : ssoToken.getId(); } @@ -99,8 +101,8 @@ public class JapSsoHelper { * @param request current HTTP request * @param response current HTTP response */ - public static void logout(HttpServletRequest request, HttpServletResponse response) { - KiSsoHelper.clearLogin(request, response); + public static void logout(JapHttpRequest request, JapHttpResponse response) { + KiSsoHelper.clearLogin((javax.servlet.http.HttpServletRequest) request.getSource(), (HttpServletResponse) response.getSource()); } /** diff --git a/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoUtil.java b/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoUtil.java index 6519e00..122cc02 100644 --- a/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoUtil.java +++ b/jap-sso/src/main/java/com/fujieid/jap/sso/JapSsoUtil.java @@ -15,9 +15,10 @@ */ package com.fujieid.jap.sso; +import com.baomidou.kisso.common.util.StringUtils; import com.baomidou.kisso.security.token.SSOToken; - -import javax.servlet.http.HttpServletRequest; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.RequestUtil; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) @@ -26,16 +27,16 @@ import javax.servlet.http.HttpServletRequest; */ public class JapSsoUtil { - public static SSOToken createSsoToken(Object userId, String username, HttpServletRequest request) { + public static SSOToken createSsoToken(Object userId, String username, JapHttpRequest request) { return new SSOToken() .setId(userId) .setIssuer(username) - .setIp(request) - .setUserAgent(request) + .setIp(getIp(request)) + .setUserAgent(RequestUtil.getUa(request)) .setTime(System.currentTimeMillis()); } - public static String createToken(Object userId, String username, HttpServletRequest request) { + public static String createToken(Object userId, String username, JapHttpRequest request) { return createSsoToken(userId, username, request).getToken(); } @@ -46,4 +47,49 @@ public class JapSsoUtil { return null; } } + + public static String getIp(JapHttpRequest request) { + if (null == request) { + return ""; + } + String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; + String ip; + for (String header : headers) { + ip = request.getHeader(header); + if (isValidIp(ip)) { + return getMultistageReverseProxyIp(ip); + } + } + ip = request.getRemoteAddr(); + return getMultistageReverseProxyIp(ip); + } + + /** + * Obtain the first non-unknown ip address from the multi-level reverse proxy + * + * @param ip IP + * @return The first non-unknown ip address + */ + private static String getMultistageReverseProxyIp(String ip) { + if (ip != null && ip.indexOf(",") > 0) { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) { + if (isValidIp(subIp)) { + ip = subIp; + break; + } + } + } + return ip; + } + + /** + * Verify ip legitimacy + * + * @param ip ip + * @return boolean + */ + private static boolean isValidIp(String ip) { + return !StringUtils.isEmpty(ip) && !"unknown".equalsIgnoreCase(ip); + } } diff --git a/jap-sso/src/test/java/com/fujieid/jap/sso/JapSsoUtilTest.java b/jap-sso/src/test/java/com/fujieid/jap/sso/JapSsoUtilTest.java index 2b67910..13bf386 100644 --- a/jap-sso/src/test/java/com/fujieid/jap/sso/JapSsoUtilTest.java +++ b/jap-sso/src/test/java/com/fujieid/jap/sso/JapSsoUtilTest.java @@ -16,6 +16,8 @@ package com.fujieid.jap.sso; import com.baomidou.kisso.security.token.SSOToken; +import com.fujieid.jap.http.JapHttpRequest; +import com.fujieid.jap.http.jakarta.JakartaRequestAdapter; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -29,6 +31,7 @@ import static org.mockito.Mockito.when; public class JapSsoUtilTest { + public JapHttpRequest request; @Mock private HttpServletRequest httpServletRequestMock; @Mock @@ -39,13 +42,14 @@ public class JapSsoUtilTest { MockitoAnnotations.initMocks(this); // Arrange when(httpServletRequestMock.getSession()).thenReturn(httpsSessionMock); + this.request = new JakartaRequestAdapter(httpServletRequestMock); } @Test public void createSsoToken() { when(httpServletRequestMock.getHeader("x-forwarded-for")).thenReturn("127.0.0.1"); when(httpServletRequestMock.getHeader("user-agent")).thenReturn("ua"); - SSOToken ssoToken = JapSsoUtil.createSsoToken("userId", "userName", httpServletRequestMock); + SSOToken ssoToken = JapSsoUtil.createSsoToken("userId", "userName", request); System.out.println(ssoToken); Assert.assertNotNull(ssoToken); } @@ -54,7 +58,7 @@ public class JapSsoUtilTest { public void createToken() { when(httpServletRequestMock.getHeader("x-forwarded-for")).thenReturn("127.0.0.1"); when(httpServletRequestMock.getHeader("user-agent")).thenReturn("ua"); - String token = JapSsoUtil.createToken("userId", "userName", httpServletRequestMock); + String token = JapSsoUtil.createToken("userId", "userName", request); System.out.println(token); Assert.assertNotNull(token); } diff --git a/pom.xml b/pom.xml index a63416c..0c5e0bb 100644 --- a/pom.xml +++ b/pom.xml @@ -47,12 +47,14 @@ jap-oidc jap-mfa jap-ids + jap-ids-web + jap-http-api jap-bom - 1.0.4 + 1.0.5 1.8 UTF-8 @@ -72,16 +74,20 @@ 2.23.4 5.5.7 4.0.4 - 1.16.2 + 1.16.4 0.7.6 1.7.30 3.2.0 - 3.7.6 + 3.7.7 0.0.2 - 1.0.3 + 1.0.5 3.3.3 - 1.4.0 + 1.5.0 1.4 + + 4.5.13 + 1.0.0 + 1.0.0 @@ -106,11 +112,13 @@ slf4j-api test - - jakarta.servlet - jakarta.servlet-api - provided + com.fujieid.jap.http + jap-http + + + com.fujieid.jap.http + jap-http-jakarta-adapter @@ -156,6 +164,11 @@ jap-ids ${revision} + + com.fujieid + jap-ids-web + ${revision} + junit @@ -198,12 +211,6 @@ hutool-crypto ${hutool.version} - - - jakarta.servlet - jakarta.servlet-api - ${jakarta.servlet.version} - com.baomidou kisso @@ -239,6 +246,21 @@ commons-cli ${commons-cli.version} + + org.apache.httpcomponents + httpclient + ${httpclient.version} + + + com.fujieid.jap.http + jap-http + ${jap-http.version} + + + com.fujieid.jap.http + jap-http-jakarta-adapter + ${jap-http-adapter.version} +