├── .editorconfig ├── .gitattributes ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose-quickstart.yml ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── cdk8s │ │ └── tkey │ │ └── server │ │ ├── Application.java │ │ ├── actuator │ │ └── CustomUPMSApiServerHealthEndpoint.java │ │ ├── audit │ │ └── LoggingTraceRepository.java │ │ ├── config │ │ ├── AsyncConfig.java │ │ ├── CustomErrorController.java │ │ ├── MeterConfig.java │ │ ├── OkHttpConfig.java │ │ ├── RedisConfig.java │ │ └── WebConfig.java │ │ ├── constant │ │ ├── GlobalVariable.java │ │ └── GlobalVariableToJunit.java │ │ ├── controller │ │ └── OauthController.java │ │ ├── enums │ │ ├── BasicEnum.java │ │ └── ResponseProduceTypeEnum.java │ │ ├── exception │ │ ├── ExceptionControllerAdvice.java │ │ ├── ExceptionDescriptionEnum.java │ │ ├── OauthApiException.java │ │ └── SystemException.java │ │ ├── init │ │ ├── ApplicationHealthInitRunner.java │ │ └── ApplicationTestDataInitRunner.java │ │ ├── pojo │ │ ├── bo │ │ │ ├── cache │ │ │ │ ├── OauthAccessTokenToRedisBO.java │ │ │ │ ├── OauthClientToRedisBO.java │ │ │ │ ├── OauthCodeToRedisBO.java │ │ │ │ ├── OauthRefreshTokenToRedisBO.java │ │ │ │ ├── OauthTgcToRedisBO.java │ │ │ │ ├── OauthTokenToRedisBO.java │ │ │ │ └── OauthUserInfoToRedisBO.java │ │ │ └── handle │ │ │ │ └── OauthTokenStrategyHandleBO.java │ │ └── dto │ │ │ ├── OauthIntrospect.java │ │ │ ├── OauthToken.java │ │ │ ├── OauthUserAttribute.java │ │ │ ├── OauthUserProfile.java │ │ │ └── param │ │ │ ├── OauthAuthorizeParam.java │ │ │ ├── OauthClientParam.java │ │ │ ├── OauthFormLoginParam.java │ │ │ ├── OauthIntrospectTokenParam.java │ │ │ ├── OauthRefreshTokenParam.java │ │ │ ├── OauthTokenParam.java │ │ │ └── resolve │ │ │ ├── OauthAuthorizeParamArgumentResolver.java │ │ │ ├── OauthFormParamArgumentResolver.java │ │ │ ├── OauthIntrospectTokenParamArgumentResolver.java │ │ │ ├── OauthRefreshTokenParamArgumentResolver.java │ │ │ └── OauthTokenParamArgumentResolver.java │ │ ├── properties │ │ └── OauthProperties.java │ │ ├── retry │ │ └── RetryService.java │ │ ├── service │ │ ├── OauthCheckParamService.java │ │ ├── OauthClientService.java │ │ ├── OauthGenerateService.java │ │ ├── OauthSaveService.java │ │ └── OauthThirdPartyApiService.java │ │ ├── strategy │ │ ├── OauthClientToTokenStrategy.java │ │ ├── OauthCodeToTokenStrategy.java │ │ ├── OauthPasswordToTokenStrategy.java │ │ ├── OauthRefreshTokenToTokenStrategy.java │ │ ├── OauthTokenStrategyContext.java │ │ └── OauthTokenStrategyInterface.java │ │ └── util │ │ ├── CodecUtil.java │ │ ├── CollectionUtil.java │ │ ├── CookieUtil.java │ │ ├── DatetimeUtil.java │ │ ├── ExceptionUtil.java │ │ ├── IPUtil.java │ │ ├── JsonUtil.java │ │ ├── NumericGeneratorUtil.java │ │ ├── RandomUtil.java │ │ ├── StringUtil.java │ │ ├── UserAgentUtil.java │ │ ├── okhttp │ │ ├── OkHttpResponse.java │ │ └── OkHttpService.java │ │ ├── redis │ │ └── StringRedisService.java │ │ └── response │ │ ├── R.java │ │ ├── ResponseErrorEnum.java │ │ └── ResponseErrorObject.java └── resources │ ├── application-dev.yml │ ├── application-gatling.yml │ ├── application-test.yml │ ├── application.yml │ ├── banner.txt │ ├── logback │ ├── logback-dev.xml │ ├── logback-gatling.xml │ ├── logback-prod.xml │ └── logback-test.xml │ ├── static │ ├── css │ │ └── login.css │ ├── favicon.ico │ ├── images │ │ ├── 404-qr.png │ │ ├── error-qr.png │ │ ├── loading.svg │ │ ├── tkey-logo.png │ │ └── tkey-tool.png │ └── js │ │ └── login.js │ └── templates │ ├── 404.html │ ├── error.html │ ├── index.html │ ├── login.html │ └── logoutSuccess.html └── test ├── java └── com │ └── cdk8s │ └── tkey │ └── server │ └── controller │ ├── AuthorizationCodeByFormTest.java │ └── AuthorizationCodeByHeaderTest.java └── resources ├── application-junit.yml └── application.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | end_of_line = lf 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | max_line_length = 300 12 | 13 | [*.{groovy,java,kt,kts}] 14 | indent_style = tab 15 | continuation_indent_size = 8 16 | 17 | [*.{xml,xsd}] 18 | indent_style = tab 19 | 20 | [Makefile] 21 | indent_style = tab 22 | 23 | [*.py] 24 | indent_size = 4 25 | 26 | [*.js] 27 | indent_size = 4 28 | 29 | [*.ts] 30 | indent_size = 4 31 | 32 | [*.html] 33 | indent_size = 4 34 | 35 | [*.{less,css}] 36 | indent_size = 2 37 | 38 | [*.json] 39 | indent_size = 2 40 | 41 | [*.{tsx,jsx}] 42 | indent_size = 2 43 | 44 | [*.yml] 45 | indent_size = 2 46 | 47 | [*.sql] 48 | indent_size = 2 49 | 50 | [*.md] 51 | insert_final_newline = false 52 | trim_trailing_whitespace = false 53 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # This file is inspired by https://github.com/alexkaratarakis/gitattributes 2 | # 3 | # Auto detect text files and perform LF normalization 4 | # http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ 5 | * text=auto 6 | 7 | # The above will handle all files NOT found below 8 | # These files are text and should be normalized (Convert crlf => lf) 9 | 10 | # Java sources 11 | *.java text diff=java 12 | *.gradle text diff=java 13 | *.gradle.kts text diff=java 14 | 15 | 16 | *.css text diff=css 17 | *.htm text diff=html 18 | *.html text diff=html 19 | *.xhtml text diff=html 20 | *.coffee text 21 | *.cql text 22 | *.df text 23 | *.ejs text 24 | *.js text 25 | *.jsx text 26 | *.json text 27 | *.less text 28 | *.properties text 29 | *.sass text 30 | *.scss text diff=css 31 | *.sh text eol=lf 32 | *.sql text 33 | *.txt text 34 | *.ts text 35 | *.tsx text 36 | *.xml text 37 | *.yaml text 38 | *.yml text 39 | *.jsp text 40 | *.jspf text 41 | *.jspx text 42 | *.tld text 43 | *.tag text 44 | *.tagx text 45 | *.inc text 46 | *.ini text 47 | 48 | # Documents 49 | *.doc diff=astextplain 50 | *.DOC diff=astextplain 51 | *.docx diff=astextplain 52 | *.DOCX diff=astextplain 53 | *.dot diff=astextplain 54 | *.DOT diff=astextplain 55 | *.pdf diff=astextplain 56 | *.PDF diff=astextplain 57 | *.rtf diff=astextplain 58 | *.RTF diff=astextplain 59 | *.markdown text 60 | *.md text 61 | *.adoc text 62 | *.textile text 63 | *.mustache text 64 | *.csv text 65 | *.tab text 66 | *.tsv text 67 | *.txt text 68 | AUTHORS text 69 | CHANGELOG text 70 | CHANGES text 71 | CONTRIBUTING text 72 | COPYING text 73 | copyright text 74 | *COPYRIGHT* text 75 | INSTALL text 76 | license text 77 | LICENSE text 78 | NEWS text 79 | readme text 80 | *README* text 81 | TODO text 82 | 83 | # Graphics 84 | *.png binary 85 | *.jpg binary 86 | *.jpeg binary 87 | *.gif binary 88 | *.tif binary 89 | *.tiff binary 90 | *.ico binary 91 | 92 | # SVG treated as an asset (binary) by default. If you want to treat it as text, 93 | # comment-out the following line and uncomment the line after. 94 | *.svg binary 95 | #*.svg text 96 | *.eps binary 97 | 98 | # These files are binary and should be left untouched 99 | # (binary is a macro for -text -diff) 100 | *.class binary 101 | *.jar binary 102 | *.war binary 103 | *.dll binary 104 | *.ear binary 105 | *.so binary 106 | *.exe binary 107 | *.pyc binary 108 | 109 | ## LINTERS 110 | .csslintrc text 111 | .eslintrc text 112 | .jscsrc text 113 | .jshintrc text 114 | .jshintignore text 115 | .stylelintrc text 116 | 117 | ## CONFIGS 118 | *.conf text 119 | *.config text 120 | .editorconfig text 121 | .gitattributes text 122 | .gitconfig text 123 | .gitignore text 124 | .htaccess text 125 | *.npmignore text 126 | 127 | ## HEROKU 128 | Procfile text 129 | .slugignore text 130 | 131 | ## AUDIO 132 | *.kar binary 133 | *.m4a binary 134 | *.mid binary 135 | *.midi binary 136 | *.mp3 binary 137 | *.ogg binary 138 | *.ra binary 139 | 140 | ## VIDEO 141 | *.3gpp binary 142 | *.3gp binary 143 | *.as binary 144 | *.asf binary 145 | *.asx binary 146 | *.fla binary 147 | *.flv binary 148 | *.m4v binary 149 | *.mng binary 150 | *.mov binary 151 | *.mp4 binary 152 | *.mpeg binary 153 | *.mpg binary 154 | *.swc binary 155 | *.swf binary 156 | *.webm binary 157 | 158 | ## ARCHIVES 159 | *.7z binary 160 | *.7z binary 161 | *.gz binary 162 | *.rar binary 163 | *.tar binary 164 | *.zip binary 165 | 166 | ## FONTS 167 | *.ttf binary 168 | *.eot binary 169 | *.otf binary 170 | *.woff binary 171 | *.woff2 binary 172 | 173 | 174 | *.bash text eol=lf 175 | *.bat text eol=crlf 176 | *.cmd text eol=crlf 177 | 178 | 179 | *.php text diff=php 180 | *.py text diff=python 181 | *.rb text diff=ruby 182 | 183 | # Docker 184 | *.dockerignore text 185 | Dockerfile text 186 | 187 | 188 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | target/ 3 | node_modules/ 4 | classes/ 5 | .deploy*/ 6 | .gradle 7 | *.log.* 8 | *.log 9 | 10 | # ====================================================== 11 | 12 | ### STS ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | 26 | ### NetBeans ### 27 | nbproject/private/ 28 | build/ 29 | nbbuild/ 30 | dist/ 31 | nbdist/ 32 | .nb-gradle/ 33 | 34 | # ====================================================== 35 | 36 | # Windows image file caches 37 | Thumbs.db 38 | ehthumbs.db 39 | 40 | # Folder config file 41 | Desktop.ini 42 | 43 | # Recycle Bin used on file shares 44 | $RECYCLE.BIN/ 45 | 46 | # Windows Installer files 47 | *.cab 48 | *.msi 49 | *.msm 50 | *.msp 51 | 52 | # Windows shortcuts 53 | *.lnk 54 | 55 | # ====================================================== 56 | 57 | # OSX 58 | .DS_Store 59 | .AppleDouble 60 | .LSOverride 61 | 62 | # Thumbnails 63 | ._* 64 | 65 | # Files that might appear in the root of a volume 66 | .DocumentRevisions-V100 67 | .fseventsd 68 | .Spotlight-V100 69 | .TemporaryItems 70 | .Trashes 71 | .VolumeIcon.icns 72 | 73 | # Directories potentially created on remote AFP share 74 | .AppleDB 75 | .AppleDesktop 76 | Network Trash Folder 77 | Temporary Items 78 | .apdisk 79 | 80 | # ====================================================== 81 | 82 | npm-debug.log* 83 | yarn-error.log 84 | .idea/ 85 | .ipr 86 | .iws 87 | *~ 88 | ~* 89 | *.diff 90 | *.patch 91 | *.bak 92 | .*proj 93 | .svn/ 94 | *.swp 95 | *.swo 96 | *.json.gzip 97 | .buildpath 98 | nohup.out 99 | dist 100 | *.tmp 101 | 102 | # ====================================================== 103 | 104 | .DS_STORE 105 | *.pyc 106 | remote-repo/ 107 | coverage/ 108 | .module-cache 109 | *.log* 110 | chrome-user-data 111 | *.sublime-project 112 | *.sublime-workspace 113 | .vscode 114 | 115 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM anapsix/alpine-java:8_server-jre_unlimited 2 | 3 | MAINTAINER cdk8s cdk8s@qq.com 4 | 5 | VOLUME /tmp 6 | 7 | ENV TZ=Asia/Shanghai 8 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 9 | 10 | ADD ./target/tkey-sso-server-1.0.0.jar /app.jar 11 | RUN bash -c 'touch /app.jar' 12 | 13 | EXPOSE 9091 14 | EXPOSE 19091 15 | ENTRYPOINT ["java", "-jar", "-Xms1024m", "-Xmx1024m", "-XX:MetaspaceSize=124m", "-XX:MaxMetaspaceSize=224M" ,"/app.jar"] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 重要通知 2 | 3 | - 2019-12 基于 TKey 基础上的单体完整系统已经建立,需要继续扩展学习的可以查看:[Github](https://github.com/cdk8s/sculptor-boot-generator) [Gitee](https://Gitee.com/cdk8s/sculptor-boot-generator) 4 | 5 | ## 只有上云才能撑住规模化后的发展 6 | 7 | - 初期技术选型上尽可能寻找云支持的 8 | - 在公司规模小,自建服务基本都做不到 99.999% 高可用 9 | - 在公司规模发展变迅速时,如果云技术和已有技术契合,迁移成本会低很多很多 10 | - 目前暂定只选择:[阿里云服务](https://www.aliyun.com/minisite/goods?userCode=v2zozyxz) 11 | - 这里罗列了阿里云常用的一些:[产品](https://github.com/cdk8s/cdk8s-team-style/blob/master/ops/aliyun.md) 12 | 13 | ## Introduction 14 | 15 | - **TKey = Token Key** 以 OAuth 2.0 标准为接口设计原则的单点登录系统(SSO) 16 | - **初衷:** 17 | - 做国内各种登录场景的配件,以完善的学习资料为主核心竞争力 18 | - 希望让有 1 ~ 2 年工作经验的人都可以用 TKey 进行自定义扩展需求 19 | - 纯粹的 HTTP,任意设备、任意场景 20 | - 跨域无状态,随意横向扩展,服务高可用 21 | - Spring Boot 2.1.x 技术栈,没有封装,有一点点基础即刻魔改(MIT License) 22 | - 从开发、测试、部署、监控、前后端分离的材料都已具备 23 | - **最后:没有哪个框架、系统可以套用在任意用户、任意场景、任意需求上,希望对你有思路帮助** 24 | 25 | ## Git 26 | 27 | - Github: 28 | - Gitee: 29 | 30 | ## Live Demo 31 | 32 | ![登陆完整过程](http://img.gitnavi.com/tkey/tkey-sso-login.gif) 33 | 34 | - **注意:带宽只有 1M,访问会慢** 35 | - 一共3个完整系统: 36 | - 第一个是后台管理系统,包含用户管理: 37 | - 第二个是演示系统Client-1: 38 | - 第三个是演示系统Client-2: 39 | - 同一个浏览器同时访问三个站点会发现都需要跳去登录,表明三个系统链接都是需要登录才可以访问 40 | - 先登录第一个系统 41 | - 然后访问第二个系统、第三个系统会发现直接返回用户 JSON 信息 42 | 43 | ## Architecture 44 | 45 | ![架构图](http://img.gitnavi.com/tkey/tkey-sso-architecture.jpg) 46 | 47 | - 上图的视频讲解:[B 站](https://www.bilibili.com/video/av65883281/)、[腾讯视频](https://v.qq.com/x/page/e0920wdqe7v.html) 48 | - OAuth2.0 授权码模式细节时序图可以查看:[点击我查看](http://img.gitnavi.com/tkey/tkey-oauth.png) 49 | 50 | ## Preview(Gif) 51 | 52 | - 主图需要右键复制地址,然后粘贴到地址栏才能打开 Orz.. 53 | - **登录完整过程:** [主图](https://upload-images.jianshu.io/upload_images/19119711-cd483cefb50eb763.gif)、[备图](http://img.gitnavi.com/tkey/tkey-sso-login.gif) 54 | - **Grafana 监控大屏:** [主图](https://upload-images.jianshu.io/upload_images/19119711-af9b3d3411db1da1.gif)、[备图](http://img.gitnavi.com/tkey/actuator-prometheus-grafana.gif) 55 | - **GoAccess 监控大屏:** [主图](https://upload-images.jianshu.io/upload_images/19119711-b3bcc4edcf0df007.gif)、[备图](http://img.gitnavi.com/tkey/goaccess-data.gif) 56 | - **Postman 接口调用:** [主图](https://upload-images.jianshu.io/upload_images/19119711-a8316b794bf4bf56.gif)、[备图](http://img.gitnavi.com/tkey/postman-request-api.gif) 57 | - **Docker 容器管理:** [主图](https://upload-images.jianshu.io/upload_images/19119711-281dd6b40f2d7fc7.gif)、[备图](http://img.gitnavi.com/tkey/portainer-docker.gif) 58 | - **Jenkins 部署流水线:** [主图](https://upload-images.jianshu.io/upload_images/19119711-2d20e2fba98ddbbd.gif)、[备图](http://img.gitnavi.com/tkey/tkey-jenkins.gif) 59 | - **JProfiler 压测变化:** [主图](https://upload-images.jianshu.io/upload_images/19119711-922b8202de206b06.gif)、[备图](http://img.gitnavi.com/tkey/tkey-jprofiler.gif) 60 | - **VisualVM 压测变化:** [主图](https://upload-images.jianshu.io/upload_images/19119711-067bcdf1a6e95b44.gif)、[备图](http://img.gitnavi.com/tkey/tkey-visualvm.gif) 61 | 62 | 63 | ## Quick Start 64 | 65 | - 单元测试:[主图](https://upload-images.jianshu.io/upload_images/19119711-6bc18bb5b1063911.gif)、[备图](http://img.gitnavi.com/tkey/tkey-junit-test.gif) 66 | - TKey SSO Server JAR 方式部署过程:[主图](https://upload-images.jianshu.io/upload_images/19119711-72e375355e3df651.gif)、[备图](http://img.gitnavi.com/tkey/tkey-runapp-jar.gif) 67 | - TKey SSO Server Docker Compose:[主图](https://upload-images.jianshu.io/upload_images/19119711-10011adf8a15e049.gif)、[备图](http://img.gitnavi.com/tkey/tkey-sso-server-docker-compose.gif) 68 | - TKey SSO Client Management Docker Compose:[主图](https://upload-images.jianshu.io/upload_images/19119711-8edd4a914ed4540a.gif)、[备图](http://img.gitnavi.com/tkey/tkey-sso-client-management-docker-compose.gif) 69 | - 项目完全依赖 Lombok(推荐),如果没有用过可以参考 [该篇文章](https://github.com/cdk8s/cdk8s-team-style/blob/master/dev/backend/java/java-lombok.md) 70 | - 项目最优搭配 IntelliJ IDEA,如果还没用过可以参考 [该系列文章(我们的作品)](https://github.com/judasn/IntelliJ-IDEA-Tutorial) 71 | - Maven 中央仓库已经申请下来,大家现在可以直接使用我们自己封装的 REST API 客户端了 72 | 73 | ## Documentation 74 | 75 | - 我们统一了 TKey 项目的所有文档,方便大家查看 76 | - Github: 77 | - Gitee: 78 | - Gitbook: 79 | - **认识阶段 (必读)** 80 | - 单点登录系统认知与基础介绍:[Github](https://github.com/cdk8s/tkey-docs/blob/master/other/tkey-baisc.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/other/tkey-baisc.md) 81 | - 故意设计点(常见问题):[Github](https://github.com/cdk8s/tkey-docs/blob/master/faq/README.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/faq/README.md) 82 | - 项目结构与端口占用:[Github](https://github.com/cdk8s/tkey-docs/blob/master/other/project-structure.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/other/project-structure.md) 83 | - OAuth2.0 四种模式:[Github](https://github.com/cdk8s/tkey-docs/blob/master/server/oauth-grant-type/README.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/server/oauth-grant-type/README.md) 84 | - JAR 方式部署:[Github](https://github.com/cdk8s/tkey-docs/blob/master/deployment/jar-runapp.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/deployment/jar-runapp.md) 85 | - Docker 方式部署:[Github](https://github.com/cdk8s/tkey-docs/blob/master/deployment/docker-runapp.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/deployment/docker-runapp.md) 86 | - Docker Compose 方式部署:[Github](https://github.com/cdk8s/tkey-docs/blob/master/deployment/docker-compose-runapp.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/deployment/docker-compose-runapp.md) 87 | - TKey Server 开发阶段 88 | - 开发改造引导:[Github](https://github.com/cdk8s/tkey-docs/blob/master/server/dev.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/server/dev.md) 89 | - TKey Management 开发阶段(也是前后端分离的最佳实践示例) 90 | - 后端开发改造引导:[Github](https://github.com/cdk8s/tkey-docs/blob/master/management/dev-backend.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/management/dev-backend.md) 91 | - 前端开发改造引导:[Github](https://github.com/cdk8s/tkey-docs/blob/master/management/dev-frontend.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/management/dev-frontend.md) 92 | - TKey Client Java 开发阶段 93 | - 自己封装的 REST Client:[Github](https://github.com/cdk8s/tkey-docs/blob/master/client/dev-rest-client.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/client/dev-rest-client.md) 94 | - Spring Security 支持:[Github](https://github.com/cdk8s/tkey-docs/blob/master/client/dev-spring-security-client.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/client/dev-spring-security-client.md) 95 | - 测试阶段 96 | - 单元测试:[Github](https://github.com/cdk8s/tkey/blob/master/src/test/java/com/cdk8s/tkey/server/controller/AuthorizationCodeByFormTest.java)、[Gitee](https://gitee.com/cdk8s/tkey/blob/master/src/test/java/com/cdk8s/tkey/server/controller/AuthorizationCodeByFormTest.java) 97 | - 压力测试:[Github](https://github.com/cdk8s/tkey-docs/blob/master/test/performance.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/test/performance.md) 98 | - 部署阶段 99 | - 生产注意事项:[Github](https://github.com/cdk8s/tkey-docs/blob/master/deployment/production-environment.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/deployment/production-environment.md) 100 | - 部署环境搭建:[Github](https://github.com/cdk8s/tkey-docs/blob/master/deployment/deployment-core.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/deployment/deployment-core.md) 101 | - 监控阶段 102 | - Spring Boot Micrometer:[Github](https://github.com/cdk8s/tkey-docs/blob/master/deployment/micrometer.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/deployment/micrometer.md) 103 | - 其他工具全在 `部署环境搭建`,请自行查看 104 | - 线上问题诊断 105 | - [Actuator 在线修改 log 输出级别(Gif 动图)](http://img.gitnavi.com/tkey/actuator-update-log-level.gif) 106 | - [Arthas 诊断 Docker 应用](https://alibaba.github.io/arthas/docker.html#dockerjava) 107 | - 夜间开放端口,挑选流量远程 Debug:[Github](https://github.com/cdk8s/tkey-docs/blob/master/server/remote-debug.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/server/remote-debug.md) 108 | 109 | 110 | ## TKey Client 111 | 112 | - Java 前后端分离最佳实践 113 | - TKey SSO Client Management Backend:[Github](https://github.com/cdk8s/tkey-management)、[Gitee](https://gitee.com/cdk8s/tkey-management) 114 | - TKey SSO Client Management Frontend:[Github](https://github.com/cdk8s/tkey-management-frontend)、[Gitee](https://gitee.com/cdk8s/tkey-management) 115 | - Angular、Vue 的前后端分离版本会在稍后几周发出来 116 | - Java REST API 客户端:[Github](https://github.com/cdk8s/tkey-client-java)、[Gitee](https://gitee.com/cdk8s/tkey-client-java) 117 | - Java Spring Security 客户端:[Github](https://github.com/cdk8s/tkey-client-java-spring-security)、[Gitee](https://gitee.com/cdk8s/tkey-client-java-spring-security) 118 | - C#(暂缺) 119 | - GO(暂缺) 120 | - PHP(暂缺) 121 | - Python(暂缺) 122 | - Ruby(暂缺) 123 | - Node.js(暂缺) 124 | 125 | ## Share 126 | 127 | - Grafana Dashboard:[Github](https://github.com/cdk8s/tkey-docs/blob/master/share-file/grafana/dashboard.json)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/share-file/grafana/dashboard.json) 128 | - Postman API:[Github](https://github.com/cdk8s/tkey-docs/blob/master/share-file/postman/tkey-sso-server-api_collection_2.1_format.json)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/share-file/postman/tkey-sso-server-api_collection_2.1_format.json) 129 | - Run JAR Shell:[Github](https://github.com/cdk8s/tkey-docs/blob/master/share-file/shell/runapp.sh)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/share-file/shell/runapp.sh) 130 | 131 | 132 | ## Roadmap 133 | 134 | - 规划版本:[Github](https://github.com/cdk8s/tkey-docs/blob/master/roadmap/README.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/roadmap/README.md) 135 | 136 | ## Changelog 137 | 138 | - 版本更新:[Github](https://github.com/cdk8s/tkey-docs/blob/master/changelog/README.md)、[Gitee](https://gitee.com/cdk8s/tkey-docs/blob/master/changelog/README.md) 139 | 140 | 141 | ## Issues 142 | 143 | - 目前只开放了一个 issues 入口,集中问题,可以方便大家检索。 144 | - 去提问:[Github](https://github.com/cdk8s/tkey-issues)、[Gitee](https://gitee.com/cdk8s/tkey-issues) 145 | 146 | ## Contributors 147 | 148 | - 暂无 149 | - 欢迎 pull request 150 | 151 | ## Adopters 152 | 153 | - 去申请:[Github](https://github.com/cdk8s/tkey-issues/issues/1)、[Gitee](https://gitee.com/cdk8s/tkey-issues/issues/1) 154 | - 以企业角色联系我进行咨询有优先权,我们会花更多耐心进行讲解和帮助 155 | - 所以,请在加好友之后先表明公司、立场 156 | 157 | 158 | ## Sponsors 159 | 160 | - 暂无 161 | 162 | ## Backer 163 | 164 | - [我要喝喜茶 Orz..](http://www.youmeek.com/donate/) 165 | 166 | 167 | ## Join 168 | 169 | - 邮箱:`cdk8s#qq.com` 170 | - 博客: 171 | - Github: 172 | - Gitee: 173 | - 公众号 174 | 175 | ![公众号](http://img.gitnavi.com/markdown/cdk8s_qr_300px.png) 176 | 177 | 178 | ## Jobs 179 | 180 | - 我们在广州 181 | - 有广州或深圳的合作、Offer 欢迎联系我们 182 | - 邮箱:`cdk8s#qq.com` 183 | - 公众号:`联系我们` 184 | 185 | ## Thanks 186 | 187 | - [IntelliJ IDEA](https://www.jetbrains.com/idea/) 188 | - [CAS](https://github.com/apereo/cas) 189 | - [Okta](https://www.okta.com/) 190 | 191 | 192 | ## Copyright And License 193 | 194 | - Copyright (c) CDK8S. All rights reserved. 195 | - Licensed under the **MIT** license. 196 | - **再次强调: 因为是 MIT 协议,大家有不满意的,除了 PR 也可以 fork 后自己尽情改造!** 197 | 198 | 199 | -------------------------------------------------------------------------------- /docker-compose-quickstart.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | tkey-sso-server: 4 | build: 5 | context: ./ 6 | dockerfile: ./Dockerfile 7 | container_name: tkey-sso-server 8 | image: tkey-sso-server 9 | ports: 10 | - 9091:9091 11 | - 19091:19091 12 | environment: 13 | SPRING_PROFILES_ACTIVE: dev 14 | SPRING_REDIS_DATABASE: 0 15 | SPRING_REDIS_PORT: 6379 16 | SPRING_REDIS_HOST: tkey-sso-server-redis 17 | SPRING_REDIS_PASSWORD: 123456 18 | TKEY_NODE_NUMBER: 10 19 | JAVA_OPTS: "-Xms1024m -Xmx1024m -XX:MetaspaceSize=124m -XX:MaxMetaspaceSize=224m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/headDump" 20 | networks: 21 | - tkey-network 22 | depends_on: 23 | - tkey-sso-server-redis 24 | 25 | 26 | tkey-sso-server-redis: 27 | image: redis:4 28 | container_name: tkey-sso-server-redis 29 | command: redis-server --requirepass 123456 30 | restart: always 31 | ports: 32 | - 6379:6379 33 | networks: 34 | - tkey-network 35 | 36 | 37 | networks: 38 | tkey-network: 39 | driver: bridge 40 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.cdk8s.tkey 7 | tkey-sso-server 8 | 1.0.1 9 | jar 10 | 11 | ${project.artifactId} 12 | ${project.artifactId} 13 | 14 | https://github.com/cdk8s 15 | 16 | 17 | 18 | cdk8s 19 | cdk8s@qq.com 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-parent 26 | 2.1.9.RELEASE 27 | 28 | 29 | 30 | 31 | UTF-8 32 | UTF-8 33 | 1.8 34 | 35 | 3.8.1 36 | 1.5 37 | 4.2 38 | 2.6 39 | 1.11 40 | 41 | 27.0.1-jre 42 | 3.12.1 43 | 4.5.13 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-starter-web 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-configuration-processor 59 | true 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-thymeleaf 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-test 70 | test 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-aop 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-actuator 81 | 82 | 83 | 84 | io.micrometer 85 | micrometer-registry-prometheus 86 | 87 | 88 | 89 | org.springframework.retry 90 | spring-retry 91 | 92 | 93 | 94 | org.projectlombok 95 | lombok 96 | true 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-starter-data-redis 104 | 105 | 106 | 107 | org.apache.commons 108 | commons-pool2 109 | 110 | 111 | 112 | 113 | 114 | org.apache.commons 115 | commons-lang3 116 | ${commons-lang3.version} 117 | 118 | 119 | 120 | org.apache.commons 121 | commons-text 122 | ${commons-text.version} 123 | 124 | 125 | 126 | org.apache.commons 127 | commons-collections4 128 | ${commons-collections4.version} 129 | 130 | 131 | 132 | commons-io 133 | commons-io 134 | ${commons-io.version} 135 | 136 | 137 | 138 | commons-codec 139 | commons-codec 140 | ${commons-codec.version} 141 | 142 | 143 | 144 | com.squareup.okhttp3 145 | okhttp 146 | ${okhttp.version} 147 | 148 | 149 | 150 | com.squareup.okhttp3 151 | logging-interceptor 152 | ${okhttp.version} 153 | 154 | 155 | 156 | com.google.guava 157 | guava 158 | ${guava.version} 159 | 160 | 161 | 162 | cn.hutool 163 | hutool-all 164 | ${hutool-all.version} 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | org.springframework.boot 175 | spring-boot-maven-plugin 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | aliyun-pom-repos 184 | http://maven.aliyun.com/nexus/content/groups/public/ 185 | 186 | false 187 | 188 | 189 | 190 | 191 | 192 | 193 | aliyun-pom-plugin 194 | http://maven.aliyun.com/nexus/content/groups/public/ 195 | 196 | false 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/Application.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.web.servlet.ServletComponentScan; 9 | import org.springframework.retry.annotation.EnableRetry; 10 | import org.springframework.scheduling.annotation.EnableAsync; 11 | 12 | 13 | @EnableAsync 14 | @EnableRetry 15 | @ServletComponentScan 16 | @Slf4j 17 | @SpringBootApplication 18 | public class Application implements CommandLineRunner { 19 | 20 | @Value("${server.port:9091}") 21 | private String serverPort; 22 | 23 | @Value("${server.servlet.context-path:/sso}") 24 | private String serverContextPath; 25 | 26 | @Value("${management.server.servlet.context-path:/tkey-actuator}") 27 | private String managementContextPath; 28 | 29 | @Value("${management.server.port:19091}") 30 | private String managementPort; 31 | 32 | //================================================================================= 33 | 34 | public static void main(String[] args) { 35 | SpringApplication.run(Application.class, args); 36 | } 37 | 38 | @Override 39 | public void run(String... strings) { 40 | log.info("=================================Application Startup Success================================="); 41 | log.info("index >> http://sso.cdk8s.com:{}{}", serverPort, serverContextPath); 42 | log.info("actuator-health >> http://sso.cdk8s.com:{}{}/actuator/health", managementPort, managementContextPath); 43 | log.info("actuator-prometheus >> http://sso.cdk8s.com:{}{}/actuator/prometheus", managementPort, managementContextPath); 44 | log.info("=================================Application Startup Success================================="); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/actuator/CustomUPMSApiServerHealthEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.actuator; 2 | 3 | import com.cdk8s.tkey.server.util.okhttp.OkHttpResponse; 4 | import com.cdk8s.tkey.server.util.okhttp.OkHttpService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.actuate.health.AbstractHealthIndicator; 7 | import org.springframework.boot.actuate.health.Health; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.stereotype.Component; 10 | 11 | 12 | /** 13 | * 模拟检测第三方验证用户名密码接口 14 | */ 15 | @Component 16 | public class CustomUPMSApiServerHealthEndpoint extends AbstractHealthIndicator { 17 | 18 | @Autowired 19 | private OkHttpService okHttpService; 20 | 21 | //====================================================== 22 | 23 | @Override 24 | protected void doHealthCheck(Health.Builder builder) { 25 | OkHttpResponse okHttpResponse = okHttpService.get("https://www.baidu.com"); 26 | if (okHttpResponse.getStatus() == HttpStatus.OK.value()) { 27 | builder.up(); 28 | } else { 29 | builder.down(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/audit/LoggingTraceRepository.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.audit; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariable; 4 | import com.cdk8s.tkey.server.util.StringUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.actuate.trace.http.HttpTrace; 7 | import org.springframework.boot.actuate.trace.http.HttpTraceRepository; 8 | import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.net.URI; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | @Slf4j 17 | @Component 18 | public class LoggingTraceRepository implements HttpTraceRepository { 19 | 20 | private final HttpTraceRepository httpTraceRepository = new InMemoryHttpTraceRepository(); 21 | 22 | //=====================================业务处理 start===================================== 23 | 24 | @Override 25 | public List findAll() { 26 | return httpTraceRepository.findAll(); 27 | } 28 | 29 | @Override 30 | public void add(HttpTrace trace) { 31 | if (checkUri(trace)) { 32 | return; 33 | } 34 | printTimeTakenResult(trace); 35 | this.httpTraceRepository.add(trace); 36 | } 37 | 38 | //=====================================业务处理 end===================================== 39 | //=====================================私有方法 start===================================== 40 | 41 | private Boolean checkUri(HttpTrace trace) { 42 | HttpTrace.Request request = trace.getRequest(); 43 | String requestURI = request.getUri().toString(); 44 | 45 | List ignoreSuffix = new ArrayList<>(); 46 | ignoreSuffix.add(".js"); 47 | ignoreSuffix.add(".css"); 48 | ignoreSuffix.add(".jpg"); 49 | ignoreSuffix.add(".jpeg"); 50 | ignoreSuffix.add(".gif"); 51 | ignoreSuffix.add(".png"); 52 | ignoreSuffix.add(".bmp"); 53 | ignoreSuffix.add(".swf"); 54 | ignoreSuffix.add(".ico"); 55 | ignoreSuffix.add(".woff"); 56 | ignoreSuffix.add(".woff2"); 57 | ignoreSuffix.add(".ttf"); 58 | ignoreSuffix.add(".eot"); 59 | ignoreSuffix.add(".txt"); 60 | ignoreSuffix.add(".svg"); 61 | 62 | CharSequence[] charSequences = ignoreSuffix.toArray(new CharSequence[0]); 63 | return StringUtil.endsWithAny(requestURI, charSequences); 64 | } 65 | 66 | private void printTimeTakenResult(HttpTrace trace) { 67 | Long timeTaken = trace.getTimeTaken(); 68 | 69 | HttpTrace.Request request = trace.getRequest(); 70 | String requestMethod = request.getMethod(); 71 | Map> requestHeaders = request.getHeaders(); 72 | List cookieList = requestHeaders.get("cookie"); 73 | 74 | List refererList = requestHeaders.get("referer"); 75 | URI requestUri = request.getUri(); 76 | 77 | 78 | HttpTrace.Response response = trace.getResponse(); 79 | Map> responseHeaders = response.getHeaders(); 80 | int responseStatus = response.getStatus(); 81 | 82 | 83 | if (log.isDebugEnabled()) { 84 | log.debug("----------------------HttpTrace requestUri={}", requestUri); 85 | log.debug("----------------------HttpTrace requestMethod={}, responseStatus={}", requestMethod, responseStatus); 86 | log.debug("----------------------HttpTrace cookieList={}", cookieList); 87 | log.debug("----------------------HttpTrace refererList={}", refererList); 88 | log.debug("----------------------HttpTrace responseHeaders={}", responseHeaders.toString()); 89 | } 90 | 91 | if (timeTaken > GlobalVariable.NEED_OPTIMIZE_TIME_THRESHOLD) { 92 | log.info("----------------------HttpTrace requestUri={}----------------------", requestUri); 93 | } 94 | if (timeTaken > GlobalVariable.SERIOUS_PERFORMANCE_PROBLEMS_TIME_THRESHOLD) { 95 | log.error("----------------------HttpTrace 严重注意:该方法可能存在严重性能问题={}----------------------", timeTaken); 96 | } else if (timeTaken > GlobalVariable.GENERAL_PERFORMANCE_PROBLEMS_TIME_THRESHOLD) { 97 | log.warn("----------------------HttpTrace 注意:该方法可能存在一般性能问题={}----------------------", timeTaken); 98 | } else if (timeTaken > GlobalVariable.NEED_OPTIMIZE_TIME_THRESHOLD) { 99 | log.info("----------------------HttpTrace 提示:检查该方法是否有优化的空间={}----------------------", timeTaken); 100 | } 101 | } 102 | 103 | //=====================================私有方法 end===================================== 104 | 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.core.task.TaskExecutor; 6 | import org.springframework.scheduling.annotation.EnableAsync; 7 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 8 | 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | 11 | @Configuration 12 | @EnableAsync 13 | public class AsyncConfig { 14 | 15 | @Bean 16 | public TaskExecutor taskExecutor() { 17 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 18 | int core = Runtime.getRuntime().availableProcessors(); 19 | executor.setCorePoolSize(core); 20 | executor.setMaxPoolSize(core * 2 + 1); 21 | executor.setKeepAliveSeconds(5); 22 | executor.setQueueCapacity(50); 23 | executor.setThreadNamePrefix("custom-executor-"); 24 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 25 | return executor; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/config/CustomErrorController.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.config; 2 | 3 | import org.springframework.boot.web.servlet.error.ErrorController; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | 10 | @Controller 11 | public class CustomErrorController implements ErrorController { 12 | 13 | @RequestMapping("/error") 14 | public String handleError(HttpServletRequest request) { 15 | Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); 16 | if (statusCode == HttpStatus.NOT_FOUND.value()) { 17 | return "/404"; 18 | } else { 19 | return "/error"; 20 | } 21 | } 22 | 23 | @Override 24 | public String getErrorPath() { 25 | return "/error"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/config/MeterConfig.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.config; 2 | 3 | import io.micrometer.core.instrument.MeterRegistry; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.net.InetAddress; 10 | import java.net.UnknownHostException; 11 | import java.util.UUID; 12 | 13 | @Slf4j 14 | @Component 15 | public class MeterConfig implements MeterRegistryCustomizer { 16 | 17 | @Value("${spring.application.name}") 18 | private String applicationName; 19 | 20 | @Value("${spring.datasource.hikari.pool-name:/myHikariPoolName}") 21 | private String hikariPoolName; 22 | 23 | @Override 24 | public void customize(MeterRegistry registry) { 25 | registry.config().commonTags("application", applicationName); 26 | registry.config().commonTags("hikaricp", hikariPoolName); 27 | try { 28 | String hostAddress = InetAddress.getLocalHost().getHostAddress(); 29 | log.debug("设置 metrics instance-id 为 ip:" + hostAddress); 30 | registry.config().commonTags("instance", hostAddress); 31 | } catch (UnknownHostException e) { 32 | String uuid = UUID.randomUUID().toString(); 33 | registry.config().commonTags("instance", uuid); 34 | log.error("设置 metrics instance-id 为 uuid:" + uuid, e); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/config/OkHttpConfig.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.config; 2 | 3 | import com.cdk8s.tkey.server.util.ExceptionUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import okhttp3.ConnectionPool; 6 | import okhttp3.OkHttpClient; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import javax.net.ssl.SSLContext; 11 | import javax.net.ssl.SSLSocketFactory; 12 | import javax.net.ssl.TrustManager; 13 | import javax.net.ssl.X509TrustManager; 14 | import java.security.KeyManagementException; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.security.SecureRandom; 17 | import java.security.cert.X509Certificate; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | @Slf4j 21 | @Configuration 22 | public class OkHttpConfig { 23 | 24 | @Bean 25 | public X509TrustManager x509TrustManager() { 26 | return new X509TrustManager() { 27 | @Override 28 | public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { 29 | } 30 | 31 | @Override 32 | public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { 33 | } 34 | 35 | @Override 36 | public X509Certificate[] getAcceptedIssuers() { 37 | return new X509Certificate[0]; 38 | } 39 | }; 40 | } 41 | 42 | @Bean 43 | public SSLSocketFactory sslSocketFactory() { 44 | try { 45 | //信任任何链接 46 | SSLContext sslContext = SSLContext.getInstance("TLS"); 47 | sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom()); 48 | return sslContext.getSocketFactory(); 49 | } catch (NoSuchAlgorithmException | KeyManagementException e) { 50 | log.error(ExceptionUtil.getStackTraceAsString(e)); 51 | } 52 | return null; 53 | } 54 | 55 | @Bean 56 | public ConnectionPool pool() { 57 | return new ConnectionPool(200, 5, TimeUnit.MINUTES); 58 | } 59 | 60 | @Bean 61 | public OkHttpClient okHttpClient() { 62 | return new OkHttpClient.Builder() 63 | .sslSocketFactory(sslSocketFactory(), x509TrustManager()) 64 | .retryOnConnectionFailure(false) 65 | .connectionPool(pool()) 66 | .connectTimeout(30, TimeUnit.SECONDS) 67 | .readTimeout(30, TimeUnit.SECONDS) 68 | .writeTimeout(30, TimeUnit.SECONDS) 69 | .build(); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 4 | import com.fasterxml.jackson.annotation.PropertyAccessor; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.data.redis.connection.RedisConnectionFactory; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 11 | import org.springframework.data.redis.serializer.StringRedisSerializer; 12 | 13 | @Configuration 14 | public class RedisConfig { 15 | 16 | @Bean 17 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 18 | RedisTemplate redisTemplate = new RedisTemplate<>(); 19 | redisTemplate.setConnectionFactory(redisConnectionFactory); 20 | 21 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); 22 | 23 | ObjectMapper objectMapper = new ObjectMapper(); 24 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 25 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 26 | 27 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 28 | 29 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 30 | 31 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 32 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 33 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); 34 | 35 | redisTemplate.afterPropertiesSet(); 36 | return redisTemplate; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/config/WebConfig.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.config; 2 | 3 | 4 | import com.cdk8s.tkey.server.pojo.dto.param.resolve.*; 5 | import com.fasterxml.jackson.annotation.JsonInclude; 6 | import com.fasterxml.jackson.databind.DeserializationFeature; 7 | import com.fasterxml.jackson.databind.ObjectMapper; 8 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.http.converter.HttpMessageConverter; 11 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 12 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 13 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 14 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 15 | 16 | import java.util.List; 17 | import java.util.TimeZone; 18 | 19 | 20 | @Configuration 21 | public class WebConfig implements WebMvcConfigurer { 22 | 23 | @Override 24 | public void addArgumentResolvers(List argumentResolvers) { 25 | argumentResolvers.add(new OauthAuthorizeParamArgumentResolver()); 26 | argumentResolvers.add(new OauthFormParamArgumentResolver()); 27 | argumentResolvers.add(new OauthRefreshTokenParamArgumentResolver()); 28 | argumentResolvers.add(new OauthTokenParamArgumentResolver()); 29 | argumentResolvers.add(new OauthIntrospectTokenParamArgumentResolver()); 30 | } 31 | 32 | @Override 33 | public void addCorsMappings(CorsRegistry registry) { 34 | registry.addMapping("/**") 35 | .allowedOrigins("*") 36 | .allowedMethods("*") 37 | .allowedHeaders("*") 38 | .allowCredentials(true) 39 | .maxAge(3600L); 40 | } 41 | 42 | @Override 43 | public void extendMessageConverters(List> converters) { 44 | for (int i = 0; i < converters.size(); i++) { 45 | if (converters.get(i) instanceof MappingJackson2HttpMessageConverter) { 46 | ObjectMapper objectMapper = new ObjectMapper(); 47 | objectMapper.setPropertyNamingStrategy(new PropertyNamingStrategy.SnakeCaseStrategy()); 48 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 49 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 50 | objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); 51 | MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); 52 | converter.setObjectMapper(objectMapper); 53 | converters.set(i, converter); 54 | break; 55 | } 56 | } 57 | } 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/constant/GlobalVariable.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.constant; 2 | 3 | 4 | public interface GlobalVariable { 5 | 6 | //=====================================LOG start===================================== 7 | 8 | // LOG 严重性能问题的时间阀值(毫秒) 9 | int SERIOUS_PERFORMANCE_PROBLEMS_TIME_THRESHOLD = 4000; 10 | 11 | // LOG 一般性能问题的时间阀值(毫秒) 12 | int GENERAL_PERFORMANCE_PROBLEMS_TIME_THRESHOLD = 3000; 13 | 14 | // LOG 修改优化的时间阀值(毫秒) 15 | int NEED_OPTIMIZE_TIME_THRESHOLD = 2000; 16 | 17 | //=====================================LOG end===================================== 18 | 19 | String DEFAULT_LOGIN_PAGE_CLIENT_INFO_KEY = "oauthClient"; 20 | 21 | String DEFAULT_LOGIN_PAGE_PATH = "login"; 22 | String DEFAULT_LOGIN_ERROR_KEY = "errorMsg"; 23 | String DEFAULT_LOGOUT_PAGE_PATH = "logoutSuccess"; 24 | String REDIRECT_URI_PREFIX = "redirect:"; 25 | String HTTP_HEADER_USER_AGENT = "User-Agent"; 26 | 27 | String OAUTH_TGC_PREFIX = "TGC-"; 28 | String OAUTH_CODE_PREFIX = "OC-"; 29 | String OAUTH_ACCESS_TOKEN_PREFIX = "AT-"; 30 | String OAUTH_REFRESH_TOKEN_PREFIX = "RT-"; 31 | 32 | String REDIS_TGC_KEY_PREFIX = "OAUTH:TGC:"; 33 | String REDIS_CLIENT_ID_KEY_PREFIX = "OAUTH:CLIENT_ID:"; 34 | String REDIS_OAUTH_CODE_PREFIX_KEY_PREFIX = "OAUTH:CODE:"; 35 | String REDIS_OAUTH_ACCESS_TOKEN_KEY_PREFIX = "OAUTH:ACCESS_TOKEN:"; 36 | String REDIS_OAUTH_REFRESH_TOKEN_KEY_PREFIX = "OAUTH:REFRESH_TOKEN:"; 37 | String REDIS_OAUTH_USER_INFO_KEY_PREFIX = "OAUTH:USER_INFO:"; 38 | 39 | String OAUTH_TOKEN_TYPE = "Bearer"; 40 | String OAUTH_TOKEN_TYPE_UPPER_PREFIX = "Bearer "; 41 | String OAUTH_TOKEN_TYPE_LOWER_PREFIX = "bearer "; 42 | String BASIC_AUTH_UPPER_PREFIX = "Basic "; 43 | String BASIC_AUTH_LOWER_PREFIX = "basic "; 44 | 45 | String HTTP_HEADER_AUTHORIZATION = "Authorization"; 46 | String OAUTH_SERVER_COOKIE_KEY = "tgc"; 47 | String OAUTH_CODE_RESPONSE_TYPE = "code"; 48 | String OAUTH_STATE_KEY = "state"; 49 | String OAUTH_TOKEN_RESPONSE_TYPE = "token"; 50 | String OAUTH_REFRESH_TOKEN_GRANT_TYPE = "refresh_token"; 51 | String OAUTH_ACCESS_TOKEN_KEY = "access_token"; 52 | String OAUTH_REFRESH_TOKEN_KEY = "refresh_token"; 53 | String OAUTH_TOKEN_TYPE_KEY = "token_type"; 54 | String OAUTH_EXPIRES_IN_KEY = "expires_in"; 55 | String OAUTH_ACCESS_TOKEN_TYPE_HINT = "access_token"; 56 | String OAUTH_CODE_GRANT_TYPE = "authorization_code"; 57 | String OAUTH_CLIENT_GRANT_TYPE = "client_credentials"; 58 | String OAUTH_TOKEN_GRANT_TYPE = "token"; 59 | String OAUTH_PASSWORD_GRANT_TYPE = "password"; 60 | 61 | String OAUTH_ERROR_URI_MSG = "See the full API docs at https://github.com/cdk8s"; 62 | String OAUTH_ERROR_MSG = "invalid request"; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/constant/GlobalVariableToJunit.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.constant; 2 | 3 | 4 | public interface GlobalVariableToJunit { 5 | 6 | 7 | //=====================================biz start===================================== 8 | String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36"; 9 | String IP = "127.0.0.1"; 10 | 11 | String USERNAME = "admin"; 12 | String PASSWORD = "123456"; 13 | String USER_ID = "111111111111111111"; 14 | String ID = "111111111111111111"; 15 | Long ID_LONG = 111111111111111111L; 16 | String USER_EMAIL = "cdk8s@qq.com"; 17 | String USER_INFO_REDIS_KEY = "OAUTH:USER_INFO:111111111111111111"; 18 | 19 | String ACCESS_TOKEN = "AT-102-uUCkO2NgITHWJSD16g89C9loMwCVSQqh"; 20 | String REFRESH_TOKEN = "RT-103-zIYUBA0ddql5cyYGEdpmPcRJH63hOVpQ"; 21 | String CODE = "OC-106-uUddPxoWCEa4NBO5GaVIRJOTZLlWbHNr"; 22 | String CODE2 = "OC-107-uUddPxoWCEa4NBO5GaVIRJOTZLlWbHNr"; 23 | String TGC = "TGC-101-1uo81h7S5ho3ItlGA4cYdF4YIfpOkJDJ"; 24 | String CODE_RESPONSE_TYPE = "code"; 25 | String CODE_GRANT_TYPE = "authorization_code"; 26 | String CLIENT_ID = "test_client_id_1"; 27 | String CLIENT_SECRET = "test_client_secret_1"; 28 | String HTTP_HEADER_BASIC_AUTHORIZATION = "Basic dGVzdF9jbGllbnRfaWRfMTp0ZXN0X2NsaWVudF9zZWNyZXRfMQ=="; 29 | 30 | //=====================================biz end===================================== 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/enums/BasicEnum.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.enums; 2 | 3 | public interface BasicEnum { 4 | int getCode(); 5 | 6 | String getDescription(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/enums/ResponseProduceTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.enums; 2 | 3 | /** 4 | * 返回结果类型:JSON / HTML 5 | */ 6 | public enum ResponseProduceTypeEnum implements BasicEnum { 7 | 8 | JSON(1, "application/json;charset=UTF-8"), 9 | HTML(2, "text/html"); 10 | 11 | private int code; 12 | private String description; 13 | 14 | ResponseProduceTypeEnum(final int code, final String description) { 15 | this.code = code; 16 | this.description = description; 17 | } 18 | 19 | @Override 20 | public int getCode() { 21 | return code; 22 | } 23 | 24 | @Override 25 | public String getDescription() { 26 | return description; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/exception/ExceptionControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.exception; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariable; 4 | import com.cdk8s.tkey.server.enums.ResponseProduceTypeEnum; 5 | import com.cdk8s.tkey.server.util.ExceptionUtil; 6 | import com.cdk8s.tkey.server.util.StringUtil; 7 | import com.cdk8s.tkey.server.util.response.R; 8 | import com.cdk8s.tkey.server.util.response.ResponseErrorObject; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.ExceptionHandler; 13 | import org.springframework.web.bind.annotation.RestControllerAdvice; 14 | import org.springframework.web.servlet.ModelAndView; 15 | 16 | import javax.servlet.http.HttpServletRequest; 17 | 18 | @Slf4j 19 | @RestControllerAdvice 20 | public class ExceptionControllerAdvice { 21 | 22 | private static final String JSON_TYPE = "application/json"; 23 | private static final String X_TYPE = "XMLHttpRequest"; 24 | 25 | 26 | //=====================================业务处理 start===================================== 27 | 28 | 29 | /** 30 | * 系统异常 31 | */ 32 | @ExceptionHandler(SystemException.class) 33 | public Object handleSystemException(HttpServletRequest httpServletRequest, SystemException e) { 34 | String message = e.getMessage(); 35 | if (StringUtil.isBlank(message)) { 36 | message = "系统发生异常,请联系管理员处理"; 37 | } 38 | return getResponseObject(e, HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest, message, null, null); 39 | } 40 | 41 | /** 42 | * OAuth API 业务异常 43 | */ 44 | @ExceptionHandler(OauthApiException.class) 45 | public Object handleOauthApiException(HttpServletRequest httpServletRequest, OauthApiException e) { 46 | String message = e.getMessage(); 47 | ResponseProduceTypeEnum responseProduceTypeEnum = e.getResponseProduceTypeEnum(); 48 | if (null == responseProduceTypeEnum) { 49 | responseProduceTypeEnum = ResponseProduceTypeEnum.JSON; 50 | } 51 | String pagePath = e.getPagePath(); 52 | if (StringUtil.isBlank(pagePath)) { 53 | pagePath = "error"; 54 | } 55 | return getResponseObject(e, HttpStatus.BAD_REQUEST, httpServletRequest, message, responseProduceTypeEnum, pagePath); 56 | } 57 | 58 | 59 | /** 60 | * 其他异常 61 | */ 62 | @ExceptionHandler(Exception.class) 63 | public Object handleException(HttpServletRequest httpServletRequest, Exception e) { 64 | String message = e.getMessage(); 65 | if (StringUtil.isBlank(message)) { 66 | message = "系统发生异常"; 67 | } else { 68 | log.error(message); 69 | for (ExceptionDescriptionEnum exceptionDescription : ExceptionDescriptionEnum.values()) { 70 | if (message.contains(exceptionDescription.getKeyWord())) { 71 | message = exceptionDescription.getDescription(); 72 | break; 73 | } 74 | } 75 | } 76 | return getResponseObject(e, HttpStatus.INTERNAL_SERVER_ERROR, httpServletRequest, message, null, null); 77 | 78 | } 79 | 80 | //=====================================业务处理 end===================================== 81 | //=====================================私有方法 start===================================== 82 | 83 | private Object getResponseObject(Exception e, HttpStatus httpStatus, HttpServletRequest httpServletRequest, String message, ResponseProduceTypeEnum responseProduceTypeEnum, String pagePath) { 84 | 85 | log.error("统一异常信息输出:<{}>", message); 86 | if (log.isDebugEnabled()) { 87 | log.error(ExceptionUtil.getStackTraceAsString(e)); 88 | } 89 | 90 | if (StringUtil.isBlank(pagePath)) { 91 | pagePath = "error"; 92 | } 93 | 94 | // 指定返回类型 95 | if (null != responseProduceTypeEnum) { 96 | if (responseProduceTypeEnum == ResponseProduceTypeEnum.JSON) { 97 | return returnJson(httpStatus, message); 98 | } 99 | if (responseProduceTypeEnum == ResponseProduceTypeEnum.HTML) { 100 | return returnHtml(httpServletRequest, message, pagePath); 101 | } 102 | } 103 | 104 | // 没有指定返回类型,根据 http 请求头进行判断返回什么类型 105 | String contentTypeHeader = httpServletRequest.getHeader("Content-Type"); 106 | String acceptHeader = httpServletRequest.getHeader("Accept"); 107 | String xRequestedWith = httpServletRequest.getHeader("X-Requested-With"); 108 | boolean checkIsJson = (contentTypeHeader != null && contentTypeHeader.contains(JSON_TYPE)) || (acceptHeader != null && acceptHeader.contains(JSON_TYPE)) || X_TYPE.equalsIgnoreCase(xRequestedWith); 109 | if (checkIsJson) { 110 | return returnJson(httpStatus, message); 111 | } else { 112 | return returnHtml(httpServletRequest, message, pagePath); 113 | } 114 | } 115 | 116 | private ResponseEntity returnJson(HttpStatus httpStatus, String message) { 117 | return R.failure(httpStatus, message); 118 | } 119 | 120 | private ModelAndView returnHtml(HttpServletRequest httpServletRequest, String message, String pagePath) { 121 | ModelAndView modelAndView = new ModelAndView(); 122 | modelAndView.addObject(GlobalVariable.DEFAULT_LOGIN_ERROR_KEY, message); 123 | modelAndView.addObject("url", httpServletRequest.getRequestURL()); 124 | modelAndView.setViewName(pagePath); 125 | return modelAndView; 126 | } 127 | 128 | //=====================================私有方法 end===================================== 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/exception/ExceptionDescriptionEnum.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.exception; 2 | 3 | public enum ExceptionDescriptionEnum { 4 | 5 | CANNOT_BE_CAST_TO("cannot be cast to", "系统转换对象异常,请联系管理员进行处理"), 6 | REDIS_CONNECTION_EXCEPTION("RedisConnectionException", "连接缓存失败,请联系管理员进行处理"), 7 | ERROR_QUERYING_DATABASE("Error querying database", "查询数据库失败,请联系管理员进行处理"), 8 | REQUEST_CONTENT_TYPE_NOT_SUPPORTED("Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported", "请求内容类型不支持"), 9 | UNRECOGNIZED_PROPERTY_EXCEPTION("com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException", "JSON 序列化异常,有无法识别的属性"), 10 | NO_HANDLER_FOUND("No handler found for", "请求地址不存在"), 11 | JSON_PAUSE_ERROR("JSON parse error", "JSON 转换失败"), 12 | SQL_DEFAULT_VALUE("doesn't have a default value", "缺少指定字段值"), 13 | SQL_QUERY_ERROR("Error querying database", "查询数据库失败"), 14 | INPUT_FORMAT_ERROR("For input string:", "输入格式有误"), 15 | DATA_SIZE_TOO_LONG("java.sql.SQLException: Data too long for column", "存在某个字段过长"), 16 | DATABASE_UPDATE_ERROR("Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException", "操作数据失败,违背完整性约束"), 17 | SERVICE_REFUSE("java.net.ConnectException: Connection refused (Connection refused)", "连接某些服务被拒接"), 18 | SERVICE_NOT_AVAILABLE("com.netflix.client.ClientException: Load balancer does not have available server for client", "存在某些服务不可用"), 19 | SERVICE_TIMEOUT("java.util.concurrent.TimeoutException", "10000服务连接超时"), 20 | MYSQL_CONNECTION_TIMEOUT("Could not open JDBC Connection for transaction; nested exception is com.mysql.jdbc", "JDBC 服务连接超时"), 21 | REDIS_CONNECTION_TIMEOUT(" redis.clients.jedis.exceptions.JedisConnectionException", "Redis 服务连接超时"), 22 | RABBITMQ_CONNECTION_TIMEOUT("org.springframework.amqp.AmqpTimeoutException: java.util.concurrent.TimeoutException", "MQ 服务连接超时"), 23 | FEIGN_FAIL_AND_NO_FALLBACK("failed and no fallback available", "Feign 连接失败且无回退方法"), 24 | FEIGN_FAIL_AND_FALLBACK_AVAILABLE("failed and fallback failed", "Feign 连接失败且回退失败"); 25 | 26 | private String keyWord; 27 | private String description; 28 | 29 | public String getKeyWord() { 30 | return keyWord; 31 | } 32 | 33 | public String getDescription() { 34 | return description; 35 | } 36 | 37 | ExceptionDescriptionEnum(final String keyWord, final String description) { 38 | this.keyWord = keyWord; 39 | this.description = description; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/exception/OauthApiException.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.exception; 2 | 3 | import com.cdk8s.tkey.server.enums.ResponseProduceTypeEnum; 4 | import com.cdk8s.tkey.server.util.response.ResponseErrorEnum; 5 | 6 | public class OauthApiException extends RuntimeException { 7 | private static final long serialVersionUID = 1L; 8 | 9 | private String message; 10 | private int code = 500; 11 | private String pagePath; 12 | private ResponseProduceTypeEnum responseProduceTypeEnum; 13 | 14 | public OauthApiException(String message) { 15 | super(message); 16 | this.message = message; 17 | } 18 | 19 | public OauthApiException(String message, Throwable e) { 20 | super(message, e); 21 | this.message = message; 22 | } 23 | 24 | public OauthApiException(String message, ResponseErrorEnum responseErrorEnum) { 25 | super(message); 26 | this.message = message; 27 | this.code = responseErrorEnum.getCode(); 28 | } 29 | 30 | public OauthApiException(String message, ResponseProduceTypeEnum responseProduceTypeEnum) { 31 | super(message); 32 | this.message = message; 33 | this.responseProduceTypeEnum = responseProduceTypeEnum; 34 | } 35 | 36 | public OauthApiException(String message, ResponseErrorEnum responseErrorEnum, ResponseProduceTypeEnum responseProduceTypeEnum) { 37 | super(message); 38 | this.message = message; 39 | this.responseProduceTypeEnum = responseProduceTypeEnum; 40 | this.code = responseErrorEnum.getCode(); 41 | } 42 | 43 | public OauthApiException(String message, ResponseProduceTypeEnum responseProduceTypeEnum, String pagePath) { 44 | super(message); 45 | this.message = message; 46 | this.pagePath = pagePath; 47 | this.responseProduceTypeEnum = responseProduceTypeEnum; 48 | } 49 | 50 | public OauthApiException(String message, ResponseErrorEnum responseErrorEnum, ResponseProduceTypeEnum responseProduceTypeEnum, String pagePath) { 51 | super(message); 52 | this.message = message; 53 | this.pagePath = pagePath; 54 | this.responseProduceTypeEnum = responseProduceTypeEnum; 55 | this.code = responseErrorEnum.getCode(); 56 | } 57 | 58 | public OauthApiException(String message, ResponseErrorEnum responseErrorEnum, Throwable e) { 59 | super(message, e); 60 | this.message = message; 61 | this.code = responseErrorEnum.getCode(); 62 | } 63 | 64 | @Override 65 | public String getMessage() { 66 | return message; 67 | } 68 | 69 | public void setMessage(String message) { 70 | this.message = message; 71 | } 72 | 73 | public int getCode() { 74 | return code; 75 | } 76 | 77 | public void setCode(int code) { 78 | this.code = code; 79 | } 80 | 81 | public ResponseProduceTypeEnum getResponseProduceTypeEnum() { 82 | return responseProduceTypeEnum; 83 | } 84 | 85 | public void setResponseProduceTypeEnum(ResponseProduceTypeEnum responseProduceTypeEnum) { 86 | this.responseProduceTypeEnum = responseProduceTypeEnum; 87 | } 88 | 89 | public String getPagePath() { 90 | return pagePath; 91 | } 92 | 93 | public void setPagePath(String pagePath) { 94 | this.pagePath = pagePath; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/exception/SystemException.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.exception; 2 | 3 | 4 | import com.cdk8s.tkey.server.util.response.ResponseErrorEnum; 5 | 6 | public class SystemException extends RuntimeException { 7 | private static final long serialVersionUID = 1L; 8 | 9 | private String message; 10 | private int code = 500; 11 | 12 | public SystemException(String message) { 13 | super(message); 14 | this.message = message; 15 | } 16 | 17 | public SystemException(String message, Throwable e) { 18 | super(message, e); 19 | this.message = message; 20 | } 21 | 22 | public SystemException(String message, ResponseErrorEnum responseErrorEnum) { 23 | super(message); 24 | this.message = message; 25 | this.code = responseErrorEnum.getCode(); 26 | } 27 | 28 | public SystemException(String message, ResponseErrorEnum responseErrorEnum, Throwable e) { 29 | super(message, e); 30 | this.message = message; 31 | this.code = responseErrorEnum.getCode(); 32 | } 33 | 34 | @Override 35 | public String getMessage() { 36 | return message; 37 | } 38 | 39 | public void setMessage(String message) { 40 | this.message = message; 41 | } 42 | 43 | public int getCode() { 44 | return code; 45 | } 46 | 47 | public void setCode(int code) { 48 | this.code = code; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/init/ApplicationHealthInitRunner.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.init; 2 | 3 | import com.cdk8s.tkey.server.actuator.CustomUPMSApiServerHealthEndpoint; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.ApplicationArguments; 7 | import org.springframework.boot.ApplicationRunner; 8 | import org.springframework.boot.actuate.health.Health; 9 | import org.springframework.boot.actuate.health.ReactiveHealthIndicator; 10 | import org.springframework.boot.actuate.health.Status; 11 | import org.springframework.stereotype.Component; 12 | import reactor.core.publisher.Mono; 13 | 14 | @Slf4j 15 | @Component 16 | public class ApplicationHealthInitRunner implements ApplicationRunner { 17 | 18 | @Autowired 19 | private CustomUPMSApiServerHealthEndpoint customUPMSApiServerHealthEndpoint; 20 | 21 | @Autowired 22 | private ReactiveHealthIndicator redisReactiveHealthIndicator; 23 | 24 | //=====================================业务处理 start===================================== 25 | 26 | @Override 27 | public void run(ApplicationArguments args) { 28 | http(); 29 | redis(); 30 | } 31 | 32 | //=====================================业务处理 end===================================== 33 | //=====================================私有方法 start===================================== 34 | 35 | private void http() { 36 | Health customUPMSHealth = customUPMSApiServerHealthEndpoint.health(); 37 | if (customUPMSHealth.getStatus().equals(Status.DOWN)) { 38 | log.error("启动请求 UPMS 接口失败"); 39 | } 40 | } 41 | 42 | private void redis() { 43 | Mono redisHealth = redisReactiveHealthIndicator.health(); 44 | redisHealth.subscribe(h -> { 45 | if (h.getStatus().equals(Status.DOWN)) { 46 | log.error("启动连接 Redis 失败"); 47 | } 48 | }); 49 | } 50 | 51 | //=====================================私有方法 end===================================== 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/init/ApplicationTestDataInitRunner.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.init; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariable; 4 | import com.cdk8s.tkey.server.constant.GlobalVariableToJunit; 5 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthAccessTokenToRedisBO; 6 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthClientToRedisBO; 7 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthCodeToRedisBO; 8 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthRefreshTokenToRedisBO; 9 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 10 | import com.cdk8s.tkey.server.properties.OauthProperties; 11 | import com.cdk8s.tkey.server.util.JsonUtil; 12 | import com.cdk8s.tkey.server.util.redis.StringRedisService; 13 | import lombok.SneakyThrows; 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.ApplicationArguments; 17 | import org.springframework.boot.ApplicationRunner; 18 | import org.springframework.context.annotation.Profile; 19 | import org.springframework.stereotype.Component; 20 | 21 | 22 | @Slf4j 23 | @Profile({"dev", "gatling", "test", "junit"}) 24 | @Component 25 | public class ApplicationTestDataInitRunner implements ApplicationRunner { 26 | 27 | @Autowired 28 | private StringRedisService clientRedisService; 29 | 30 | @Autowired 31 | private StringRedisService codeRedisService; 32 | 33 | @Autowired 34 | private StringRedisService accessTokenRedisService; 35 | 36 | @Autowired 37 | private StringRedisService refreshTokenRedisService; 38 | 39 | @Autowired 40 | private OauthProperties oauthProperties; 41 | 42 | //=====================================业务处理 start===================================== 43 | 44 | @SneakyThrows 45 | @Override 46 | public void run(ApplicationArguments args) { 47 | log.info("=================================预设 Redis 测试数据 Start================================="); 48 | 49 | OauthClientToRedisBO oauthClientToRedisBO = getClient(); 50 | clientRedisService.set(GlobalVariable.REDIS_CLIENT_ID_KEY_PREFIX + oauthClientToRedisBO.getClientId(), JsonUtil.toJson(oauthClientToRedisBO)); 51 | 52 | accessTokenRedisService.set(GlobalVariable.REDIS_OAUTH_ACCESS_TOKEN_KEY_PREFIX + GlobalVariableToJunit.ACCESS_TOKEN, getAccessToken(), oauthProperties.getAccessTokenMaxTimeToLiveInSeconds()); 53 | refreshTokenRedisService.set(GlobalVariable.REDIS_OAUTH_REFRESH_TOKEN_KEY_PREFIX + GlobalVariableToJunit.REFRESH_TOKEN, getRefreshToken(), oauthProperties.getRefreshTokenMaxTimeToLiveInSeconds()); 54 | codeRedisService.set(GlobalVariable.REDIS_OAUTH_CODE_PREFIX_KEY_PREFIX + GlobalVariableToJunit.CODE, getCode(), oauthProperties.getCodeMaxTimeToLiveInSeconds()); 55 | codeRedisService.set(GlobalVariable.REDIS_OAUTH_CODE_PREFIX_KEY_PREFIX + GlobalVariableToJunit.CODE2, getCode(), oauthProperties.getCodeMaxTimeToLiveInSeconds()); 56 | 57 | log.info("=================================预设 Redis 测试数据 End================================="); 58 | 59 | } 60 | 61 | //=====================================业务处理 end===================================== 62 | //=====================================私有方法 start===================================== 63 | 64 | private OauthClientToRedisBO getClient() { 65 | OauthClientToRedisBO oauthClientToRedisBO = new OauthClientToRedisBO(); 66 | oauthClientToRedisBO.setId(GlobalVariableToJunit.ID_LONG); 67 | oauthClientToRedisBO.setClientName("通用测试系统1"); 68 | oauthClientToRedisBO.setClientId(GlobalVariableToJunit.CLIENT_ID); 69 | oauthClientToRedisBO.setClientSecret("test_client_secret_1"); 70 | oauthClientToRedisBO.setClientUrl("^(http|https)://.*"); 71 | oauthClientToRedisBO.setClientDesc("通用测试系统1"); 72 | oauthClientToRedisBO.setLogoUrl("https://www.easyicon.net/api/resizeApi.php?id=1200686&size=32"); 73 | return oauthClientToRedisBO; 74 | } 75 | 76 | private OauthAccessTokenToRedisBO getAccessToken() { 77 | OauthAccessTokenToRedisBO oauthAccessTokenToRedisBO = new OauthAccessTokenToRedisBO(); 78 | OauthUserAttribute oauthUserAttribute = new OauthUserAttribute(); 79 | oauthUserAttribute.setEmail(GlobalVariableToJunit.USER_EMAIL); 80 | oauthUserAttribute.setUserId(GlobalVariableToJunit.USER_ID); 81 | oauthUserAttribute.setUsername(GlobalVariableToJunit.USERNAME); 82 | 83 | oauthAccessTokenToRedisBO.setUserAttribute(oauthUserAttribute); 84 | oauthAccessTokenToRedisBO.setGrantType(GlobalVariableToJunit.CODE_GRANT_TYPE); 85 | oauthAccessTokenToRedisBO.setClientId(GlobalVariableToJunit.CLIENT_ID); 86 | oauthAccessTokenToRedisBO.setIat(1561522123L); 87 | return oauthAccessTokenToRedisBO; 88 | } 89 | 90 | private OauthRefreshTokenToRedisBO getRefreshToken() { 91 | OauthRefreshTokenToRedisBO oauthRefreshTokenToRedisBO = new OauthRefreshTokenToRedisBO(); 92 | oauthRefreshTokenToRedisBO.setUserInfoRedisKey(GlobalVariableToJunit.USER_INFO_REDIS_KEY); 93 | oauthRefreshTokenToRedisBO.setGrantType(GlobalVariableToJunit.CODE_GRANT_TYPE); 94 | oauthRefreshTokenToRedisBO.setClientId(GlobalVariableToJunit.CLIENT_ID); 95 | oauthRefreshTokenToRedisBO.setIat(1561522123L); 96 | return oauthRefreshTokenToRedisBO; 97 | 98 | } 99 | 100 | private OauthCodeToRedisBO getCode() { 101 | OauthCodeToRedisBO oauthCodeToRedisBO = new OauthCodeToRedisBO(); 102 | oauthCodeToRedisBO.setTgc(GlobalVariableToJunit.TGC); 103 | oauthCodeToRedisBO.setUserInfoRedisKey(GlobalVariableToJunit.USER_INFO_REDIS_KEY); 104 | oauthCodeToRedisBO.setClientId(GlobalVariableToJunit.CLIENT_ID); 105 | oauthCodeToRedisBO.setIat(1561522123L); 106 | return oauthCodeToRedisBO; 107 | } 108 | 109 | //=====================================私有方法 end===================================== 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthAccessTokenToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 4 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 5 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.ToString; 10 | 11 | import java.io.Serializable; 12 | 13 | @Getter 14 | @Setter 15 | @ToString 16 | @NoArgsConstructor 17 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 18 | public class OauthAccessTokenToRedisBO extends OauthTokenToRedisBO implements Serializable { 19 | 20 | private static final long serialVersionUID = 4489154215852194179L; 21 | 22 | private OauthUserAttribute userAttribute; 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthClientToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | import java.io.Serializable; 8 | 9 | 10 | @Setter 11 | @Getter 12 | @ToString 13 | public class OauthClientToRedisBO implements Serializable { 14 | 15 | private static final long serialVersionUID = 5004734902174453355L; 16 | 17 | private Long id; 18 | private String clientName; 19 | private String clientId; 20 | private String clientSecret; 21 | private String clientUrl; 22 | private String clientDesc; 23 | private String logoUrl; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthCodeToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | import java.io.Serializable; 11 | 12 | @Getter 13 | @Setter 14 | @ToString 15 | @NoArgsConstructor 16 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 17 | public class OauthCodeToRedisBO implements Serializable { 18 | 19 | private static final long serialVersionUID = -5698055629424777336L; 20 | private String tgc; 21 | private String userInfoRedisKey; 22 | private String clientId; 23 | 24 | private Long iat; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthRefreshTokenToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | import java.io.Serializable; 11 | 12 | @Getter 13 | @Setter 14 | @ToString 15 | @NoArgsConstructor 16 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 17 | public class OauthRefreshTokenToRedisBO extends OauthTokenToRedisBO implements Serializable { 18 | 19 | private static final long serialVersionUID = -2849331499164550050L; 20 | 21 | //因为不会经常被查询,所以不直接存用户信息 22 | private String userInfoRedisKey; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthTgcToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | import java.io.Serializable; 11 | 12 | @Getter 13 | @Setter 14 | @ToString 15 | @NoArgsConstructor 16 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 17 | public class OauthTgcToRedisBO implements Serializable { 18 | 19 | private static final long serialVersionUID = 41535419894343419L; 20 | 21 | private String userInfoRedisKey; 22 | private Long iat; 23 | 24 | private Boolean boolIsRememberMe; 25 | private Boolean boolIsMobile; 26 | 27 | private String userAgent; 28 | private String requestIp; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthTokenToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | import java.io.Serializable; 11 | 12 | @Getter 13 | @Setter 14 | @ToString 15 | @NoArgsConstructor 16 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 17 | public class OauthTokenToRedisBO implements Serializable { 18 | 19 | private static final long serialVersionUID = 6529969868543562169L; 20 | 21 | private String grantType; 22 | private String clientId; 23 | private Long iat; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/cache/OauthUserInfoToRedisBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.cache; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 4 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 5 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | import lombok.ToString; 10 | 11 | import java.io.Serializable; 12 | 13 | @Getter 14 | @Setter 15 | @ToString 16 | @NoArgsConstructor 17 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 18 | public class OauthUserInfoToRedisBO implements Serializable { 19 | 20 | 21 | private static final long serialVersionUID = 970124664158178650L; 22 | private OauthUserAttribute userAttribute; 23 | 24 | private Long iat; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/bo/handle/OauthTokenStrategyHandleBO.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.bo.handle; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OauthTokenStrategyHandleBO { 12 | private String userInfoRedisKey; 13 | private OauthUserAttribute userAttribute; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/OauthIntrospect.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | @Getter 11 | @Setter 12 | @ToString 13 | @NoArgsConstructor 14 | public class OauthIntrospect implements Serializable { 15 | 16 | private static final long serialVersionUID = 3000085116559990818L; 17 | 18 | private String tokenType; 19 | private String grantType; 20 | private String clientId; 21 | private Long exp; 22 | private Long iat; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/OauthToken.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | @Getter 11 | @Setter 12 | @ToString 13 | @NoArgsConstructor 14 | public class OauthToken implements Serializable { 15 | 16 | private static final long serialVersionUID = 7975415790497139511L; 17 | 18 | private String accessToken; 19 | private String tokenType; 20 | private Integer expiresIn; 21 | 22 | private String refreshToken; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/OauthUserAttribute.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | @Getter 11 | @Setter 12 | @ToString 13 | @NoArgsConstructor 14 | public class OauthUserAttribute implements Serializable { 15 | 16 | private static final long serialVersionUID = -5890673731362415002L; 17 | 18 | private String email; 19 | private String userId; 20 | private String username; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/OauthUserProfile.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import java.io.Serializable; 9 | 10 | @Getter 11 | @Setter 12 | @ToString 13 | @NoArgsConstructor 14 | public class OauthUserProfile implements Serializable { 15 | 16 | private static final long serialVersionUID = 8098354063458373513L; 17 | 18 | // root 对象中必须要有一个主键,Spring Security 的 FixedPrincipalExtractor.java 限定了这几个:"user", "username","userid", "user_id", "login", "id", "name" 19 | // 也因为这个场景,所以这里冗余了其他几个属性 20 | // 客户端需要用到哪个属性作为主键就用哪个,没必要全部搬过去 21 | private String username; 22 | private String name; 23 | private String id; 24 | private String userId; 25 | 26 | private OauthUserAttribute userAttribute; 27 | private String grantType; 28 | private String clientId; 29 | 30 | private Long iat; 31 | private Long exp; 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/OauthAuthorizeParam.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OauthAuthorizeParam { 12 | private String responseType; 13 | private String clientId; 14 | private String redirectUri; 15 | private String state; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/OauthClientParam.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OauthClientParam { 12 | 13 | private String clientId; 14 | private String clientSecret; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/OauthFormLoginParam.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OauthFormLoginParam extends OauthAuthorizeParam { 12 | private String username; 13 | private String password; 14 | private Boolean boolIsRememberMe; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/OauthIntrospectTokenParam.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | @Setter 8 | @Getter 9 | @ToString 10 | public class OauthIntrospectTokenParam extends OauthClientParam { 11 | 12 | private String token; 13 | private String tokenTypeHint; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/OauthRefreshTokenParam.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OauthRefreshTokenParam { 12 | private String grantType; 13 | private String refreshToken; 14 | private String clientId; 15 | private String clientSecret; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/OauthTokenParam.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OauthTokenParam extends OauthClientParam { 12 | private String grantType; 13 | 14 | private String code; 15 | private String refreshToken; 16 | private String redirectUri; 17 | 18 | private String username; 19 | private String password; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/resolve/OauthAuthorizeParamArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param.resolve; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.param.OauthAuthorizeParam; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.web.bind.support.WebDataBinderFactory; 6 | import org.springframework.web.context.request.NativeWebRequest; 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | import org.springframework.web.method.support.ModelAndViewContainer; 9 | 10 | public class OauthAuthorizeParamArgumentResolver implements HandlerMethodArgumentResolver { 11 | 12 | @Override 13 | public boolean supportsParameter(MethodParameter methodParameter) { 14 | return methodParameter.getParameterType().equals(OauthAuthorizeParam.class); 15 | } 16 | 17 | @Override 18 | public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { 19 | OauthAuthorizeParam param = new OauthAuthorizeParam(); 20 | param.setResponseType(nativeWebRequest.getParameter("response_type")); 21 | param.setClientId(nativeWebRequest.getParameter("client_id")); 22 | param.setRedirectUri(nativeWebRequest.getParameter("redirect_uri")); 23 | param.setState(nativeWebRequest.getParameter("state")); 24 | return param; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/resolve/OauthFormParamArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param.resolve; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.param.OauthFormLoginParam; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.web.bind.support.WebDataBinderFactory; 6 | import org.springframework.web.context.request.NativeWebRequest; 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | import org.springframework.web.method.support.ModelAndViewContainer; 9 | 10 | public class OauthFormParamArgumentResolver implements HandlerMethodArgumentResolver { 11 | 12 | @Override 13 | public boolean supportsParameter(MethodParameter methodParameter) { 14 | return methodParameter.getParameterType().equals(OauthFormLoginParam.class); 15 | } 16 | 17 | @Override 18 | public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { 19 | OauthFormLoginParam param = new OauthFormLoginParam(); 20 | param.setUsername(nativeWebRequest.getParameter("username")); 21 | param.setPassword(nativeWebRequest.getParameter("password")); 22 | param.setResponseType(nativeWebRequest.getParameter("response_type")); 23 | param.setClientId(nativeWebRequest.getParameter("client_id")); 24 | param.setRedirectUri(nativeWebRequest.getParameter("redirect_uri")); 25 | param.setState(nativeWebRequest.getParameter("state")); 26 | param.setBoolIsRememberMe(Boolean.valueOf(nativeWebRequest.getParameter("bool_is_remember_me"))); 27 | return param; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/resolve/OauthIntrospectTokenParamArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param.resolve; 2 | 3 | 4 | import com.cdk8s.tkey.server.pojo.dto.param.OauthIntrospectTokenParam; 5 | import org.springframework.core.MethodParameter; 6 | import org.springframework.web.bind.support.WebDataBinderFactory; 7 | import org.springframework.web.context.request.NativeWebRequest; 8 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 9 | import org.springframework.web.method.support.ModelAndViewContainer; 10 | 11 | public class OauthIntrospectTokenParamArgumentResolver implements HandlerMethodArgumentResolver { 12 | 13 | @Override 14 | public boolean supportsParameter(MethodParameter methodParameter) { 15 | return methodParameter.getParameterType().equals(OauthIntrospectTokenParam.class); 16 | } 17 | 18 | @Override 19 | public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { 20 | OauthIntrospectTokenParam param = new OauthIntrospectTokenParam(); 21 | param.setClientId(nativeWebRequest.getParameter("client_id")); 22 | param.setClientSecret(nativeWebRequest.getParameter("client_secret")); 23 | param.setToken(nativeWebRequest.getParameter("token")); 24 | param.setTokenTypeHint(nativeWebRequest.getParameter("token_type_hint")); 25 | return param; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/resolve/OauthRefreshTokenParamArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param.resolve; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.param.OauthRefreshTokenParam; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.web.bind.support.WebDataBinderFactory; 6 | import org.springframework.web.context.request.NativeWebRequest; 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | import org.springframework.web.method.support.ModelAndViewContainer; 9 | 10 | public class OauthRefreshTokenParamArgumentResolver implements HandlerMethodArgumentResolver { 11 | 12 | @Override 13 | public boolean supportsParameter(MethodParameter methodParameter) { 14 | return methodParameter.getParameterType().equals(OauthRefreshTokenParam.class); 15 | } 16 | 17 | @Override 18 | public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { 19 | OauthRefreshTokenParam param = new OauthRefreshTokenParam(); 20 | param.setGrantType(nativeWebRequest.getParameter("grant_type")); 21 | param.setRefreshToken(nativeWebRequest.getParameter("refresh_token")); 22 | param.setClientId(nativeWebRequest.getParameter("client_id")); 23 | param.setClientSecret(nativeWebRequest.getParameter("client_secret")); 24 | return param; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/pojo/dto/param/resolve/OauthTokenParamArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.pojo.dto.param.resolve; 2 | 3 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.web.bind.support.WebDataBinderFactory; 6 | import org.springframework.web.context.request.NativeWebRequest; 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver; 8 | import org.springframework.web.method.support.ModelAndViewContainer; 9 | 10 | public class OauthTokenParamArgumentResolver implements HandlerMethodArgumentResolver { 11 | 12 | @Override 13 | public boolean supportsParameter(MethodParameter methodParameter) { 14 | return methodParameter.getParameterType().equals(OauthTokenParam.class); 15 | } 16 | 17 | @Override 18 | public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) { 19 | OauthTokenParam param = new OauthTokenParam(); 20 | param.setGrantType(nativeWebRequest.getParameter("grant_type")); 21 | param.setClientId(nativeWebRequest.getParameter("client_id")); 22 | param.setClientSecret(nativeWebRequest.getParameter("client_secret")); 23 | param.setCode(nativeWebRequest.getParameter("code")); 24 | param.setRefreshToken(nativeWebRequest.getParameter("refresh_token")); 25 | param.setRedirectUri(nativeWebRequest.getParameter("redirect_uri")); 26 | param.setUsername(nativeWebRequest.getParameter("username")); 27 | param.setPassword(nativeWebRequest.getParameter("password")); 28 | return param; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/properties/OauthProperties.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.stereotype.Component; 8 | 9 | 10 | /** 11 | * 604800s = 7 天 12 | * 86400s = 24 小时 13 | * 43200s = 12 小时 14 | */ 15 | @Setter 16 | @Getter 17 | @ToString 18 | @Component 19 | @ConfigurationProperties(prefix = "tkey.sso.oauth") 20 | public class OauthProperties { 21 | 22 | private String errorUriMsg = "See the full API docs at https://github.com/cdk8s"; 23 | private Integer nodeNumber = 10; 24 | private Boolean tgcCookieSecure = true; 25 | private Integer rememberMeMaxTimeToLiveInSeconds = 604800; 26 | private Integer codeMaxTimeToLiveInSeconds = 120; 27 | private Integer accessTokenMaxTimeToLiveInSeconds = 43200; 28 | private Integer refreshTokenMaxTimeToLiveInSeconds = 86400; 29 | private Integer tgcAndUserInfoMaxTimeToLiveInSeconds = 86400; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/retry/RetryService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.retry; 2 | 3 | 4 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 5 | import com.cdk8s.tkey.server.service.OauthThirdPartyApiService; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.retry.annotation.Backoff; 9 | import org.springframework.retry.annotation.Recover; 10 | import org.springframework.retry.annotation.Retryable; 11 | import org.springframework.stereotype.Service; 12 | 13 | 14 | @Service 15 | @Slf4j 16 | public class RetryService { 17 | 18 | @Autowired 19 | private OauthThirdPartyApiService oauthThirdPartyApiService; 20 | 21 | //=====================================调用验证用户名密码的 retry 逻辑 start===================================== 22 | 23 | @Retryable(value = {Exception.class}, maxAttempts = 2, backoff = @Backoff(delay = 2000L, multiplier = 1)) 24 | public OauthUserAttribute getOauthUserAttributeBO(String username, String password) { 25 | return oauthThirdPartyApiService.getOauthUserAttributeDTO(username, password); 26 | 27 | } 28 | 29 | @Recover 30 | public OauthUserAttribute getOauthUserAttributeBORecover(Exception e) { 31 | log.error("多次重试调用验证用户名密码接口失败=<{}>", e.getMessage()); 32 | return new OauthUserAttribute(); 33 | } 34 | 35 | //=====================================调用验证用户名密码的 end===================================== 36 | 37 | 38 | //=====================================私有方法 start===================================== 39 | 40 | //=====================================私有方法 end===================================== 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/service/OauthCheckParamService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.service; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariable; 4 | import com.cdk8s.tkey.server.enums.ResponseProduceTypeEnum; 5 | import com.cdk8s.tkey.server.exception.OauthApiException; 6 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthAccessTokenToRedisBO; 7 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthClientToRedisBO; 8 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthTgcToRedisBO; 9 | import com.cdk8s.tkey.server.pojo.dto.OauthIntrospect; 10 | import com.cdk8s.tkey.server.pojo.dto.param.OauthAuthorizeParam; 11 | import com.cdk8s.tkey.server.pojo.dto.param.OauthFormLoginParam; 12 | import com.cdk8s.tkey.server.pojo.dto.param.OauthIntrospectTokenParam; 13 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 14 | import com.cdk8s.tkey.server.util.StringUtil; 15 | import com.cdk8s.tkey.server.util.redis.StringRedisService; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.stereotype.Service; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import java.util.HashMap; 22 | import java.util.Map; 23 | 24 | 25 | @Service 26 | @Slf4j 27 | public class OauthCheckParamService { 28 | 29 | @Autowired 30 | private OauthClientService oauthClientService; 31 | 32 | @Autowired 33 | private StringRedisService tgcRedisService; 34 | 35 | @Autowired 36 | private StringRedisService accessTokenRedisService; 37 | 38 | //=====================================业务处理 start===================================== 39 | 40 | public String checkCookieTgc(String userAgent, String requestIp, String tgcCookieValue) { 41 | 42 | try { 43 | checkUserAgentAndRequestIpParam(userAgent, requestIp); 44 | } catch (Exception e) { 45 | throw new OauthApiException(e.getMessage(), ResponseProduceTypeEnum.HTML); 46 | } 47 | 48 | OauthTgcToRedisBO oauthTgcToRedisBO = tgcRedisService.get(GlobalVariable.REDIS_TGC_KEY_PREFIX + tgcCookieValue); 49 | if (null == oauthTgcToRedisBO) { 50 | throw new OauthApiException("TGC 已失效,请重新登录", ResponseProduceTypeEnum.HTML, GlobalVariable.DEFAULT_LOGIN_PAGE_PATH); 51 | } 52 | 53 | // 防止重放攻击 54 | if ((!StringUtil.equalsIgnoreCase(oauthTgcToRedisBO.getUserAgent(), userAgent)) || (!StringUtil.equalsIgnoreCase(oauthTgcToRedisBO.getRequestIp(), requestIp))) { 55 | throw new OauthApiException("您的登录历史密钥只能在同一台电脑和网络上使用", ResponseProduceTypeEnum.HTML); 56 | } 57 | 58 | return oauthTgcToRedisBO.getUserInfoRedisKey(); 59 | } 60 | 61 | public void checkGrantTypeParam(String grantType) { 62 | if (StringUtil.isBlank(grantType)) { 63 | throw new OauthApiException("grant_type 不能为空"); 64 | } 65 | 66 | Map grantTypeMap = new HashMap<>(4); 67 | grantTypeMap.put(GlobalVariable.OAUTH_CODE_GRANT_TYPE, GlobalVariable.OAUTH_CODE_GRANT_TYPE); 68 | grantTypeMap.put(GlobalVariable.OAUTH_CLIENT_GRANT_TYPE, GlobalVariable.OAUTH_CLIENT_GRANT_TYPE); 69 | grantTypeMap.put(GlobalVariable.OAUTH_PASSWORD_GRANT_TYPE, GlobalVariable.OAUTH_PASSWORD_GRANT_TYPE); 70 | grantTypeMap.put(GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE, GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE); 71 | 72 | if (null == grantTypeMap.get(grantType)) { 73 | throw new OauthApiException("请求类型不匹配"); 74 | } 75 | } 76 | 77 | public OauthAccessTokenToRedisBO checkAccessTokenParam(final HttpServletRequest request) { 78 | String accessTokenFromHeader = request.getHeader(GlobalVariable.HTTP_HEADER_AUTHORIZATION); 79 | String accessTokenFromRequest = request.getParameter(GlobalVariable.OAUTH_ACCESS_TOKEN_KEY); 80 | 81 | if (StringUtil.isBlank(accessTokenFromHeader) && StringUtil.isBlank(accessTokenFromRequest)) { 82 | throw new OauthApiException("access_token 不能为空"); 83 | } 84 | 85 | String accessToken; 86 | if (StringUtil.isNotBlank(accessTokenFromHeader)) { 87 | // header 参数优先级高于 form 88 | if (StringUtil.containsIgnoreCase(accessTokenFromHeader, GlobalVariable.OAUTH_TOKEN_TYPE_UPPER_PREFIX)) { 89 | String replaceIgnoreCase = StringUtil.replaceIgnoreCase(accessTokenFromHeader, GlobalVariable.OAUTH_TOKEN_TYPE_UPPER_PREFIX, GlobalVariable.OAUTH_TOKEN_TYPE_LOWER_PREFIX); 90 | accessToken = StringUtil.substringAfter(replaceIgnoreCase, GlobalVariable.OAUTH_TOKEN_TYPE_LOWER_PREFIX); 91 | } else { 92 | accessToken = accessTokenFromHeader; 93 | } 94 | } else { 95 | accessToken = accessTokenFromRequest; 96 | } 97 | 98 | if (!StringUtil.containsIgnoreCase(accessToken, GlobalVariable.OAUTH_ACCESS_TOKEN_PREFIX)) { 99 | throw new OauthApiException("access_token 参数格式不对,必须包含:" + GlobalVariable.OAUTH_ACCESS_TOKEN_PREFIX); 100 | } 101 | 102 | OauthAccessTokenToRedisBO oauthAccessTokenToRedisBO = accessTokenRedisService.get(GlobalVariable.REDIS_OAUTH_ACCESS_TOKEN_KEY_PREFIX + accessToken); 103 | if (null == oauthAccessTokenToRedisBO) { 104 | throw new OauthApiException("access_token 已失效"); 105 | } 106 | 107 | return oauthAccessTokenToRedisBO; 108 | } 109 | 110 | public OauthClientToRedisBO checkClientIdParam(String clientId) { 111 | if (StringUtil.isBlank(clientId)) { 112 | throw new OauthApiException("client_id 参数不能为空"); 113 | } 114 | 115 | // clientId 只能包含数字、字母和下划线 116 | String regString = "^[A-Za-z0-9_]+$"; 117 | if (!clientId.matches(regString)) { 118 | throw new OauthApiException("client_id 参数值必须为字母、数字、下划线组成"); 119 | } 120 | 121 | OauthClientToRedisBO oauthClientToRedisBO = oauthClientService.findByClientId(clientId); 122 | if (null == oauthClientToRedisBO || StringUtil.isBlank(oauthClientToRedisBO.getClientId())) { 123 | throw new OauthApiException("client_id 不存在"); 124 | } 125 | 126 | return oauthClientToRedisBO; 127 | } 128 | 129 | public void checkClientIdAndClientSecretParam(String clientId, String clientSecret, Boolean clientSecretIsRequired) { 130 | 131 | OauthClientToRedisBO oauthClientToRedisBO = checkClientIdParam(clientId); 132 | 133 | if (clientSecretIsRequired && StringUtil.isBlank(clientSecret)) { 134 | throw new OauthApiException("client_secret 参数不能为空"); 135 | } 136 | 137 | if (clientSecretIsRequired) { 138 | if (StringUtil.notEqualsIgnoreCase(oauthClientToRedisBO.getClientSecret(), clientSecret)) { 139 | throw new OauthApiException("client_id 与 client_secret 不匹配"); 140 | } 141 | } 142 | } 143 | 144 | public OauthClientToRedisBO checkOauthAuthorizeParam(OauthAuthorizeParam oauthAuthorizeParam) { 145 | String responseType = oauthAuthorizeParam.getResponseType(); 146 | String redirectUri = oauthAuthorizeParam.getRedirectUri(); 147 | String clientId = oauthAuthorizeParam.getClientId(); 148 | 149 | OauthClientToRedisBO clientIdAndRedirectUriObject; 150 | try { 151 | clientIdAndRedirectUriObject = checkClientIdAndRedirectUriParam(clientId, redirectUri); 152 | } catch (Exception e) { 153 | throw new OauthApiException(e.getMessage(), ResponseProduceTypeEnum.HTML); 154 | } 155 | 156 | if (StringUtil.isBlank(responseType)) { 157 | throw new OauthApiException("response_type 参数不能为空", ResponseProduceTypeEnum.HTML); 158 | } 159 | 160 | if (!(StringUtil.equalsIgnoreCase(responseType, GlobalVariable.OAUTH_CODE_RESPONSE_TYPE) || StringUtil.equalsIgnoreCase(responseType, GlobalVariable.OAUTH_TOKEN_RESPONSE_TYPE))) { 161 | throw new OauthApiException("response_type 参数值有误", ResponseProduceTypeEnum.HTML); 162 | } 163 | 164 | return clientIdAndRedirectUriObject; 165 | } 166 | 167 | public void checkUserAgentAndRequestIpParam(String userAgent, String requestIp) { 168 | if (StringUtil.isBlank(userAgent)) { 169 | throw new OauthApiException("userAgent 参数不能为空"); 170 | } 171 | 172 | if (StringUtil.isBlank(requestIp)) { 173 | throw new OauthApiException("requestIp 参数不能为空"); 174 | } 175 | } 176 | 177 | public void checkUsernamePasswordParam(String username, String password) { 178 | 179 | if (StringUtil.isBlank(username)) { 180 | throw new OauthApiException("用户名不能为空"); 181 | } 182 | 183 | if (StringUtil.isBlank(password)) { 184 | throw new OauthApiException("密码不能为空"); 185 | } 186 | } 187 | 188 | public void checkOauthFormLoginParam(OauthFormLoginParam oauthFormLoginParam) { 189 | String username = oauthFormLoginParam.getUsername(); 190 | String password = oauthFormLoginParam.getPassword(); 191 | String redirectUri = oauthFormLoginParam.getRedirectUri(); 192 | String clientId = oauthFormLoginParam.getClientId(); 193 | 194 | checkUsernamePasswordParam(username, password); 195 | 196 | checkClientIdAndRedirectUriParam(clientId, redirectUri); 197 | } 198 | 199 | public void checkOauthTokenParam(OauthTokenParam oauthTokenParam) { 200 | String code = oauthTokenParam.getCode(); 201 | String grantType = oauthTokenParam.getGrantType(); 202 | String redirectUri = oauthTokenParam.getRedirectUri(); 203 | String clientId = oauthTokenParam.getClientId(); 204 | String clientSecret = oauthTokenParam.getClientSecret(); 205 | 206 | if (StringUtil.isBlank(code)) { 207 | throw new OauthApiException("code 参数不能为空"); 208 | } 209 | 210 | if (StringUtil.isBlank(grantType)) { 211 | throw new OauthApiException("grant_type 参数不能为空"); 212 | } 213 | 214 | if (StringUtil.isBlank(clientSecret)) { 215 | throw new OauthApiException("client_secret 参数不能为空"); 216 | } 217 | 218 | if (!StringUtil.containsIgnoreCase(code, GlobalVariable.OAUTH_CODE_PREFIX)) { 219 | throw new OauthApiException("code 参数格式不对,必须包含:" + GlobalVariable.OAUTH_CODE_PREFIX); 220 | } 221 | 222 | if (StringUtil.notEqualsIgnoreCase(grantType, GlobalVariable.OAUTH_CODE_GRANT_TYPE)) { 223 | throw new OauthApiException("grant_type 参数值必须为:" + GlobalVariable.OAUTH_CODE_GRANT_TYPE); 224 | } 225 | 226 | OauthClientToRedisBO oauthClientToRedisBO = checkClientIdAndRedirectUriParam(clientId, redirectUri); 227 | if (StringUtil.notEqualsIgnoreCase(oauthClientToRedisBO.getClientSecret(), clientSecret)) { 228 | throw new OauthApiException("client_id 与 client_secret 不匹配"); 229 | } 230 | } 231 | 232 | public void checkOauthRefreshTokenParam(OauthTokenParam oauthTokenParam) { 233 | String grantType = oauthTokenParam.getGrantType(); 234 | String refreshToken = oauthTokenParam.getRefreshToken(); 235 | String clientId = oauthTokenParam.getClientId(); 236 | String clientSecret = oauthTokenParam.getClientSecret(); 237 | 238 | if (StringUtil.isBlank(refreshToken)) { 239 | throw new OauthApiException("refresh_token 参数不能为空"); 240 | } 241 | 242 | if (StringUtil.isBlank(grantType)) { 243 | throw new OauthApiException("grant_type 参数不能为空"); 244 | } 245 | 246 | if (StringUtil.isBlank(clientSecret)) { 247 | throw new OauthApiException("client_secret 参数不能为空"); 248 | } 249 | 250 | if (!StringUtil.containsIgnoreCase(refreshToken, GlobalVariable.OAUTH_REFRESH_TOKEN_PREFIX)) { 251 | throw new OauthApiException("refresh_token 参数格式不对,必须包含:" + GlobalVariable.OAUTH_REFRESH_TOKEN_PREFIX); 252 | } 253 | 254 | if (StringUtil.notEqualsIgnoreCase(grantType, GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE)) { 255 | throw new OauthApiException("grant_type 参数值必须为:" + GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE); 256 | } 257 | 258 | OauthClientToRedisBO oauthClientToRedisBO = checkClientIdParam(clientId); 259 | 260 | if (StringUtil.notEqualsIgnoreCase(oauthClientToRedisBO.getClientSecret(), clientSecret)) { 261 | throw new OauthApiException("client_id 与 client_secret 不匹配"); 262 | } 263 | } 264 | 265 | public OauthIntrospect checkOauthIntrospectTokenParam(OauthIntrospectTokenParam oauthIntrospectTokenParam) { 266 | String token = oauthIntrospectTokenParam.getToken(); 267 | String tokenTypeHint = oauthIntrospectTokenParam.getTokenTypeHint(); 268 | String clientSecret = oauthIntrospectTokenParam.getClientSecret(); 269 | 270 | if (StringUtil.isBlank(token)) { 271 | throw new OauthApiException("token 参数不能为空"); 272 | } 273 | 274 | if (StringUtil.isBlank(tokenTypeHint)) { 275 | throw new OauthApiException("token_type_hint 参数不能为空"); 276 | } 277 | 278 | if (StringUtil.isBlank(clientSecret)) { 279 | throw new OauthApiException("client_secret 参数不能为空"); 280 | } 281 | 282 | if (StringUtil.equalsIgnoreCase(tokenTypeHint, GlobalVariable.OAUTH_ACCESS_TOKEN_TYPE_HINT)) { 283 | if (!StringUtil.containsIgnoreCase(token, GlobalVariable.OAUTH_ACCESS_TOKEN_PREFIX)) { 284 | throw new OauthApiException("access_token 参数格式不对,必须包含:" + GlobalVariable.OAUTH_ACCESS_TOKEN_PREFIX); 285 | } 286 | } else if (StringUtil.equalsIgnoreCase(tokenTypeHint, GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE)) { 287 | if (!StringUtil.containsIgnoreCase(token, GlobalVariable.OAUTH_REFRESH_TOKEN_PREFIX)) { 288 | throw new OauthApiException("refresh_token 参数格式不对,必须包含:" + GlobalVariable.OAUTH_REFRESH_TOKEN_PREFIX); 289 | } 290 | } else { 291 | throw new OauthApiException("token_type_hint 参数值不合法"); 292 | } 293 | 294 | String clientId = oauthIntrospectTokenParam.getClientId(); 295 | OauthClientToRedisBO oauthClientToRedisBO = checkClientIdParam(clientId); 296 | 297 | if (StringUtil.notEqualsIgnoreCase(oauthClientToRedisBO.getClientSecret(), clientSecret)) { 298 | throw new OauthApiException("client_id 与 client_secret 不匹配"); 299 | } 300 | 301 | OauthIntrospect oauthIntrospect = new OauthIntrospect(); 302 | oauthIntrospect.setTokenType(GlobalVariable.OAUTH_TOKEN_TYPE); 303 | oauthIntrospect.setClientId(clientId); 304 | return oauthIntrospect; 305 | } 306 | 307 | //=====================================业务处理 end===================================== 308 | 309 | //=====================================私有方法 start===================================== 310 | 311 | private OauthClientToRedisBO checkClientIdAndRedirectUriParam(String clientId, String redirectUri) { 312 | 313 | OauthClientToRedisBO checkClientIdObject = checkClientIdParam(clientId); 314 | 315 | if (StringUtil.isBlank(redirectUri)) { 316 | throw new OauthApiException("redirect_uri 参数不能为空"); 317 | } 318 | 319 | String regularToRedirectUri = checkClientIdObject.getClientUrl(); 320 | if (!redirectUri.matches(regularToRedirectUri)) { 321 | throw new OauthApiException("redirect_uri 不匹配该 client_id"); 322 | } 323 | 324 | return checkClientIdObject; 325 | } 326 | 327 | //=====================================私有方法 end===================================== 328 | 329 | } 330 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/service/OauthClientService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.service; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariable; 4 | import com.cdk8s.tkey.server.exception.OauthApiException; 5 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthClientToRedisBO; 6 | import com.cdk8s.tkey.server.util.JsonUtil; 7 | import com.cdk8s.tkey.server.util.StringUtil; 8 | import com.cdk8s.tkey.server.util.redis.StringRedisService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | 14 | @Service 15 | @Slf4j 16 | public class OauthClientService { 17 | 18 | /** 19 | * 这里必须采用 value 为 String 类型,不然 客户端和服务端都要读取该信息,但是又没有共同的类进行序列化,所以必须转换成 JSON 字符串 20 | * 如果后面出现的公共类越来越多我再考虑独立出一个 jar 包出来维护 21 | */ 22 | @Autowired 23 | private StringRedisService clientRedisService; 24 | 25 | //=====================================业务处理 start===================================== 26 | 27 | public OauthClientToRedisBO findByClientId(String clientId) { 28 | String clientIdRedisKey = GlobalVariable.REDIS_CLIENT_ID_KEY_PREFIX + clientId; 29 | String result = clientRedisService.get(clientIdRedisKey); 30 | if (StringUtil.isBlank(result)) { 31 | throw new OauthApiException("client_id 不存在"); 32 | } 33 | return JsonUtil.toObject(result, OauthClientToRedisBO.class); 34 | } 35 | 36 | //=====================================业务处理 end===================================== 37 | 38 | //=====================================私有方法 start===================================== 39 | 40 | //=====================================私有方法 end===================================== 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/service/OauthGenerateService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.service; 2 | 3 | 4 | import com.cdk8s.tkey.server.constant.GlobalVariable; 5 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 6 | import com.cdk8s.tkey.server.properties.OauthProperties; 7 | import com.cdk8s.tkey.server.util.NumericGeneratorUtil; 8 | import com.cdk8s.tkey.server.util.RandomUtil; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | 14 | @Service 15 | @Slf4j 16 | public class OauthGenerateService { 17 | 18 | @Autowired 19 | private OauthProperties oauthProperties; 20 | 21 | //=====================================业务处理 start===================================== 22 | 23 | public OauthToken generateOauthTokenInfoBO(boolean needIncludeRefreshToken) { 24 | OauthToken oauthToken = new OauthToken(); 25 | oauthToken.setAccessToken(generateAccessToken()); 26 | if (needIncludeRefreshToken) { 27 | oauthToken.setRefreshToken(generateRefreshToken()); 28 | } 29 | oauthToken.setTokenType(GlobalVariable.OAUTH_TOKEN_TYPE); 30 | oauthToken.setExpiresIn(oauthProperties.getAccessTokenMaxTimeToLiveInSeconds()); 31 | 32 | return oauthToken; 33 | } 34 | 35 | public String generateUserInfoRedisKey(String userId) { 36 | return GlobalVariable.REDIS_OAUTH_USER_INFO_KEY_PREFIX + userId; 37 | } 38 | 39 | public String generateTgc() { 40 | return getUniqueTicket(GlobalVariable.OAUTH_TGC_PREFIX); 41 | } 42 | 43 | public String generateCode() { 44 | return getUniqueTicket(GlobalVariable.OAUTH_CODE_PREFIX); 45 | } 46 | 47 | public String generateAccessToken() { 48 | return getUniqueTicket(GlobalVariable.OAUTH_ACCESS_TOKEN_PREFIX); 49 | } 50 | 51 | public String generateRefreshToken() { 52 | return getUniqueTicket(GlobalVariable.OAUTH_REFRESH_TOKEN_PREFIX); 53 | } 54 | 55 | //=====================================业务处理 end===================================== 56 | 57 | //=====================================私有方法 start===================================== 58 | 59 | private String getUniqueTicket(String prefix) { 60 | // 组成结构:前缀-节点编号+计算器数-随机数 61 | return prefix + oauthProperties.getNodeNumber() + NumericGeneratorUtil.getNumber() + "-" + RandomUtil.randomAlphanumeric(32); 62 | } 63 | 64 | //=====================================私有方法 end===================================== 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/service/OauthSaveService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.service; 2 | 3 | 4 | import com.cdk8s.tkey.server.constant.GlobalVariable; 5 | import com.cdk8s.tkey.server.pojo.bo.cache.*; 6 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 7 | import com.cdk8s.tkey.server.properties.OauthProperties; 8 | import com.cdk8s.tkey.server.util.DatetimeUtil; 9 | import com.cdk8s.tkey.server.util.UserAgentUtil; 10 | import com.cdk8s.tkey.server.util.redis.StringRedisService; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.scheduling.annotation.Async; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.concurrent.TimeUnit; 17 | 18 | 19 | @Service 20 | @Slf4j 21 | public class OauthSaveService { 22 | 23 | @Autowired 24 | private StringRedisService codeRedisService; 25 | 26 | @Autowired 27 | private StringRedisService tgcRedisService; 28 | 29 | @Autowired 30 | private StringRedisService userInfoRedisService; 31 | 32 | @Autowired 33 | private StringRedisService accessTokenRedisService; 34 | 35 | @Autowired 36 | private StringRedisService refreshTokenRedisService; 37 | 38 | @Autowired 39 | private OauthGenerateService oauthGenerateService; 40 | 41 | @Autowired 42 | private OauthProperties oauthProperties; 43 | 44 | //=====================================业务处理 start===================================== 45 | 46 | @Async 47 | public void saveUserInfoKeyToRedis(String userInfoRedisKey, OauthUserAttribute oauthUserAttribute) { 48 | OauthUserInfoToRedisBO oauthUserInfoToRedisBO = new OauthUserInfoToRedisBO(); 49 | oauthUserInfoToRedisBO.setUserAttribute(oauthUserAttribute); 50 | oauthUserInfoToRedisBO.setIat(DatetimeUtil.currentEpochSecond()); 51 | 52 | userInfoRedisService.set(userInfoRedisKey, oauthUserInfoToRedisBO, oauthProperties.getRefreshTokenMaxTimeToLiveInSeconds(), TimeUnit.SECONDS); 53 | } 54 | 55 | @Async 56 | public void saveCodeToRedis(String code, String tgc, String userInfoRedisKey, String clientId) { 57 | OauthCodeToRedisBO oauthCodeToRedisBO = new OauthCodeToRedisBO(); 58 | oauthCodeToRedisBO.setTgc(tgc); 59 | oauthCodeToRedisBO.setUserInfoRedisKey(userInfoRedisKey); 60 | oauthCodeToRedisBO.setClientId(clientId); 61 | oauthCodeToRedisBO.setIat(DatetimeUtil.currentEpochSecond()); 62 | 63 | codeRedisService.set(GlobalVariable.REDIS_OAUTH_CODE_PREFIX_KEY_PREFIX + code, oauthCodeToRedisBO, oauthProperties.getCodeMaxTimeToLiveInSeconds(), TimeUnit.SECONDS); 64 | } 65 | 66 | @Async 67 | public void saveTgcToRedisAndCookie(String tgc, Integer maxTimeToLiveInSeconds, String userInfoRedisKey, String userAgent, String requestIp, boolean isRememberMe) { 68 | OauthTgcToRedisBO oauthTgcToRedisBO = new OauthTgcToRedisBO(); 69 | oauthTgcToRedisBO.setIat(DatetimeUtil.currentEpochSecond()); 70 | oauthTgcToRedisBO.setUserAgent(userAgent); 71 | oauthTgcToRedisBO.setRequestIp(requestIp); 72 | oauthTgcToRedisBO.setBoolIsRememberMe(isRememberMe); 73 | oauthTgcToRedisBO.setBoolIsMobile(UserAgentUtil.isMobile(userAgent)); 74 | oauthTgcToRedisBO.setUserInfoRedisKey(userInfoRedisKey); 75 | 76 | oauthTgcToRedisBO.setIat(DatetimeUtil.currentEpochSecond()); 77 | tgcRedisService.set(GlobalVariable.REDIS_TGC_KEY_PREFIX + tgc, oauthTgcToRedisBO, maxTimeToLiveInSeconds, TimeUnit.SECONDS); 78 | } 79 | 80 | @Async 81 | public void updateTgcAndUserInfoRedisKeyExpire(String tgc, String userInfoRedisKey) { 82 | tgcRedisService.expire(GlobalVariable.REDIS_TGC_KEY_PREFIX + tgc, oauthProperties.getTgcAndUserInfoMaxTimeToLiveInSeconds(), TimeUnit.SECONDS); 83 | userInfoRedisService.expire(userInfoRedisKey, oauthProperties.getTgcAndUserInfoMaxTimeToLiveInSeconds(), TimeUnit.SECONDS); 84 | } 85 | 86 | @Async 87 | public void saveAccessToken(String accessToken, OauthUserAttribute oauthUserAttribute, String clientId, String grantType) { 88 | long currentEpochSecond = DatetimeUtil.currentEpochSecond(); 89 | OauthAccessTokenToRedisBO oauthAccessTokenToRedisBO = new OauthAccessTokenToRedisBO(); 90 | if (null != oauthUserAttribute) { 91 | //客户端模式情况下是没有用户信息的 92 | oauthAccessTokenToRedisBO.setUserAttribute(oauthUserAttribute); 93 | } 94 | oauthAccessTokenToRedisBO.setGrantType(grantType); 95 | oauthAccessTokenToRedisBO.setClientId(clientId); 96 | oauthAccessTokenToRedisBO.setIat(currentEpochSecond); 97 | saveAccessTokenToRedis(accessToken, oauthAccessTokenToRedisBO); 98 | } 99 | 100 | @Async 101 | public void saveRefreshToken(String refreshToken, OauthUserAttribute oauthUserAttribute, String clientId, String grantType) { 102 | long currentEpochSecond = DatetimeUtil.currentEpochSecond(); 103 | OauthRefreshTokenToRedisBO oauthRefreshTokenToRedisBO = new OauthRefreshTokenToRedisBO(); 104 | if (null != oauthUserAttribute) { 105 | //客户端模式情况下是没有用户信息的 106 | String userId = oauthUserAttribute.getUserId(); 107 | String userInfoRedisKey = oauthGenerateService.generateUserInfoRedisKey(userId); 108 | oauthRefreshTokenToRedisBO.setUserInfoRedisKey(userInfoRedisKey); 109 | } 110 | oauthRefreshTokenToRedisBO.setGrantType(grantType); 111 | oauthRefreshTokenToRedisBO.setClientId(clientId); 112 | oauthRefreshTokenToRedisBO.setIat(currentEpochSecond); 113 | saveRefreshTokenToRedis(refreshToken, oauthRefreshTokenToRedisBO); 114 | } 115 | 116 | //=====================================业务处理 end===================================== 117 | 118 | //=====================================私有方法 start===================================== 119 | 120 | private void saveAccessTokenToRedis(String accessToken, OauthAccessTokenToRedisBO oauthAccessTokenToRedisBO) { 121 | accessTokenRedisService.set(GlobalVariable.REDIS_OAUTH_ACCESS_TOKEN_KEY_PREFIX + accessToken, oauthAccessTokenToRedisBO, oauthProperties.getAccessTokenMaxTimeToLiveInSeconds(), TimeUnit.SECONDS); 122 | } 123 | 124 | private void saveRefreshTokenToRedis(String refreshToken, OauthRefreshTokenToRedisBO oauthRefreshTokenToRedisBO) { 125 | refreshTokenRedisService.set(GlobalVariable.REDIS_OAUTH_REFRESH_TOKEN_KEY_PREFIX + refreshToken, oauthRefreshTokenToRedisBO, oauthProperties.getRefreshTokenMaxTimeToLiveInSeconds(), TimeUnit.SECONDS); 126 | } 127 | 128 | //=====================================私有方法 end===================================== 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/service/OauthThirdPartyApiService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.service; 2 | 3 | 4 | import com.cdk8s.tkey.server.exception.OauthApiException; 5 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 6 | import com.cdk8s.tkey.server.util.JsonUtil; 7 | import com.cdk8s.tkey.server.util.StringUtil; 8 | import com.cdk8s.tkey.server.util.okhttp.OkHttpService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | 14 | @Service 15 | @Slf4j 16 | public class OauthThirdPartyApiService { 17 | 18 | @Autowired 19 | private OkHttpService okHttpService; 20 | 21 | //=====================================业务处理 start===================================== 22 | 23 | 24 | public OauthUserAttribute getOauthUserAttributeDTO(String username, String password) { 25 | log.debug("正在请求 UPMS 接口"); 26 | log.debug("username=<{}>", username); 27 | log.debug("password=<{}>", password); 28 | 29 | 30 | if (StringUtil.notEqualsIgnoreCase(username, "admin")) { 31 | throw new OauthApiException("演示模式下,用户名是 admin"); 32 | } 33 | 34 | if (StringUtil.notEqualsIgnoreCase(password, "123456")) { 35 | throw new OauthApiException("演示模式下,密码是 123456"); 36 | } 37 | 38 | // 下面是真实场景下的 REST 调用方式。如果可以直连数据库的话,这里可以改为 Mapper 查询 39 | // OkHttpResponse okHttpResponse = okHttpService.get("https://www.baidu.com/"); 40 | // log.debug("调用第三方接口返回=<{}>", okHttpResponse.toString()); 41 | // if (okHttpResponse.getStatus() != HttpStatus.OK.value()) { 42 | // throw new OauthApiException("调用 UPMS 接口获取用户信息失败"); 43 | // } 44 | 45 | return getUserInfoApi(username); 46 | } 47 | 48 | 49 | //=====================================业务处理 end===================================== 50 | 51 | //=====================================私有方法 start===================================== 52 | 53 | 54 | private OauthUserAttribute getUserInfoApi(String username) { 55 | String userInfoJson = "{\n" + 56 | " \"email\": \"" + username + "@cdk8s.com\",\n" + 57 | " \"userId\": \"111111111111111111\",\n" + 58 | " \"username\": \"" + username + "\"\n" + 59 | "}"; 60 | 61 | return JsonUtil.toObject(userInfoJson, OauthUserAttribute.class); 62 | } 63 | 64 | //=====================================私有方法 end===================================== 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/strategy/OauthClientToTokenStrategy.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.strategy; 2 | 3 | 4 | import com.cdk8s.tkey.server.constant.GlobalVariable; 5 | import com.cdk8s.tkey.server.pojo.bo.handle.OauthTokenStrategyHandleBO; 6 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 7 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 8 | import com.cdk8s.tkey.server.service.OauthCheckParamService; 9 | import com.cdk8s.tkey.server.service.OauthGenerateService; 10 | import com.cdk8s.tkey.server.service.OauthSaveService; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | @Slf4j 16 | @Service(GlobalVariable.OAUTH_CLIENT_GRANT_TYPE) 17 | public class OauthClientToTokenStrategy implements OauthTokenStrategyInterface { 18 | 19 | @Autowired 20 | private OauthCheckParamService oauthCheckParamService; 21 | 22 | @Autowired 23 | private OauthGenerateService oauthGenerateService; 24 | 25 | @Autowired 26 | private OauthSaveService oauthSaveService; 27 | 28 | //=====================================业务处理 start===================================== 29 | 30 | @Override 31 | public void checkParam(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 32 | oauthCheckParamService.checkClientIdAndClientSecretParam(oauthTokenParam.getClientId(), oauthTokenParam.getClientSecret(), true); 33 | } 34 | 35 | @Override 36 | public OauthToken handle(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 37 | OauthToken oauthTokenInfoByClientBO = oauthGenerateService.generateOauthTokenInfoBO(true); 38 | 39 | oauthSaveService.saveAccessToken(oauthTokenInfoByClientBO.getAccessToken(), null, oauthTokenParam.getClientId(), GlobalVariable.OAUTH_CLIENT_GRANT_TYPE); 40 | oauthSaveService.saveRefreshToken(oauthTokenInfoByClientBO.getRefreshToken(), null, oauthTokenParam.getClientId(), GlobalVariable.OAUTH_CLIENT_GRANT_TYPE); 41 | 42 | return oauthTokenInfoByClientBO; 43 | } 44 | 45 | //=====================================业务处理 end===================================== 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/strategy/OauthCodeToTokenStrategy.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.strategy; 2 | 3 | 4 | import com.cdk8s.tkey.server.constant.GlobalVariable; 5 | import com.cdk8s.tkey.server.exception.OauthApiException; 6 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthCodeToRedisBO; 7 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthUserInfoToRedisBO; 8 | import com.cdk8s.tkey.server.pojo.bo.handle.OauthTokenStrategyHandleBO; 9 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 10 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 11 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 12 | import com.cdk8s.tkey.server.service.OauthCheckParamService; 13 | import com.cdk8s.tkey.server.service.OauthGenerateService; 14 | import com.cdk8s.tkey.server.service.OauthSaveService; 15 | import com.cdk8s.tkey.server.util.StringUtil; 16 | import com.cdk8s.tkey.server.util.redis.StringRedisService; 17 | import lombok.extern.slf4j.Slf4j; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.stereotype.Service; 20 | 21 | @Slf4j 22 | @Service(GlobalVariable.OAUTH_CODE_GRANT_TYPE) 23 | public class OauthCodeToTokenStrategy implements OauthTokenStrategyInterface { 24 | 25 | @Autowired 26 | private StringRedisService codeRedisService; 27 | 28 | @Autowired 29 | private StringRedisService userInfoRedisService; 30 | 31 | @Autowired 32 | private OauthCheckParamService oauthCheckParamService; 33 | 34 | @Autowired 35 | private OauthGenerateService oauthGenerateService; 36 | 37 | @Autowired 38 | private OauthSaveService oauthSaveService; 39 | 40 | //=====================================业务处理 start===================================== 41 | 42 | @Override 43 | public void checkParam(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 44 | oauthCheckParamService.checkOauthTokenParam(oauthTokenParam); 45 | 46 | OauthCodeToRedisBO oauthCodeToRedisBO = codeRedisService.get(GlobalVariable.REDIS_OAUTH_CODE_PREFIX_KEY_PREFIX + oauthTokenParam.getCode()); 47 | if (null == oauthCodeToRedisBO) { 48 | throw new OauthApiException("code 无效"); 49 | } 50 | 51 | if (StringUtil.notEqualsIgnoreCase(oauthCodeToRedisBO.getClientId(), oauthTokenParam.getClientId())) { 52 | throw new OauthApiException("该 code 与当前请求的 client_id 参数不匹配"); 53 | } 54 | 55 | oauthTokenStrategyHandleBO.setUserInfoRedisKey(oauthCodeToRedisBO.getUserInfoRedisKey()); 56 | } 57 | 58 | @Override 59 | public OauthToken handle(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 60 | String userInfoRedisKey = oauthTokenStrategyHandleBO.getUserInfoRedisKey(); 61 | OauthUserInfoToRedisBO oauthUserInfoToRedisBO = userInfoRedisService.get(userInfoRedisKey); 62 | 63 | OauthToken oauthToken = oauthGenerateService.generateOauthTokenInfoBO(true); 64 | 65 | OauthUserAttribute userAttribute = oauthUserInfoToRedisBO.getUserAttribute(); 66 | oauthSaveService.saveAccessToken(oauthToken.getAccessToken(), userAttribute, oauthTokenParam.getClientId(), GlobalVariable.OAUTH_CODE_GRANT_TYPE); 67 | oauthSaveService.saveRefreshToken(oauthToken.getRefreshToken(), userAttribute, oauthTokenParam.getClientId(), GlobalVariable.OAUTH_CODE_GRANT_TYPE); 68 | 69 | // code 只能被用一次,这里用完会立马被删除 70 | codeRedisService.delete(GlobalVariable.REDIS_OAUTH_CODE_PREFIX_KEY_PREFIX + oauthTokenParam.getCode()); 71 | return oauthToken; 72 | } 73 | 74 | //=====================================业务处理 end===================================== 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/strategy/OauthPasswordToTokenStrategy.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.strategy; 2 | 3 | 4 | import com.cdk8s.tkey.server.constant.GlobalVariable; 5 | import com.cdk8s.tkey.server.exception.OauthApiException; 6 | import com.cdk8s.tkey.server.pojo.bo.handle.OauthTokenStrategyHandleBO; 7 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 8 | import com.cdk8s.tkey.server.pojo.dto.OauthUserAttribute; 9 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 10 | import com.cdk8s.tkey.server.retry.RetryService; 11 | import com.cdk8s.tkey.server.service.OauthCheckParamService; 12 | import com.cdk8s.tkey.server.service.OauthGenerateService; 13 | import com.cdk8s.tkey.server.service.OauthSaveService; 14 | import com.cdk8s.tkey.server.util.StringUtil; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Service; 18 | 19 | @Slf4j 20 | @Service(GlobalVariable.OAUTH_PASSWORD_GRANT_TYPE) 21 | public class OauthPasswordToTokenStrategy implements OauthTokenStrategyInterface { 22 | 23 | @Autowired 24 | private OauthCheckParamService oauthCheckParamService; 25 | 26 | @Autowired 27 | private OauthGenerateService oauthGenerateService; 28 | 29 | @Autowired 30 | private OauthSaveService oauthSaveService; 31 | 32 | @Autowired 33 | private RetryService retryService; 34 | 35 | //=====================================业务处理 start===================================== 36 | 37 | @Override 38 | public void checkParam(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 39 | oauthCheckParamService.checkClientIdParam(oauthTokenParam.getClientId()); 40 | } 41 | 42 | @Override 43 | public OauthToken handle(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 44 | String clientId = oauthTokenParam.getClientId(); 45 | String clientSecret = oauthTokenParam.getClientSecret(); 46 | oauthCheckParamService.checkClientIdAndClientSecretParam(clientId, clientSecret, false); 47 | String username = oauthTokenParam.getUsername(); 48 | String password = oauthTokenParam.getPassword(); 49 | oauthCheckParamService.checkUsernamePasswordParam(username, password); 50 | 51 | // 校验用户名密码 52 | OauthUserAttribute oauthUserAttribute = retryService.getOauthUserAttributeBO(username, password); 53 | if (null == oauthUserAttribute || StringUtil.isBlank(oauthUserAttribute.getUserId())) { 54 | throw new OauthApiException("用户名或密码错误"); 55 | } 56 | 57 | String userInfoRedisKey = oauthGenerateService.generateUserInfoRedisKey(oauthUserAttribute.getUserId()); 58 | oauthSaveService.saveUserInfoKeyToRedis(userInfoRedisKey, oauthUserAttribute); 59 | 60 | oauthTokenStrategyHandleBO.setUserAttribute(oauthUserAttribute); 61 | 62 | OauthUserAttribute userAttribute = oauthTokenStrategyHandleBO.getUserAttribute(); 63 | 64 | OauthToken oauthTokenInfoByClientBO = oauthGenerateService.generateOauthTokenInfoBO(true); 65 | 66 | oauthSaveService.saveAccessToken(oauthTokenInfoByClientBO.getAccessToken(), userAttribute, clientId, GlobalVariable.OAUTH_PASSWORD_GRANT_TYPE); 67 | oauthSaveService.saveRefreshToken(oauthTokenInfoByClientBO.getRefreshToken(), userAttribute, clientId, GlobalVariable.OAUTH_PASSWORD_GRANT_TYPE); 68 | 69 | return oauthTokenInfoByClientBO; 70 | } 71 | 72 | //=====================================业务处理 end===================================== 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/strategy/OauthRefreshTokenToTokenStrategy.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.strategy; 2 | 3 | 4 | import com.cdk8s.tkey.server.constant.GlobalVariable; 5 | import com.cdk8s.tkey.server.exception.OauthApiException; 6 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthRefreshTokenToRedisBO; 7 | import com.cdk8s.tkey.server.pojo.bo.cache.OauthUserInfoToRedisBO; 8 | import com.cdk8s.tkey.server.pojo.bo.handle.OauthTokenStrategyHandleBO; 9 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 10 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 11 | import com.cdk8s.tkey.server.service.OauthCheckParamService; 12 | import com.cdk8s.tkey.server.service.OauthGenerateService; 13 | import com.cdk8s.tkey.server.service.OauthSaveService; 14 | import com.cdk8s.tkey.server.util.StringUtil; 15 | import com.cdk8s.tkey.server.util.redis.StringRedisService; 16 | import lombok.extern.slf4j.Slf4j; 17 | import org.springframework.beans.factory.annotation.Autowired; 18 | import org.springframework.stereotype.Service; 19 | 20 | @Slf4j 21 | @Service(GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE) 22 | public class OauthRefreshTokenToTokenStrategy implements OauthTokenStrategyInterface { 23 | 24 | @Autowired 25 | private StringRedisService userInfoRedisService; 26 | 27 | @Autowired 28 | private StringRedisService refreshTokenRedisService; 29 | 30 | @Autowired 31 | private OauthCheckParamService oauthCheckParamService; 32 | 33 | @Autowired 34 | private OauthSaveService oauthSaveService; 35 | 36 | @Autowired 37 | private OauthGenerateService oauthGenerateService; 38 | 39 | //=====================================业务处理 start===================================== 40 | 41 | @Override 42 | public void checkParam(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 43 | oauthCheckParamService.checkOauthRefreshTokenParam(oauthTokenParam); 44 | 45 | OauthRefreshTokenToRedisBO oauthRefreshTokenToRedisBO = refreshTokenRedisService.get(GlobalVariable.REDIS_OAUTH_REFRESH_TOKEN_KEY_PREFIX + oauthTokenParam.getRefreshToken()); 46 | if (null == oauthRefreshTokenToRedisBO) { 47 | throw new OauthApiException("refresh_token 已失效"); 48 | } 49 | 50 | if (StringUtil.notEqualsIgnoreCase(oauthRefreshTokenToRedisBO.getClientId(), oauthTokenParam.getClientId())) { 51 | throw new OauthApiException("该 refresh_token 与当前请求的 client_id 参数不匹配"); 52 | } 53 | 54 | String userInfoRedisKey = oauthRefreshTokenToRedisBO.getUserInfoRedisKey(); 55 | if (null != userInfoRedisKey) { 56 | OauthUserInfoToRedisBO oauthUserInfoToRedisBO = userInfoRedisService.get(userInfoRedisKey); 57 | if (null == oauthUserInfoToRedisBO) { 58 | throw new OauthApiException("未找到该用户信息"); 59 | } 60 | 61 | oauthTokenStrategyHandleBO.setUserAttribute(oauthUserInfoToRedisBO.getUserAttribute()); 62 | } 63 | } 64 | 65 | @Override 66 | public OauthToken handle(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 67 | 68 | OauthToken oauthTokenInfoByRefreshTokenBO = oauthGenerateService.generateOauthTokenInfoBO(false); 69 | 70 | if (null != oauthTokenStrategyHandleBO.getUserAttribute()) { 71 | oauthSaveService.saveAccessToken(oauthTokenInfoByRefreshTokenBO.getAccessToken(), oauthTokenStrategyHandleBO.getUserAttribute(), oauthTokenParam.getClientId(), GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE); 72 | } else { 73 | oauthSaveService.saveAccessToken(oauthTokenInfoByRefreshTokenBO.getAccessToken(), null, oauthTokenParam.getClientId(), GlobalVariable.OAUTH_REFRESH_TOKEN_GRANT_TYPE); 74 | } 75 | 76 | return oauthTokenInfoByRefreshTokenBO; 77 | } 78 | 79 | //=====================================业务处理 end===================================== 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/strategy/OauthTokenStrategyContext.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.strategy; 2 | 3 | import com.cdk8s.tkey.server.pojo.bo.handle.OauthTokenStrategyHandleBO; 4 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 5 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 6 | import com.cdk8s.tkey.server.util.StringUtil; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | 14 | @Component 15 | public class OauthTokenStrategyContext { 16 | 17 | 18 | private final Map strategyMap = new ConcurrentHashMap<>(); 19 | 20 | @Autowired 21 | public OauthTokenStrategyContext(Map strategyMap) { 22 | this.strategyMap.clear(); 23 | strategyMap.forEach(this.strategyMap::put); 24 | } 25 | 26 | public void checkParam(String beanName, OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 27 | if (StringUtil.isNotBlank(beanName)) { 28 | strategyMap.get(beanName).checkParam(oauthTokenParam, oauthTokenStrategyHandleBO); 29 | } 30 | } 31 | 32 | public OauthToken generateOauthTokenInfo(String beanName, OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO) { 33 | if (StringUtil.isNotBlank(beanName)) { 34 | return strategyMap.get(beanName).handle(oauthTokenParam, oauthTokenStrategyHandleBO); 35 | } 36 | return null; 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/strategy/OauthTokenStrategyInterface.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.strategy; 2 | 3 | 4 | import com.cdk8s.tkey.server.pojo.bo.handle.OauthTokenStrategyHandleBO; 5 | import com.cdk8s.tkey.server.pojo.dto.OauthToken; 6 | import com.cdk8s.tkey.server.pojo.dto.param.OauthTokenParam; 7 | 8 | public interface OauthTokenStrategyInterface { 9 | 10 | /** 11 | * 检查请求参数 12 | */ 13 | void checkParam(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO); 14 | 15 | /** 16 | * 生成 Token 17 | */ 18 | OauthToken handle(OauthTokenParam oauthTokenParam, OauthTokenStrategyHandleBO oauthTokenStrategyHandleBO); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/CodecUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.codec.binary.Base64; 5 | 6 | @Slf4j 7 | public final class CodecUtil { 8 | 9 | public static String base64DecodeBySafe(final String base64String) { 10 | return new String(Base64.decodeBase64(base64String)); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/CollectionUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import org.apache.commons.collections4.CollectionUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * 集合工具类 10 | */ 11 | public final class CollectionUtil { 12 | 13 | 14 | //=====================================Apache Common 包 start===================================== 15 | 16 | /** 17 | * 数组转换成 List 18 | */ 19 | public static List toList(T[] arrays) { 20 | List list = new ArrayList<>(); 21 | CollectionUtils.addAll(list, arrays); 22 | return list; 23 | } 24 | 25 | //=====================================Apache Common 包 end===================================== 26 | 27 | //=====================================Guava 包 end===================================== 28 | 29 | //=====================================Guava 包 end===================================== 30 | 31 | //=====================================其他包 start===================================== 32 | 33 | 34 | //=====================================其他包 end===================================== 35 | 36 | 37 | //=====================================私有方法 start===================================== 38 | 39 | 40 | //=====================================私有方法 end===================================== 41 | 42 | } 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/CookieUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | import javax.servlet.http.Cookie; 8 | import javax.servlet.http.HttpServletRequest; 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.net.URLDecoder; 11 | import java.net.URLEncoder; 12 | 13 | @Slf4j 14 | public final class CookieUtil { 15 | 16 | public static void setCookie(final HttpServletResponse response, String name, String value, int maxAge, boolean httpOnly, boolean secure) { 17 | setCookie(response, name, value, "/", maxAge, httpOnly, secure); 18 | } 19 | 20 | @SneakyThrows 21 | public static void setCookie(final HttpServletResponse response, String name, String value, String path, int maxAge, boolean httpOnly, boolean secure) { 22 | Cookie cookie = new Cookie(name, null); 23 | cookie.setPath(path); 24 | cookie.setMaxAge(maxAge); 25 | cookie.setVersion(1); 26 | cookie.setHttpOnly(httpOnly); 27 | cookie.setSecure(secure); 28 | cookie.setValue(URLEncoder.encode(value, "utf-8")); 29 | response.addCookie(cookie); 30 | } 31 | 32 | // ============================================================================================================ 33 | 34 | public static void deleteCookie(final HttpServletRequest request, HttpServletResponse response, String name) { 35 | deleteCookie(request, response, name, "/"); 36 | } 37 | 38 | public static void deleteCookie(final HttpServletRequest request, HttpServletResponse response, String name, String path) { 39 | Cookie[] cookies = request.getCookies(); 40 | if (cookies != null) { 41 | for (Cookie cookie : cookies) { 42 | if (cookie.getName().equals(name)) { 43 | cookie.setPath(path); 44 | cookie.setValue(""); 45 | cookie.setMaxAge(0); 46 | response.addCookie(cookie); 47 | } 48 | } 49 | } 50 | } 51 | 52 | // ============================================================================================================ 53 | 54 | public static String getCookie(final HttpServletRequest request, String name) { 55 | return getCookie(request, null, name, false); 56 | } 57 | 58 | @SneakyThrows 59 | public static String getCookie(final HttpServletRequest request, final HttpServletResponse response, String name, boolean isRemove) { 60 | String value = null; 61 | Cookie[] cookies = request.getCookies(); 62 | if (cookies != null) { 63 | for (Cookie cookie : cookies) { 64 | if (StringUtil.equalsIgnoreCase(cookie.getName(), name)) { 65 | value = URLDecoder.decode(cookie.getValue(), "utf-8"); 66 | if (isRemove) { 67 | cookie.setMaxAge(0); 68 | response.addCookie(cookie); 69 | } 70 | } 71 | } 72 | } 73 | return value; 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/DatetimeUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import java.time.Instant; 4 | 5 | public final class DatetimeUtil { 6 | 7 | 8 | //=====================================JDK8 包 start===================================== 9 | 10 | public static long currentEpochSecond() { 11 | return Instant.now().getEpochSecond(); 12 | } 13 | 14 | //=====================================JDK8 包 end===================================== 15 | 16 | //=====================================其他包 start===================================== 17 | 18 | //=====================================其他包 end===================================== 19 | 20 | 21 | //=====================================私有方法 start===================================== 22 | 23 | //=====================================私有方法 end===================================== 24 | 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/ExceptionUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | 4 | import org.apache.commons.lang3.exception.ExceptionUtils; 5 | 6 | public final class ExceptionUtil { 7 | 8 | public static String getStackTraceAsString(Throwable e) { 9 | if (e == null) { 10 | return ""; 11 | } 12 | return ExceptionUtils.getStackTrace(e); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/IPUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import cn.hutool.extra.servlet.ServletUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | 8 | @Slf4j 9 | public final class IPUtil { 10 | 11 | public static String getIp(HttpServletRequest request) { 12 | return ServletUtil.getClientIP(request); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | @Slf4j 7 | public final class JsonUtil { 8 | 9 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 10 | 11 | public static String toJson(T obj) { 12 | String json; 13 | try { 14 | json = OBJECT_MAPPER.writeValueAsString(obj); 15 | } catch (Exception e) { 16 | log.error("convert POJO to JSON failure", e); 17 | throw new RuntimeException(e); 18 | } 19 | return json; 20 | } 21 | 22 | public static T toObject(String json, Class type) { 23 | T pojo; 24 | try { 25 | pojo = OBJECT_MAPPER.readValue(json, type); 26 | } catch (Exception e) { 27 | log.error("convert JSON to POJO failure", e); 28 | throw new RuntimeException(e); 29 | } 30 | return pojo; 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/NumericGeneratorUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import java.util.concurrent.atomic.LongAdder; 4 | 5 | public class NumericGeneratorUtil { 6 | 7 | private static final LongAdder LONG_ADDER = new LongAdder(); 8 | 9 | public static long getNumber() { 10 | LONG_ADDER.increment(); 11 | return LONG_ADDER.longValue(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/RandomUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import org.apache.commons.lang3.RandomStringUtils; 4 | 5 | 6 | public final class RandomUtil { 7 | 8 | public static String randomAlphanumeric(final int count) { 9 | return RandomStringUtils.randomAlphanumeric(count); 10 | } 11 | 12 | } 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.util.List; 6 | 7 | 8 | public final class StringUtil { 9 | 10 | 11 | //=====================================Apache Common 包 start===================================== 12 | 13 | public static boolean isNotBlank(final String str) { 14 | return StringUtils.isNotBlank(str); 15 | } 16 | 17 | public static boolean isBlank(final String str) { 18 | return StringUtils.isBlank(str); 19 | } 20 | 21 | public static boolean containsIgnoreCase(final String str, final String search) { 22 | return StringUtils.containsIgnoreCase(str, search); 23 | } 24 | 25 | public static String substringAfter(final String str, final String search) { 26 | return StringUtils.substringAfter(str, search); 27 | } 28 | 29 | public static String replaceIgnoreCase(final String text, final String searchString, final String replacement) { 30 | return StringUtils.replaceIgnoreCase(text, searchString, replacement); 31 | } 32 | 33 | public static List split(String str, String separator) { 34 | return CollectionUtil.toList(StringUtils.split(str, separator)); 35 | } 36 | 37 | public static boolean equalsIgnoreCase(final String str1, final String str2) { 38 | return StringUtils.equalsIgnoreCase(str1, str2); 39 | } 40 | 41 | public static boolean notEqualsIgnoreCase(final String str1, final String str2) { 42 | return !equalsIgnoreCase(str1, str2); 43 | } 44 | 45 | public static boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) { 46 | return StringUtils.endsWithAny(sequence, searchStrings); 47 | } 48 | 49 | //=====================================Apache Common 包 end===================================== 50 | 51 | //=====================================其他包 start===================================== 52 | 53 | 54 | //=====================================其他包 end===================================== 55 | 56 | 57 | //=====================================私有方法 start===================================== 58 | 59 | //=====================================私有方法 end===================================== 60 | 61 | } 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/UserAgentUtil.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util; 2 | 3 | import cn.hutool.http.useragent.UserAgentParser; 4 | 5 | 6 | public class UserAgentUtil { 7 | 8 | public static boolean isMobile(String userAgentString) { 9 | return UserAgentParser.parse(userAgentString).isMobile(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/okhttp/OkHttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util.okhttp; 2 | 3 | 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | @Setter 9 | @Getter 10 | @ToString 11 | public class OkHttpResponse { 12 | 13 | private int status; 14 | private String response; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/okhttp/OkHttpService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util.okhttp; 2 | 3 | import com.cdk8s.tkey.server.util.ExceptionUtil; 4 | import com.cdk8s.tkey.server.util.StringUtil; 5 | import lombok.extern.slf4j.Slf4j; 6 | import okhttp3.*; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.Map; 11 | 12 | 13 | @Service 14 | @Slf4j 15 | public class OkHttpService { 16 | 17 | @Autowired 18 | private OkHttpClient okHttpClient; 19 | 20 | //=====================================业务处理 start===================================== 21 | 22 | public OkHttpResponse get(String url) { 23 | Request request = new Request.Builder().url(url).build(); 24 | return getResponse(request); 25 | } 26 | 27 | public OkHttpResponse get(String url, Map queries, Map headers) { 28 | StringBuilder fullUrl = new StringBuilder(url); 29 | if (queries != null && queries.keySet().size() > 0) { 30 | fullUrl.append("?"); 31 | 32 | for (Map.Entry entry : queries.entrySet()) { 33 | if (StringUtil.isNotBlank(entry.getValue()) && !StringUtil.equalsIgnoreCase(entry.getValue(), "null")) { 34 | fullUrl.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); 35 | } 36 | } 37 | 38 | fullUrl.deleteCharAt(fullUrl.length() - 1); 39 | } 40 | 41 | Request.Builder builderRequest = new Request.Builder(); 42 | 43 | if (headers != null && headers.keySet().size() > 0) { 44 | headers.forEach(builderRequest::addHeader); 45 | } 46 | 47 | Request request = builderRequest.url(fullUrl.toString()).build(); 48 | return getResponse(request); 49 | } 50 | 51 | public OkHttpResponse post(String url, Map params, Map headers) { 52 | FormBody.Builder builder = new FormBody.Builder(); 53 | if (params != null && params.keySet().size() > 0) { 54 | params.forEach(builder::add); 55 | } 56 | 57 | Request.Builder builderRequest = new Request.Builder(); 58 | if (headers != null && headers.keySet().size() > 0) { 59 | headers.forEach(builderRequest::addHeader); 60 | } 61 | 62 | Request request = builderRequest.url(url).post(builder.build()).build(); 63 | return getResponse(request); 64 | } 65 | 66 | //=====================================业务处理 end===================================== 67 | 68 | //=====================================私有方法 start===================================== 69 | 70 | private Request buildPostRequestBody(String url, RequestBody requestBody, Map headers) { 71 | Request.Builder builderRequest = new Request.Builder(); 72 | if (headers != null && headers.keySet().size() > 0) { 73 | headers.forEach(builderRequest::addHeader); 74 | } 75 | return builderRequest.url(url).post(requestBody).build(); 76 | } 77 | 78 | private OkHttpResponse getResponse(Request request) { 79 | Response response = null; 80 | try { 81 | response = okHttpClient.newCall(request).execute(); 82 | OkHttpResponse okHttpResponse = new OkHttpResponse(); 83 | okHttpResponse.setStatus(response.code()); 84 | okHttpResponse.setResponse(response.body().string()); 85 | return okHttpResponse; 86 | } catch (Exception e) { 87 | log.error("okhttp error = {}", ExceptionUtil.getStackTraceAsString(e)); 88 | } finally { 89 | if (response != null) { 90 | response.close(); 91 | } 92 | } 93 | return null; 94 | } 95 | 96 | 97 | //=====================================私有方法 end===================================== 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/redis/StringRedisService.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util.redis; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.data.redis.core.ValueOperations; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 限制 key 只能为 String 12 | * 13 | * @param 14 | * @param 15 | */ 16 | @Service 17 | public class StringRedisService { 18 | 19 | @Autowired 20 | private RedisTemplate redisTemplate; 21 | 22 | //=====================================common start===================================== 23 | 24 | 25 | public Boolean expire(String key, long timeout, TimeUnit timeUnit) { 26 | //设置过期时间 27 | return redisTemplate.expire(key, timeout, timeUnit); 28 | } 29 | 30 | 31 | public Boolean delete(String key) { 32 | return redisTemplate.delete(key); 33 | } 34 | 35 | //=====================================common end===================================== 36 | 37 | //=====================================key value start===================================== 38 | 39 | public void set(String key, V value) { 40 | redisTemplate.opsForValue().set(key, value); 41 | } 42 | 43 | 44 | public void set(String key, V value, long timeout) { 45 | redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); 46 | } 47 | 48 | 49 | public void set(String key, V value, long timeout, TimeUnit timeUnit) { 50 | redisTemplate.opsForValue().set(key, value, timeout, timeUnit); 51 | } 52 | 53 | 54 | public V get(String key) { 55 | ValueOperations valueOperations = redisTemplate.opsForValue(); 56 | return valueOperations.get(key); 57 | } 58 | 59 | 60 | //=====================================key value end===================================== 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/response/R.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util.response; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariable; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | 7 | public class R { 8 | 9 | 10 | //=====================================正常返回 start===================================== 11 | 12 | 13 | //=====================================正常返回 end===================================== 14 | 15 | //=====================================异常返回 start===================================== 16 | 17 | public static ResponseEntity failure() { 18 | return failure(HttpStatus.INTERNAL_SERVER_ERROR); 19 | } 20 | 21 | public static ResponseEntity failure(HttpStatus httpStatus) { 22 | ResponseErrorObject responseErrorObject = new ResponseErrorObject(); 23 | responseErrorObject.setError(GlobalVariable.OAUTH_ERROR_MSG); 24 | responseErrorObject.setErrorDescription(GlobalVariable.OAUTH_ERROR_MSG); 25 | responseErrorObject.setErrorUriMsg(GlobalVariable.OAUTH_ERROR_URI_MSG); 26 | return ResponseEntity.status(httpStatus).body(responseErrorObject); 27 | } 28 | 29 | public static ResponseEntity failure(HttpStatus httpStatus, String message) { 30 | ResponseErrorObject responseErrorObject = new ResponseErrorObject(); 31 | responseErrorObject.setError(GlobalVariable.OAUTH_ERROR_MSG); 32 | responseErrorObject.setErrorDescription(message); 33 | responseErrorObject.setErrorUriMsg(GlobalVariable.OAUTH_ERROR_URI_MSG); 34 | return ResponseEntity.status(httpStatus).body(responseErrorObject); 35 | } 36 | 37 | //=====================================异常返回 end===================================== 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/response/ResponseErrorEnum.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util.response; 2 | 3 | /** 4 | * 微信:https://mp.weixin.qq.com/wiki?action=doc&id=mp1433747234 5 | * 钉钉:https://open-doc.dingtalk.com/microapp/faquestions/rftpfg 6 | */ 7 | public enum ResponseErrorEnum { 8 | 9 | SYSTEM_BUSY(1, "系统繁忙,请稍候重试"), 10 | ILLEGAL_STATE(100001, "非法访问"), 11 | PARAM_REQUIRED(100002, "参数不能为空"), 12 | PARAM_FORMAT_ILLEGAL(100003, "参数格式错误"), 13 | REQUEST_DATA_DUPLICATION(100004, "重复请求"), 14 | REQUEST_DATA_ERROR(100005, "请求数据错误"), 15 | REQUEST_DATA_NOT_MATCH(100006, "请求数据不一致"), 16 | RECORD_NOT_EXIST(100007, "数据不存在"), 17 | RECORD_EXISTED(100008, "数据已存在"), 18 | RECORD_ILLEGAL_STATE(100009, "数据异常"), 19 | CALL_INNER_ERROR(100010, "调用内部服务接口异常"), 20 | THIRD_PART_ERROR(100011, "调用第三方接口异常"), 21 | SYSTEM_ERROR(999999, "系统异常"); 22 | 23 | private int code; 24 | private String description; 25 | 26 | ResponseErrorEnum(final int code, final String description) { 27 | this.code = code; 28 | this.description = description; 29 | } 30 | 31 | public int getCode() { 32 | return code; 33 | } 34 | 35 | public String getDescription() { 36 | return description; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/cdk8s/tkey/server/util/response/ResponseErrorObject.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.util.response; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.*; 6 | 7 | import java.io.Serializable; 8 | 9 | 10 | @Setter 11 | @Getter 12 | @ToString 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 16 | public class ResponseErrorObject implements Serializable { 17 | 18 | private static final long serialVersionUID = 9010786825517645779L; 19 | 20 | private String error; 21 | private String errorDescription; 22 | private String errorUriMsg; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | 2 | #================================================================================= 3 | # 容器相关设置 4 | server: 5 | port: ${SERVER_PORT:9091} 6 | servlet: 7 | context-path: /sso 8 | tomcat: 9 | max-connections: 10000 10 | max-threads: 200 11 | min-spare-threads: 200 12 | connection-timeout: 5000ms 13 | 14 | 15 | debug: true 16 | logging: 17 | config: classpath:logback/logback-dev.xml 18 | #================================================================================= 19 | # spring 组件相关设置 20 | spring: 21 | redis: 22 | database: ${SPRING_REDIS_DATABASE:0} 23 | port: ${SPRING_REDIS_PORT:6379} 24 | host: ${SPRING_REDIS_HOST:redis.cdk8s.com} 25 | password: ${SPRING_REDIS_PASSWORD:123456} 26 | timeout: 10000ms 27 | lettuce: 28 | pool: 29 | max-active: -1 30 | max-idle: -1 31 | min-idle: 200 32 | max-wait: 2000ms 33 | 34 | # actuator 35 | management: 36 | server: 37 | port: 19091 38 | 39 | #================================================================================= 40 | # 其他辅助框架相关配置 41 | 42 | #================================================================================= 43 | # 自定义参数相关配置 44 | tkey: 45 | sso: 46 | oauth: 47 | error-uri-msg: "See the full API docs at https://github.com/cdk8s" 48 | # 用于 token 前缀,表名是哪个节点服务生成的 token 49 | node-number: ${TKEY_NODE_NUMBER:10} 50 | tgc-cookie-secure: false 51 | remember-me-max-time-to-live-in-seconds: 604800 52 | code-max-time-to-live-in-seconds: 120 53 | access-token-max-time-to-live-in-seconds: 43200 54 | refresh-token-max-time-to-live-in-seconds: 86400 55 | tgc-and-user-info-max-time-to-live-in-seconds: 86400 56 | #================================================================================= 57 | -------------------------------------------------------------------------------- /src/main/resources/application-gatling.yml: -------------------------------------------------------------------------------- 1 | 2 | #================================================================================= 3 | # 容器相关设置 4 | server: 5 | port: ${SERVER_PORT:9091} 6 | servlet: 7 | context-path: /sso 8 | tomcat: 9 | max-connections: 10000 10 | max-threads: 200 11 | min-spare-threads: 200 12 | connection-timeout: 5000ms 13 | 14 | 15 | debug: false 16 | logging: 17 | config: classpath:logback/logback-gatling.xml 18 | #================================================================================= 19 | # spring 组件相关设置 20 | spring: 21 | redis: 22 | database: ${SPRING_REDIS_DATABASE:0} 23 | port: ${SPRING_REDIS_PORT:6379} 24 | host: ${SPRING_REDIS_HOST:redis.cdk8s.com} 25 | password: ${SPRING_REDIS_PASSWORD:123456} 26 | timeout: 10000ms 27 | lettuce: 28 | pool: 29 | max-active: -1 30 | max-idle: -1 31 | min-idle: 200 32 | max-wait: 2000ms 33 | 34 | # actuator 35 | management: 36 | server: 37 | port: 19091 38 | 39 | #================================================================================= 40 | # 其他辅助框架相关配置 41 | 42 | #================================================================================= 43 | # 自定义参数相关配置 44 | tkey: 45 | sso: 46 | oauth: 47 | error-uri-msg: "See the full API docs at https://github.com/cdk8s" 48 | # 用于 token 前缀,表名是哪个节点服务生成的 token 49 | node-number: ${TKEY_NODE_NUMBER:10} 50 | tgc-cookie-secure: false 51 | remember-me-max-time-to-live-in-seconds: 604800 52 | code-max-time-to-live-in-seconds: 120 53 | access-token-max-time-to-live-in-seconds: 43200 54 | refresh-token-max-time-to-live-in-seconds: 86400 55 | tgc-and-user-info-max-time-to-live-in-seconds: 86400 56 | #================================================================================= 57 | -------------------------------------------------------------------------------- /src/main/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | 2 | #================================================================================= 3 | # 容器相关设置 4 | server: 5 | port: ${SERVER_PORT:9091} 6 | servlet: 7 | context-path: /sso 8 | tomcat: 9 | max-connections: 10000 10 | max-threads: 200 11 | min-spare-threads: 200 12 | connection-timeout: 5000ms 13 | 14 | 15 | debug: true 16 | logging: 17 | config: classpath:logback/logback-test.xml 18 | #================================================================================= 19 | # spring 组件相关设置 20 | spring: 21 | redis: 22 | database: ${SPRING_REDIS_DATABASE:0} 23 | port: ${SPRING_REDIS_PORT:6379} 24 | host: ${SPRING_REDIS_HOST:redis.cdk8s.com} 25 | password: ${SPRING_REDIS_PASSWORD:123456} 26 | timeout: 10000ms 27 | lettuce: 28 | pool: 29 | max-active: -1 30 | max-idle: -1 31 | min-idle: 200 32 | max-wait: 2000ms 33 | 34 | # actuator 35 | management: 36 | server: 37 | port: 19091 38 | 39 | #================================================================================= 40 | # 其他辅助框架相关配置 41 | 42 | #================================================================================= 43 | # 自定义参数相关配置 44 | tkey: 45 | sso: 46 | oauth: 47 | error-uri-msg: "See the full API docs at https://github.com/cdk8s" 48 | # 用于 token 前缀,表名是哪个节点服务生成的 token 49 | node-number: ${TKEY_NODE_NUMBER:10} 50 | tgc-cookie-secure: false 51 | remember-me-max-time-to-live-in-seconds: 604800 52 | code-max-time-to-live-in-seconds: 120 53 | access-token-max-time-to-live-in-seconds: 43200 54 | refresh-token-max-time-to-live-in-seconds: 86400 55 | tgc-and-user-info-max-time-to-live-in-seconds: 86400 56 | #================================================================================= 57 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | #================================================================================= 3 | # 容器相关设置 4 | 5 | #================================================================================= 6 | # spring 组件相关设置 7 | spring: 8 | application: 9 | name: tkey-sso-server 10 | profiles: 11 | active: ${SPRING_PROFILES_ACTIVE:dev} 12 | # active: 'gatling' 13 | jackson: 14 | default-property-inclusion: non_null 15 | date-format: 'yyyy-MM-dd HH:mm:ss' 16 | thymeleaf: 17 | encoding: utf-8 18 | prefix: 'classpath:/templates/' 19 | suffix: '.html' 20 | cache: false 21 | 22 | 23 | # actuator 24 | management: 25 | endpoints: 26 | web: 27 | exposure: 28 | include: "*" 29 | endpoint: 30 | health: 31 | show-details: ALWAYS 32 | server: 33 | servlet: 34 | context-path: /tkey-actuator 35 | 36 | 37 | #================================================================================= 38 | # 其他辅助框架相关配置 39 | 40 | #================================================================================= 41 | # 自定义参数相关配置 42 | 43 | 44 | #================================================================================= 45 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 2 | _______ _ __________ __ _____ _____ _ _____ _____ 3 | |__ __| |/ / ____\ \ / / / ____| __ \| |/ / _ \ / ____| 4 | | | | ' /| |__ \ \_/ /_____| | | | | | ' / (_) | (___ 5 | | | | < | __| \ /______| | | | | | < > _ < \___ \ 6 | | | | . \| |____ | | | |____| |__| | . \ (_) |____) | 7 | |_| |_|\_\______| |_| \_____|_____/|_|\_\___/|_____/ 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/logback/logback-dev.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | ${CONSOLE_LOG_PATTERN} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 0 20 | ${DEFAULT_ASYNC_QUEUE_SIZE} 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/main/resources/logback/logback-gatling.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ${LOG_FILE_ROOT_PATH}/${APP_NAME}.error.log 20 | 21 | ${FILE_LOG_PATTERN} 22 | UTF-8 23 | 24 | 25 | ${LOG_FILE_ROOT_PATH}/archived/${APP_NAME}.error.%d{yyyy-MM-dd}_%i.log.gz 26 | ${DEFAULT_MAX_FILE_SIZE} 27 | ${DEFAULT_MAX_HISTORY} 28 | 29 | 30 | ERROR 31 | ACCEPT 32 | DENY 33 | 34 | 35 | 36 | 37 | 38 | 39 | 0 40 | ${DEFAULT_ASYNC_QUEUE_SIZE} 41 | true 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/main/resources/logback/logback-prod.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ${LOG_FILE_ROOT_PATH}/${APP_NAME}.log 20 | 21 | ${FILE_LOG_PATTERN} 22 | UTF-8 23 | 24 | 25 | ${LOG_FILE_ROOT_PATH}/archived/${APP_NAME}.%d{yyyy-MM-dd}_%i.log.gz 26 | ${DEFAULT_MAX_FILE_SIZE} 27 | ${DEFAULT_MAX_HISTORY} 28 | 29 | 30 | INFO 31 | ACCEPT 32 | 33 | 34 | 35 | 36 | 37 | 38 | ${LOG_FILE_ROOT_PATH}/${APP_NAME}.error.log 39 | 40 | ${FILE_LOG_PATTERN} 41 | UTF-8 42 | 43 | 44 | ${LOG_FILE_ROOT_PATH}/archived/${APP_NAME}.error.%d{yyyy-MM-dd}_%i.log.gz 45 | ${DEFAULT_MAX_FILE_SIZE} 46 | ${DEFAULT_MAX_HISTORY} 47 | 48 | 49 | ERROR 50 | ACCEPT 51 | DENY 52 | 53 | 54 | 55 | 56 | 57 | 58 | 0 59 | ${DEFAULT_ASYNC_QUEUE_SIZE} 60 | true 61 | 62 | 63 | 64 | 65 | 0 66 | ${DEFAULT_ASYNC_QUEUE_SIZE} 67 | true 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/main/resources/logback/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ${CONSOLE_LOG_PATTERN} 21 | 22 | 23 | 24 | 25 | 26 | 27 | ${LOG_FILE_ROOT_PATH}/${APP_NAME}.log 28 | 29 | ${FILE_LOG_PATTERN} 30 | UTF-8 31 | 32 | 33 | ${LOG_FILE_ROOT_PATH}/archived/${APP_NAME}.%d{yyyy-MM-dd}_%i.log.gz 34 | ${DEFAULT_MAX_FILE_SIZE} 35 | ${DEFAULT_MAX_HISTORY} 36 | 37 | 38 | INFO 39 | ACCEPT 40 | 41 | 42 | 43 | 44 | 45 | 46 | ${LOG_FILE_ROOT_PATH}/${APP_NAME}.error.log 47 | 48 | ${FILE_LOG_PATTERN} 49 | UTF-8 50 | 51 | 52 | ${LOG_FILE_ROOT_PATH}/archived/${APP_NAME}.error.%d{yyyy-MM-dd}_%i.log.gz 53 | ${DEFAULT_MAX_FILE_SIZE} 54 | ${DEFAULT_MAX_HISTORY} 55 | 56 | 57 | ERROR 58 | ACCEPT 59 | DENY 60 | 61 | 62 | 63 | 64 | 65 | 66 | 0 67 | ${DEFAULT_ASYNC_QUEUE_SIZE} 68 | true 69 | 70 | 71 | 72 | 73 | 0 74 | ${DEFAULT_ASYNC_QUEUE_SIZE} 75 | true 76 | 77 | 78 | 79 | 80 | 0 81 | ${DEFAULT_ASYNC_QUEUE_SIZE} 82 | true 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/main/resources/static/css/login.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | line-height: 1.15; 4 | /* 1 */ 5 | -webkit-text-size-adjust: 100%; 6 | /* 2 */ 7 | } 8 | 9 | [type="checkbox"], [type="radio"] { 10 | box-sizing: border-box; 11 | padding: 0; 12 | width: 16px; 13 | height: 16px; 14 | line-height: 19px; 15 | vertical-align: sub; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | padding: 0px; 21 | min-width: 278px; 22 | background: #f7f7f7; 23 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Liberation Sans", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, SimSun, "WenQuanYi Zen Hei Sharp", sans-serif; 24 | font-size: 14px; 25 | line-height: 1.33; 26 | } 27 | 28 | 29 | main { 30 | display: block; 31 | } 32 | 33 | 34 | h1 { 35 | font-size: 2em; 36 | margin: 0.67em 0; 37 | } 38 | 39 | 40 | hr { 41 | box-sizing: content-box; 42 | /* 1 */ 43 | height: 0; 44 | /* 1 */ 45 | overflow: visible; 46 | /* 2 */ 47 | } 48 | 49 | 50 | pre { 51 | font-family: monospace, monospace; 52 | /* 1 */ 53 | font-size: 1em; 54 | /* 2 */ 55 | } 56 | 57 | 58 | a { 59 | cursor: pointer; 60 | background-color: transparent; 61 | color: #137eff; 62 | } 63 | 64 | 65 | abbr[title] { 66 | border-bottom: none; 67 | /* 1 */ 68 | text-decoration: underline; 69 | /* 2 */ 70 | text-decoration: underline dotted; 71 | /* 2 */ 72 | } 73 | 74 | 75 | b, strong { 76 | font-weight: bolder; 77 | } 78 | 79 | 80 | code, kbd, samp { 81 | font-family: monospace, monospace; 82 | /* 1 */ 83 | font-size: 1em; 84 | /* 2 */ 85 | } 86 | 87 | 88 | small { 89 | font-size: 80%; 90 | } 91 | 92 | sub, sup { 93 | font-size: 75%; 94 | line-height: 0; 95 | position: relative; 96 | vertical-align: baseline; 97 | } 98 | 99 | sub { 100 | bottom: -0.25em; 101 | } 102 | 103 | sup { 104 | top: -0.5em; 105 | } 106 | 107 | 108 | img { 109 | border-style: none; 110 | } 111 | 112 | 113 | button, input, optgroup, select, textarea { 114 | font-family: inherit; 115 | /* 1 */ 116 | font-size: 100%; 117 | /* 1 */ 118 | line-height: 1.15; 119 | /* 1 */ 120 | margin: 0; 121 | /* 2 */ 122 | } 123 | 124 | 125 | button, input { 126 | /* 1 */ 127 | overflow: visible; 128 | } 129 | 130 | 131 | button, select { 132 | /* 1 */ 133 | text-transform: none; 134 | } 135 | 136 | 137 | button, [type="button"], [type="reset"], [type="submit"] { 138 | -webkit-appearance: button; 139 | } 140 | 141 | 142 | button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { 143 | border-style: none; 144 | padding: 0; 145 | } 146 | 147 | 148 | button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { 149 | outline: 1px dotted ButtonText; 150 | } 151 | 152 | 153 | fieldset { 154 | padding: 0.35em 0.75em 0.625em; 155 | } 156 | 157 | 158 | legend { 159 | box-sizing: border-box; 160 | /* 1 */ 161 | color: inherit; 162 | /* 2 */ 163 | display: table; 164 | /* 1 */ 165 | max-width: 100%; 166 | /* 1 */ 167 | padding: 0; 168 | /* 3 */ 169 | white-space: normal; 170 | /* 1 */ 171 | } 172 | 173 | 174 | progress { 175 | vertical-align: baseline; 176 | } 177 | 178 | 179 | textarea { 180 | overflow: auto; 181 | } 182 | 183 | 184 | [type="checkbox"], [type="radio"] { 185 | box-sizing: border-box; 186 | /* 1 */ 187 | padding: 0; 188 | /* 2 */ 189 | } 190 | 191 | 192 | [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { 193 | height: auto; 194 | } 195 | 196 | 197 | [type="search"] { 198 | -webkit-appearance: textfield; 199 | /* 1 */ 200 | outline-offset: -2px; 201 | /* 2 */ 202 | } 203 | 204 | 205 | [type="search"]::-webkit-search-decoration { 206 | -webkit-appearance: none; 207 | } 208 | 209 | 210 | ::-webkit-file-upload-button { 211 | -webkit-appearance: button; 212 | /* 1 */ 213 | font: inherit; 214 | /* 2 */ 215 | } 216 | 217 | 218 | details { 219 | display: block; 220 | } 221 | 222 | 223 | summary { 224 | display: list-item; 225 | } 226 | 227 | 228 | template { 229 | display: none; 230 | } 231 | 232 | 233 | [hidden] { 234 | display: none; 235 | } 236 | 237 | 238 | /* 代码*/ 239 | 240 | .header { 241 | margin-top: 60px; 242 | position: relative; 243 | } 244 | 245 | .login-wrapper { 246 | display: flex; 247 | width: 1000px; 248 | height: 500px; 249 | background: #fff; 250 | box-shadow: 0px 20px 80px 0px rgba(0, 0, 0, 0.3); 251 | margin: 50px auto 0 auto; 252 | } 253 | 254 | .content-left { 255 | width: 500px; 256 | height: 500px; 257 | background-image: url("../images/tkey-tool.png"); 258 | background-size: 100%; 259 | background-repeat: no-repeat; 260 | } 261 | 262 | .content-right { 263 | color: #666; 264 | box-sizing: border-box; 265 | width: 50%; 266 | padding: 5px 75px 48px; 267 | } 268 | 269 | .right-header { 270 | display: flex; 271 | } 272 | 273 | .right-header-h2 { 274 | flex: 1; 275 | margin-top: 20px; 276 | text-align: center; 277 | color: #666666; 278 | } 279 | 280 | .right-header-span { 281 | color: #b3b3b3; 282 | text-align: right; 283 | flex: 1; 284 | } 285 | 286 | .right-form-wrapper { 287 | margin-top: 0; 288 | } 289 | 290 | .username-input-default { 291 | box-sizing: border-box; 292 | padding-left: 20px; 293 | margin-top: 10px; 294 | width: 100%; 295 | height: 40px; 296 | outline: none; 297 | line-height: 40px; 298 | border: 1px solid #e5e5e5; 299 | border-radius: 0em 0.2857rem 0.2857rem 0em; 300 | background: #ffffff; 301 | } 302 | 303 | .username-input-default::-webkit-input-placeholder { 304 | color: #b3b3b3; 305 | } 306 | 307 | .username-input-default:-moz-placeholder { 308 | color: #b3b3b3; 309 | } 310 | 311 | .username-input-default::-moz-placeholder { 312 | color: #b3b3b3; 313 | } 314 | 315 | .username-input-default::-ms-input-placeholder { 316 | color: #b3b3b3; 317 | } 318 | 319 | .password-input-default { 320 | box-sizing: border-box; 321 | padding-left: 20px; 322 | margin-top: 5px; 323 | width: 100%; 324 | height: 40px; 325 | outline: none; 326 | line-height: 40px; 327 | border: 1px solid #e5e5e5; 328 | border-radius: 0em 0.2857rem 0.2857rem 0em; 329 | background: #ffffff; 330 | } 331 | 332 | .password-input-default::-webkit-input-placeholder { 333 | color: #b3b3b3; 334 | } 335 | 336 | .password-input-default:-moz-placeholder { 337 | color: #b3b3b3; 338 | } 339 | 340 | .password-input-default::-moz-placeholder { 341 | color: #b3b3b3; 342 | } 343 | 344 | .password-input-default::-ms-input-placeholder { 345 | color: #b3b3b3; 346 | } 347 | 348 | .username-input-error { 349 | box-sizing: border-box; 350 | padding-left: 20px; 351 | margin-top: 10px; 352 | width: 100%; 353 | height: 40px; 354 | outline: none; 355 | line-height: 40px; 356 | border: 1px solid #dbb1b1; 357 | border-radius: 0em 0.2857rem 0.2857rem 0em; 358 | background: #fff0f0; 359 | } 360 | 361 | .password-input-error { 362 | box-sizing: border-box; 363 | padding-left: 20px; 364 | margin-top: 5px; 365 | width: 100%; 366 | height: 40px; 367 | outline: none; 368 | line-height: 40px; 369 | border: 1px solid #dbb1b1; 370 | border-radius: 0em 0.2857rem 0.2857rem 0em; 371 | background: #fff0f0; 372 | } 373 | 374 | .checkMe { 375 | display: flex; 376 | align-items: center; 377 | justify-content: space-between; 378 | margin-top: 5px; 379 | } 380 | 381 | .text-login { 382 | 383 | } 384 | 385 | .submit-btn-wrapper { 386 | margin-top: 15px; 387 | position: relative; 388 | } 389 | 390 | .submit-btn { 391 | width: 100%; 392 | padding: 8px 26px; 393 | color: #fff; 394 | background: #78b2ff; 395 | font-size: 20px; 396 | line-height: 22px; 397 | font-weight: 500; 398 | border: none; 399 | outline: none; 400 | cursor: pointer; 401 | border-radius: 22px; 402 | letter-spacing: 4px; 403 | } 404 | 405 | .btn-loading { 406 | position: absolute; 407 | left: 60%; 408 | top: 50%; 409 | transform: translate(-50%,-50%); 410 | width: 18px; 411 | height: 18px; 412 | display: none; 413 | } 414 | 415 | .logo-img { 416 | height: 60px; 417 | text-align: center; 418 | margin-left: 50%; 419 | transform: translateX(-50%); 420 | } 421 | 422 | .footer { 423 | margin-top: 142px; 424 | color: #8c92a4; 425 | text-align: center; 426 | } 427 | 428 | .ml5 { 429 | margin-left: 5px; 430 | } 431 | 432 | .mt10 { 433 | margin-top: 10px; 434 | } 435 | 436 | .mt15 { 437 | margin-top: 10px; 438 | } 439 | 440 | .tac { 441 | text-align: center; 442 | } 443 | 444 | @media screen and (max-width: 400px) and (min-width: 300px) { 445 | .content-left { 446 | display: none; 447 | } 448 | } 449 | 450 | .other-login-wrapper { 451 | margin-top: 13px; 452 | } 453 | 454 | .line { 455 | display: inline-block; 456 | width: 33%; 457 | height: 1px; 458 | background-color: #ccc; 459 | vertical-align: middle; 460 | } 461 | 462 | .other-login { 463 | padding: 10px; 464 | display: inline-block; 465 | vertical-align: middle; 466 | color: #b3b3b3; 467 | } 468 | 469 | .login-logo-icon { 470 | display: flex; 471 | justify-content: center; 472 | } 473 | 474 | .login-logo-icon-item { 475 | z-index: 2; 476 | cursor: pointer; 477 | width: 35px; 478 | height: 35px; 479 | margin: 0 10px; 480 | border-radius: 50%; 481 | } 482 | 483 | /*qq 企业微信 钉钉 微博 微信 GitHub google Facebook 推特 */ 484 | 485 | .mt25 { 486 | margin-top: 25px; 487 | } 488 | 489 | .dn { 490 | display: none; 491 | } 492 | 493 | .db { 494 | display: block; 495 | } 496 | 497 | .username-error { 498 | height: 18px; 499 | margin: 5px 0 0 0; 500 | color: #d95c5c; 501 | } 502 | 503 | .password-error { 504 | margin: 5px 0 0 0; 505 | color: #d95c5c; 506 | } 507 | 508 | .opacity0 { 509 | opacity: 0; 510 | transition: opacity .5s; 511 | } 512 | 513 | .opacity1 { 514 | opacity: 1; 515 | transition: opacity .5s; 516 | } 517 | -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdk8s/tkey/b2ac49e90a48a8c91f7d36c571f4acc80d6b98d9/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/images/404-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdk8s/tkey/b2ac49e90a48a8c91f7d36c571f4acc80d6b98d9/src/main/resources/static/images/404-qr.png -------------------------------------------------------------------------------- /src/main/resources/static/images/error-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdk8s/tkey/b2ac49e90a48a8c91f7d36c571f4acc80d6b98d9/src/main/resources/static/images/error-qr.png -------------------------------------------------------------------------------- /src/main/resources/static/images/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/main/resources/static/images/tkey-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdk8s/tkey/b2ac49e90a48a8c91f7d36c571f4acc80d6b98d9/src/main/resources/static/images/tkey-logo.png -------------------------------------------------------------------------------- /src/main/resources/static/images/tkey-tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdk8s/tkey/b2ac49e90a48a8c91f7d36c571f4acc80d6b98d9/src/main/resources/static/images/tkey-tool.png -------------------------------------------------------------------------------- /src/main/resources/static/js/login.js: -------------------------------------------------------------------------------- 1 | const username = document.querySelector('.username'); 2 | const password = document.querySelector('.password'); 3 | const btnLoading = document.querySelector('.btn-loading'); 4 | const submitBtn = document.querySelector('.submit-btn'); 5 | const usernameError = document.querySelector('.username-error'); 6 | const passwordError = document.querySelector('.password-error'); 7 | window.onload = function () { 8 | if (usernameError.innerHTML !== '') { 9 | username.className = 'username username-input-error'; 10 | usernameError.className = 'username-error opacity1'; 11 | } 12 | }; 13 | 14 | function handleSubmit(e) { 15 | if (username.value === '') { 16 | username.className = 'username username-input-error'; 17 | usernameError.className = 'username-error opacity1'; 18 | usernameError.innerHTML = '请输入用户名'; 19 | return false; 20 | } 21 | if (password.value === '') { 22 | password.className = 'password password-input-error'; 23 | passwordError.className = 'password-error opacity1'; 24 | usernameError.innerHTML = '请输入密码'; 25 | return false 26 | } 27 | btnLoading.style.display = 'block'; 28 | submitBtn.style.backgroundColor = '#b3b3b3'; 29 | submitBtn.style.cursor = 'default'; 30 | return true; 31 | } 32 | 33 | function usernameInputFocus() { 34 | if (username.className === 'username username-input-error') { 35 | username.className = 'username username-input-default'; 36 | usernameError.className = 'username-error opacity0'; 37 | } 38 | } 39 | 40 | function passwordInputFocus() { 41 | if (password.className === 'password password-input-error') { 42 | password.className = 'password password-input-default'; 43 | passwordError.className = 'password-error opacity0'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/templates/404.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 404 页面 5 | 6 | 7 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |

啊~ 哦~

24 |

您要查看的页面不存在或已删除!

25 |

请检查您输入的网址是否正确

26 |
27 |
28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | ERROR 页面 5 | 6 | 7 | 17 | 18 | 19 |
20 |
21 |
22 |
23 |

啊~ 哦~ 发生错误了:

24 |

25 |
26 |
27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 这是首页 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 这是首页,请访问业务系统跳转到登录页面 16 |

17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/logoutSuccess.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 这是首页 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | 登出成功 16 |

17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/java/com/cdk8s/tkey/server/controller/AuthorizationCodeByFormTest.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.controller; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariableToJunit; 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.junit.FixMethodOrder; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.junit.runners.MethodSorters; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | import org.springframework.test.web.servlet.RequestBuilder; 18 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 19 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 20 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 21 | 22 | import java.nio.charset.Charset; 23 | import java.nio.charset.StandardCharsets; 24 | 25 | /** 26 | * 把 GlobalVariableToJunit.CLIENT_SECRET 和 GlobalVariableToJunit.CLIENT_SECRET 使用 Form 参数方式提交 27 | */ 28 | @Slf4j 29 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 30 | @RunWith(SpringRunner.class) 31 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 32 | @AutoConfigureMockMvc 33 | public class AuthorizationCodeByFormTest { 34 | 35 | private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8; 36 | 37 | private static final MediaType JSON_MEDIA_TYPE = new MediaType(MediaType.APPLICATION_JSON_UTF8.getType(), MediaType.APPLICATION_JSON_UTF8.getSubtype(), UTF8_CHARSET); 38 | private static final MediaType TEXT_MEDIA_TYPE = new MediaType(MediaType.TEXT_HTML.getType(), MediaType.TEXT_HTML.getSubtype(), UTF8_CHARSET); 39 | private static final MediaType DEFAULT_FORM_MEDIA_TYPE = new MediaType(MediaType.APPLICATION_FORM_URLENCODED.getType(), MediaType.APPLICATION_FORM_URLENCODED.getSubtype(), UTF8_CHARSET); 40 | 41 | private static final String ROOT_PATH = "/oauth"; 42 | 43 | private static final String REDIRECT_URI = "http://test1.cdk8s.com:9393/client-scribejava/codeCallback/aHR0cDovL3Rlc3QxLmNkazhzLmNvbTo5MzkzL2NsaWVudC1zY3JpYmVqYXZhL3VzZXI_aWQ9MTIzNDU2Jm5hbWU9Y2RrOHM"; 44 | 45 | 46 | @Autowired 47 | private MockMvc mockMvc; 48 | 49 | //=====================================业务处理 start===================================== 50 | 51 | @SneakyThrows 52 | @Test 53 | public void a_authorize() { 54 | RequestBuilder request = MockMvcRequestBuilders 55 | .get(ROOT_PATH + "/authorize") 56 | .param("response_type", GlobalVariableToJunit.CODE_RESPONSE_TYPE) 57 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 58 | .param("redirect_uri", REDIRECT_URI) 59 | .accept(TEXT_MEDIA_TYPE) 60 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 61 | 62 | mockMvc.perform(request) 63 | .andDo(MockMvcResultHandlers.print()) 64 | .andExpect(MockMvcResultMatchers.status().isOk()) 65 | .andExpect(MockMvcResultMatchers.view().name("login")) 66 | .andExpect(MockMvcResultMatchers.model().attributeExists("oauthClient")); 67 | } 68 | 69 | @SneakyThrows 70 | @Test 71 | public void b_formLogin() { 72 | RequestBuilder request = MockMvcRequestBuilders 73 | .post(ROOT_PATH + "/authorize") 74 | .header("User-Agent", GlobalVariableToJunit.USER_AGENT) 75 | .param("response_type", GlobalVariableToJunit.CODE_RESPONSE_TYPE) 76 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 77 | .param("redirect_uri", REDIRECT_URI) 78 | .param("username", GlobalVariableToJunit.USERNAME) 79 | .param("password", GlobalVariableToJunit.PASSWORD) 80 | .accept(TEXT_MEDIA_TYPE) 81 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 82 | 83 | mockMvc.perform(request) 84 | .andDo(MockMvcResultHandlers.print()) 85 | .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) 86 | .andExpect(MockMvcResultMatchers.redirectedUrlPattern("http://test1.cdk8s.com:9393/client-scribejava/**")); 87 | } 88 | 89 | @SneakyThrows 90 | @Test 91 | public void c_token() { 92 | RequestBuilder request = MockMvcRequestBuilders 93 | .post(ROOT_PATH + "/token") 94 | .param("grant_type", GlobalVariableToJunit.CODE_GRANT_TYPE) 95 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 96 | .param("client_secret", GlobalVariableToJunit.CLIENT_SECRET) 97 | .param("redirect_uri", REDIRECT_URI) 98 | .param("code", GlobalVariableToJunit.CODE) 99 | .accept(JSON_MEDIA_TYPE) 100 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 101 | 102 | mockMvc.perform(request) 103 | .andDo(MockMvcResultHandlers.print()) 104 | .andExpect(MockMvcResultMatchers.status().isOk()) 105 | .andExpect(MockMvcResultMatchers.jsonPath("$.access_token").exists()); 106 | } 107 | 108 | @SneakyThrows 109 | @Test 110 | public void d_userinfo() { 111 | RequestBuilder request = MockMvcRequestBuilders 112 | .get(ROOT_PATH + "/userinfo") 113 | .param("access_token", GlobalVariableToJunit.ACCESS_TOKEN) 114 | .accept(JSON_MEDIA_TYPE); 115 | 116 | mockMvc.perform(request) 117 | .andDo(MockMvcResultHandlers.print()) 118 | .andExpect(MockMvcResultMatchers.status().isOk()) 119 | .andExpect(MockMvcResultMatchers.jsonPath("$.user_attribute").exists()); 120 | } 121 | 122 | @SneakyThrows 123 | @Test 124 | public void e_introspectByAccessToken() { 125 | RequestBuilder request = MockMvcRequestBuilders 126 | .post(ROOT_PATH + "/introspect") 127 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 128 | .param("client_secret", GlobalVariableToJunit.CLIENT_SECRET) 129 | .param("token_type_hint", "access_token") 130 | .param("token", GlobalVariableToJunit.ACCESS_TOKEN) 131 | .accept(JSON_MEDIA_TYPE) 132 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 133 | 134 | mockMvc.perform(request) 135 | .andDo(MockMvcResultHandlers.print()) 136 | .andExpect(MockMvcResultMatchers.status().isOk()) 137 | .andExpect(MockMvcResultMatchers.jsonPath("$.client_id").exists()); 138 | } 139 | 140 | @SneakyThrows 141 | @Test 142 | public void f_introspectByRefreshToken() { 143 | RequestBuilder request = MockMvcRequestBuilders 144 | .post(ROOT_PATH + "/introspect") 145 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 146 | .param("client_secret", GlobalVariableToJunit.CLIENT_SECRET) 147 | .param("token_type_hint", "refresh_token") 148 | .param("token", GlobalVariableToJunit.REFRESH_TOKEN) 149 | .accept(JSON_MEDIA_TYPE) 150 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 151 | 152 | mockMvc.perform(request) 153 | .andDo(MockMvcResultHandlers.print()) 154 | .andExpect(MockMvcResultMatchers.status().isOk()) 155 | .andExpect(MockMvcResultMatchers.jsonPath("$.client_id").exists()); 156 | } 157 | 158 | @SneakyThrows 159 | @Test 160 | public void g_logout() { 161 | RequestBuilder request = MockMvcRequestBuilders 162 | .get(ROOT_PATH + "/logout") 163 | .param("redirect_uri", "http://www.gitnavi.com") 164 | .accept(TEXT_MEDIA_TYPE); 165 | 166 | mockMvc.perform(request) 167 | .andDo(MockMvcResultHandlers.print()) 168 | .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) 169 | .andExpect(MockMvcResultMatchers.redirectedUrlPattern("http://www.gitnavi.com**")); 170 | } 171 | 172 | 173 | //=====================================业务处理 end===================================== 174 | //=====================================私有方法 start===================================== 175 | 176 | //=====================================私有方法 end===================================== 177 | } 178 | -------------------------------------------------------------------------------- /src/test/java/com/cdk8s/tkey/server/controller/AuthorizationCodeByHeaderTest.java: -------------------------------------------------------------------------------- 1 | package com.cdk8s.tkey.server.controller; 2 | 3 | import com.cdk8s.tkey.server.constant.GlobalVariableToJunit; 4 | import lombok.SneakyThrows; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.junit.FixMethodOrder; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.junit.runners.MethodSorters; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 14 | import org.springframework.http.MediaType; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | import org.springframework.test.web.servlet.RequestBuilder; 18 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; 19 | import org.springframework.test.web.servlet.result.MockMvcResultHandlers; 20 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 21 | 22 | import java.nio.charset.Charset; 23 | import java.nio.charset.StandardCharsets; 24 | 25 | /** 26 | * 把 CLIENT_SECRET 和 CLIENT_SECRET 使用 Header Authorization 参数方式提交 27 | */ 28 | @Slf4j 29 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 30 | @RunWith(SpringRunner.class) 31 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 32 | @AutoConfigureMockMvc 33 | public class AuthorizationCodeByHeaderTest { 34 | 35 | private static final Charset UTF8_CHARSET = StandardCharsets.UTF_8; 36 | 37 | private static final MediaType JSON_MEDIA_TYPE = new MediaType(MediaType.APPLICATION_JSON_UTF8.getType(), MediaType.APPLICATION_JSON_UTF8.getSubtype(), UTF8_CHARSET); 38 | private static final MediaType TEXT_MEDIA_TYPE = new MediaType(MediaType.TEXT_HTML.getType(), MediaType.TEXT_HTML.getSubtype(), UTF8_CHARSET); 39 | private static final MediaType DEFAULT_FORM_MEDIA_TYPE = new MediaType(MediaType.APPLICATION_FORM_URLENCODED.getType(), MediaType.APPLICATION_FORM_URLENCODED.getSubtype(), UTF8_CHARSET); 40 | 41 | private static final String ROOT_PATH = "/oauth"; 42 | 43 | private static final String REDIRECT_URI = "http://test1.cdk8s.com:9393/client-scribejava/codeCallback/aHR0cDovL3Rlc3QxLmNkazhzLmNvbTo5MzkzL2NsaWVudC1zY3JpYmVqYXZhL3VzZXI_aWQ9MTIzNDU2Jm5hbWU9Y2RrOHM"; 44 | 45 | @Autowired 46 | private MockMvc mockMvc; 47 | 48 | //=====================================业务处理 start===================================== 49 | 50 | @SneakyThrows 51 | @Test 52 | public void a_authorize() { 53 | RequestBuilder request = MockMvcRequestBuilders 54 | .get(ROOT_PATH + "/authorize") 55 | .param("response_type", GlobalVariableToJunit.CODE_RESPONSE_TYPE) 56 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 57 | .param("redirect_uri", REDIRECT_URI) 58 | .accept(TEXT_MEDIA_TYPE) 59 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 60 | 61 | mockMvc.perform(request) 62 | .andDo(MockMvcResultHandlers.print()) 63 | .andExpect(MockMvcResultMatchers.status().isOk()) 64 | .andExpect(MockMvcResultMatchers.view().name("login")) 65 | .andExpect(MockMvcResultMatchers.model().attributeExists("oauthClient")); 66 | } 67 | 68 | @SneakyThrows 69 | @Test 70 | public void b_formLogin() { 71 | RequestBuilder request = MockMvcRequestBuilders 72 | .post(ROOT_PATH + "/authorize") 73 | .header("User-Agent", GlobalVariableToJunit.USER_AGENT) 74 | .param("response_type", GlobalVariableToJunit.CODE_RESPONSE_TYPE) 75 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 76 | .param("redirect_uri", REDIRECT_URI) 77 | .param("username", GlobalVariableToJunit.USERNAME) 78 | .param("password", GlobalVariableToJunit.PASSWORD) 79 | .accept(TEXT_MEDIA_TYPE) 80 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 81 | 82 | mockMvc.perform(request) 83 | .andDo(MockMvcResultHandlers.print()) 84 | .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) 85 | .andExpect(MockMvcResultMatchers.redirectedUrlPattern("http://test1.cdk8s.com:9393/client-scribejava/**")); 86 | } 87 | 88 | @SneakyThrows 89 | @Test 90 | public void c_token() { 91 | RequestBuilder request = MockMvcRequestBuilders 92 | .post(ROOT_PATH + "/token") 93 | .header("Authorization", GlobalVariableToJunit.HTTP_HEADER_BASIC_AUTHORIZATION) 94 | .param("grant_type", GlobalVariableToJunit.CODE_GRANT_TYPE) 95 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 96 | .param("redirect_uri", REDIRECT_URI) 97 | .param("code", GlobalVariableToJunit.CODE2) 98 | .accept(JSON_MEDIA_TYPE) 99 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 100 | 101 | mockMvc.perform(request) 102 | .andDo(MockMvcResultHandlers.print()) 103 | .andExpect(MockMvcResultMatchers.status().isOk()) 104 | .andExpect(MockMvcResultMatchers.jsonPath("$.access_token").exists()); 105 | } 106 | 107 | @SneakyThrows 108 | @Test 109 | public void d_userinfo() { 110 | RequestBuilder request = MockMvcRequestBuilders 111 | .get(ROOT_PATH + "/userinfo") 112 | .param("access_token", GlobalVariableToJunit.ACCESS_TOKEN) 113 | .accept(JSON_MEDIA_TYPE); 114 | 115 | mockMvc.perform(request) 116 | .andDo(MockMvcResultHandlers.print()) 117 | .andExpect(MockMvcResultMatchers.status().isOk()) 118 | .andExpect(MockMvcResultMatchers.jsonPath("$.user_attribute").exists()); 119 | } 120 | 121 | @SneakyThrows 122 | @Test 123 | public void e_introspectByAccessToken() { 124 | RequestBuilder request = MockMvcRequestBuilders 125 | .post(ROOT_PATH + "/introspect") 126 | .header("Authorization", GlobalVariableToJunit.HTTP_HEADER_BASIC_AUTHORIZATION) 127 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 128 | .param("token_type_hint", "access_token") 129 | .param("token", GlobalVariableToJunit.ACCESS_TOKEN) 130 | .accept(JSON_MEDIA_TYPE) 131 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 132 | 133 | mockMvc.perform(request) 134 | .andDo(MockMvcResultHandlers.print()) 135 | .andExpect(MockMvcResultMatchers.status().isOk()) 136 | .andExpect(MockMvcResultMatchers.jsonPath("$.client_id").exists()); 137 | } 138 | 139 | @SneakyThrows 140 | @Test 141 | public void f_introspectByRefreshToken() { 142 | RequestBuilder request = MockMvcRequestBuilders 143 | .post(ROOT_PATH + "/introspect") 144 | .header("Authorization", GlobalVariableToJunit.HTTP_HEADER_BASIC_AUTHORIZATION) 145 | .param("client_id", GlobalVariableToJunit.CLIENT_ID) 146 | .param("token_type_hint", "refresh_token") 147 | .param("token", GlobalVariableToJunit.REFRESH_TOKEN) 148 | .accept(JSON_MEDIA_TYPE) 149 | .contentType(DEFAULT_FORM_MEDIA_TYPE); 150 | 151 | mockMvc.perform(request) 152 | .andDo(MockMvcResultHandlers.print()) 153 | .andExpect(MockMvcResultMatchers.status().isOk()) 154 | .andExpect(MockMvcResultMatchers.jsonPath("$.client_id").exists()); 155 | } 156 | 157 | @SneakyThrows 158 | @Test 159 | public void g_logout() { 160 | RequestBuilder request = MockMvcRequestBuilders 161 | .get(ROOT_PATH + "/logout") 162 | .param("redirect_uri", "http://www.gitnavi.com") 163 | .accept(TEXT_MEDIA_TYPE); 164 | 165 | mockMvc.perform(request) 166 | .andDo(MockMvcResultHandlers.print()) 167 | .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) 168 | .andExpect(MockMvcResultMatchers.redirectedUrlPattern("http://www.gitnavi.com**")); 169 | } 170 | 171 | 172 | //=====================================业务处理 end===================================== 173 | //=====================================私有方法 start===================================== 174 | 175 | //=====================================私有方法 end===================================== 176 | } 177 | -------------------------------------------------------------------------------- /src/test/resources/application-junit.yml: -------------------------------------------------------------------------------- 1 | 2 | #================================================================================= 3 | # 容器相关设置 4 | server: 5 | port: ${SERVER_PORT:9091} 6 | servlet: 7 | context-path: /sso 8 | tomcat: 9 | max-connections: 10000 10 | max-threads: 200 11 | min-spare-threads: 200 12 | connection-timeout: 5000ms 13 | 14 | 15 | debug: true 16 | logging: 17 | config: classpath:logback/logback-dev.xml 18 | #================================================================================= 19 | # spring 组件相关设置 20 | spring: 21 | redis: 22 | database: ${SPRING_REDIS_DATABASE:0} 23 | port: ${SPRING_REDIS_PORT:6379} 24 | host: ${SPRING_REDIS_HOST:redis.cdk8s.com} 25 | password: ${SPRING_REDIS_PASSWORD:123456} 26 | timeout: 10000ms 27 | lettuce: 28 | pool: 29 | max-active: -1 30 | max-idle: -1 31 | min-idle: 200 32 | max-wait: 2000ms 33 | 34 | # actuator 35 | management: 36 | server: 37 | port: 19091 38 | 39 | #================================================================================= 40 | # 其他辅助框架相关配置 41 | 42 | #================================================================================= 43 | # 自定义参数相关配置 44 | tkey: 45 | sso: 46 | oauth: 47 | error-uri-msg: "See the full API docs at https://github.com/cdk8s" 48 | # 用于 token 前缀,表名是哪个节点服务生成的 token 49 | node-number: ${TKEY_NODE_NUMBER:10} 50 | tgc-cookie-secure: false 51 | remember-me-max-time-to-live-in-seconds: 604800 52 | code-max-time-to-live-in-seconds: 120 53 | access-token-max-time-to-live-in-seconds: 43200 54 | refresh-token-max-time-to-live-in-seconds: 86400 55 | tgc-and-user-info-max-time-to-live-in-seconds: 86400 56 | #================================================================================= 57 | -------------------------------------------------------------------------------- /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | #================================================================================= 3 | # 容器相关设置 4 | 5 | #================================================================================= 6 | # spring 组件相关设置 7 | spring: 8 | application: 9 | name: tkey-sso-server 10 | profiles: 11 | active: 'junit' 12 | jackson: 13 | default-property-inclusion: non_null 14 | date-format: 'yyyy-MM-dd HH:mm:ss' 15 | thymeleaf: 16 | encoding: utf-8 17 | prefix: 'classpath:/templates/' 18 | suffix: '.html' 19 | cache: false 20 | 21 | 22 | # actuator 23 | management: 24 | endpoints: 25 | web: 26 | exposure: 27 | include: "*" 28 | endpoint: 29 | health: 30 | show-details: ALWAYS 31 | server: 32 | servlet: 33 | context-path: /tkey-actuator 34 | 35 | 36 | #================================================================================= 37 | # 其他辅助框架相关配置 38 | 39 | #================================================================================= 40 | # 自定义参数相关配置 41 | 42 | 43 | #================================================================================= 44 | --------------------------------------------------------------------------------