{"id":29363025,"url":"https://github.com/patternhelloworld/spring-oauth2-easyplus","last_synced_at":"2026-02-27T03:01:03.784Z","repository":{"id":232195981,"uuid":"783318298","full_name":"patternhelloworld/spring-oauth2-easyplus","owner":"patternhelloworld","description":"App-Token based fully extended and extensible JPA implementation of Spring Security 6 + Spring Authorization Server (https://mvnrepository.com/artifact/io.github.patternhelloworld.securityhelper.oauth2.api/spring-oauth2-easyplus)","archived":false,"fork":false,"pushed_at":"2025-04-20T11:39:21.000Z","size":1444,"stargazers_count":32,"open_issues_count":0,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-09T09:36:12.571Z","etag":null,"topics":["authorization-code-grant","jpa","jwt","oauth2","oauth2-password-flow","spring-authorization","spring-authorization-server","spring-security"],"latest_commit_sha":null,"homepage":"https://central.sonatype.com/artifact/io.github.patternhelloworld.securityhelper.oauth2.api/spring-oauth2-easyplus","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/patternhelloworld.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-04-07T15:02:40.000Z","updated_at":"2025-10-01T08:02:49.000Z","dependencies_parsed_at":"2024-05-31T06:57:19.162Z","dependency_job_id":"47fa4f60-7f67-4a3e-ac9e-d223ba6dace0","html_url":"https://github.com/patternhelloworld/spring-oauth2-easyplus","commit_stats":null,"previous_names":["andrew-kang-g/spring-security-oauth2-password-jpa-implementation","patternknife/spring-security-oauth2-password-jpa-implementation","patternhelloworld/spring-security-oauth2-password-jpa-implementation","patternhelloworld/spring-oauth2-easyplus"],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/patternhelloworld/spring-oauth2-easyplus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patternhelloworld%2Fspring-oauth2-easyplus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patternhelloworld%2Fspring-oauth2-easyplus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patternhelloworld%2Fspring-oauth2-easyplus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patternhelloworld%2Fspring-oauth2-easyplus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/patternhelloworld","download_url":"https://codeload.github.com/patternhelloworld/spring-oauth2-easyplus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/patternhelloworld%2Fspring-oauth2-easyplus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29883111,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-26T23:51:21.483Z","status":"online","status_checked_at":"2026-02-27T02:00:06.759Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization-code-grant","jpa","jwt","oauth2","oauth2-password-flow","spring-authorization","spring-authorization-server","spring-security"],"created_at":"2025-07-09T09:22:09.713Z","updated_at":"2026-02-27T03:01:03.736Z","avatar_url":"https://github.com/patternhelloworld.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spring Oauth2 EasyPlus\r\n\r\n\u003e App-Token based easy OAuth2 implementation built to grow with Spring Boot\r\n\r\n## Table of Contents\r\n\r\n- [Quick Start](#quick-start)\r\n- [Features](#features)\r\n- [Dependencies](#dependencies)\r\n- [Run the App](#run-the-app)\r\n- [API Guide](#api-guide)\r\n    - [Registration](#registration)\r\n    - [Implementations](#implementations)\r\n        - [Mandatory Settings](#mandatory-settings)\r\n        - [Customizable Settings](#customizable-settings)\r\n- [OAuth2 - ROPC](#oauth2---ropc)\r\n- [OAuth2 - Authorization Code](#oauth2---authorization-code)\r\n- [Running this App with Docker](#running-this-app-with-docker)\r\n- [Contribution Guide](#contribution-guide)\r\n\r\n\r\n## Quick Start\r\n```xml\r\n\u003cdependency\u003e\r\n    \u003cgroupId\u003eio.github.patternhelloworld.securityhelper.oauth2.api\u003c/groupId\u003e\r\n    \u003cartifactId\u003espring-oauth2-easyplus\u003c/artifactId\u003e\r\n    \u003cversion\u003e4.4.1\u003c/version\u003e\r\n\u003c/dependency\u003e\r\n```\r\n\r\n\r\n## Features\r\n\r\n* Complete separation of the library and the client\r\n    * Library : API\r\n    * Client : DOC, Integration tester\r\n* Use JPA for various databases to gain full control over all tokens and permissions, unlike simple in-memory examples.\r\n* Extensible: Supports multiple authorization servers and resource servers with this library.\r\n* Hybrid Resource Servers Token Verification Methods: Support for multiple verification approaches, including API calls to the authorization server, direct database validation, and local JWT decoding.\r\n* Immediate Permission (Authority) Check: Not limited to verifying the token itself, but also ensuring real-time validation of any updates to permissions in the database.\r\n* Authentication management based on a combination of Username, client ID, and App-Token\r\n    * What is an App-Token?\r\n      * An App-Token is an additional token that serves as a unique identifier for each device. Unlike access tokens, it is not regenerated with each login. Instead, it uses a device-specific unique value, such as a GUID in Android, to control device-level authentication, even when the app is reinstalled. If the token values are the same, the same access token is shared.\r\n\r\n| App-Token Status       | Access Token Behavior      |\r\n|------------------------|----------------------------|\r\n| same for the same user | Access-Token is shared     |\r\n| different for the same user | Access-Token is NOT shared |\r\n\r\n* Set this in your ``application.properties``.\r\n    * App-Token Behavior Based on `io.github.patternhelloworld.securityhelper.oauth2.no-app-token-same-access-token`\r\n\r\n| `no-app-token-same-access-token` Value | App-Token Status                          | Access Token Sharing Behavior                                                                                     |\r\n|------------------------------------------------------------|-------------------------------------------|-------------------------------------------------------------------------------------------------------------------|\r\n| `true`                                                     | App-Token is `null` for the same user     | Same user with a `null` App-Token shares the same access token across multiple logins.                             |\r\n| `false`                                                    | App-Token is `null` for the same user                       | Even if the App-Token is `null`, the same user will receive a new access token for each login.                     |\r\n| `-`                                                        | App-Token is shared for the same user     | Access tokens will not be shared. A new access token is generated for each unique App-Token, even for the same user.|\r\n| `-`                                                        | App-Token is NOT shared for the same user | Each unique App-Token generates a new access token for the same user.                                              |\r\n\r\n\r\n* Separated UserDetails implementation for Admin and Customer roles as an example. (This can be extended such as Admin, Customer, Seller and Buyer... by implementing ``UserDetailsServiceFactory``)\r\n* Authorization Code Flow with Optional PKCE, Authorization Consent and Single Page Application (XMLHttpRequest)\r\n* ROPC for scenarios where accessing a browser screen on the server is either unavailable or impractical\r\n* Application of Spring Rest Docs, Postman payloads provided\r\n* Set up the same access \u0026 refresh token APIs on both ``/oauth2/token`` and on our controller layer such as ``/api/v1/traditional-oauth/token``, both of which function same and have `the same request \u0026 response payloads for success and errors`. (However, ``/oauth2/token`` is the standard that \"spring-authorization-server\" provides.)\r\n    * As you are aware, the API ``/oauth2/token``(Recommended) is what \"spring-authorization-server\" provides.\r\n        * ``/api/v1/traditional-oauth/token``(Easily Customizable) is what this library implemented directly.\r\n            * Success Payload\r\n             ```json\r\n              {\r\n                  \"access_token\" : \"Vd4x8D4lDg7VBFh...\",\r\n                  \"token_type\" : \"Bearer\",\r\n                  \"refresh_token\" : \"m3UgLrvPtXKdy7jiD...\",\r\n                  \"expires_in\" : 3469,\r\n                  \"scope\" : \"read write\"\r\n               }\r\n            ```\r\n\r\n            * Error Payload (Customizable)\r\n            ```json\r\n              {\r\n                  \"timestamp\": 1719470948370,\r\n                  \"message\": \"Couldn't find the client ID : client_admin\", // Sensitive info such as being thrown from StackTraces\r\n                  \"details\": \"uri=/oauth2/token\",\r\n                  \"userMessage\": \"Authentication failed. Please check your credentials.\",\r\n                  \"userValidationMessage\": null\r\n              }\r\n            ```\r\n\r\n            * In the following error payload, the 'message' shouldn't be exposed to clients; instead, the 'userMessage' should be.\r\n            * Definitely, you can customize the payload sent to the client by implementing AuthenticationFailureHandler.\r\n* See the sample folder ``com.patternhelloworld.securityhelper.oauth2.client.config.securityimpl`` to understand how to implement the library.\r\n\r\n## Dependencies\r\n\r\n| Category          | Dependencies                                                           |\r\n|-------------------|------------------------------------------------------------------------|\r\n| Backend-Language  | Java 17                                                                |\r\n| Backend-Framework | Spring Boot 3.3.2                                |\r\n| Main Libraries    | Spring Security 6.3.1, Spring Security Authorization Server 1.3.1, JPA |\r\n| Package-Manager   | Maven 3.6.3 (mvnw, Dockerfile)                                         |\r\n| RDBMS             | Mysql 8.0.17                                                           |\r\n\r\n- Requires selected dependencies for Spring Boot. Check the ``pom.xml`` or this link \"https://libraries.io/maven/io.github.patternhelloworld.securityhelper.oauth2.api:spring-oauth2-easyplus/4.4.1/tree\".\r\n\r\n## Run the App\r\n\r\n#### Import the SQL file in the ``mysql`` folder.\r\n- If you don't have a MySQL instance readily available, you can clone https://github.com/patternhelloworld/docker-mysql-8 .\r\n\r\n#### Install Maven\r\n```shell\r\n# Do NOT use your latest Maven version, but mvnw here or one with the same version.\r\ncd lib\r\nmvnw clean install\r\ncd ..\r\ncd client\r\nmvnw clean install # Integration tests are done here, which creates docs by Spring-Rest-Doc.\r\n```\r\n- Run the client module by running ``SpringSecurityOauth2PasswordJpaImplApplication`` in the client.\r\n- The API information is found on ``http://localhost:8370/docs/api-app.html``, managed by Spring Rest Doc\r\n\r\n![img.png](reference/docs/img1.png)\r\n\r\n- In case you use IntelliJ, I recommend creating an empty project and importing the API (root) module and client module separately.\r\n- The client module definitely consumes the API module, but not vice versa.\r\n\r\n## API Guide\r\n\r\n### **Registration**\r\n- See the `client` folder.\r\n- As the Api module consumes JPA, adding it to Beans is required.\r\n\r\n```java\r\n\r\n// Add 'io.github.patternhelloworld.securityhelper.oauth2.api'\r\n@SpringBootApplication(scanBasePackages =  {\"com.patternhelloworld.securityhelper.oauth2.client\", \"io.github.patternhelloworld.securityhelper.oauth2.api\"})\r\npublic class SpringSecurityOauth2PasswordJpaImplApplication {\r\n\r\n    public static void main(String[] args) {\r\n        SpringApplication.run(SpringSecurityOauth2PasswordJpaImplApplication.class, args);\r\n    }\r\n\r\n}\r\n```\r\n\r\n```java\r\n@Configuration\r\n// ADD 'io.github.patternhelloworld.securityhelper.oauth2.api.config.security'\r\n@EnableJpaRepositories(\r\n        basePackages = {\"com.patternhelloworld.securityhelper.oauth2.client.domain\",\r\n                \"com.patternhelloworld.securityhelper.oauth2.client.config.securityimpl\",\r\n                \"io.github.patternhelloworld.securityhelper.oauth2.api.config.security\"},\r\n        entityManagerFactoryRef = \"commonEntityManagerFactory\",\r\n        transactionManagerRef= \"commonTransactionManager\"\r\n)\r\npublic class CommonDataSourceConfiguration {\r\n    \r\n\r\n   // ADD 'io.github.patternhelloworld.securityhelper.oauth2.api.config.security'\r\n    @Primary\r\n    @Bean(name = \"commonEntityManagerFactory\")\r\n    public LocalContainerEntityManagerFactoryBean commonEntityManagerFactory(EntityManagerFactoryBuilder builder) {\r\n        return builder\r\n                .dataSource(commonDataSource())\r\n                .packages(\"com.patternhelloworld.securityhelper.oauth2.client.domain\",\r\n                        \"io.github.patternhelloworld.securityhelper.oauth2.api.config.security\")\r\n                .persistenceUnit(\"commonEntityManager\")\r\n                .build();\r\n    }\r\n\r\n}\r\n```\r\n\r\n### **Implementations**\r\n- As indicated, the ``client`` folder demonstrates how to use this library.\r\n\r\n#### \"Mandatory\" settings\r\n\r\n- The only mandatory setting is ``client.config.securityimpl.service.userdetail.CustomUserDetailsServiceFactory``. The rest depend on your specific situation.\r\n\r\n#### \"Customizable\" settings\r\n\r\n- **Insert your code when events happen such as tokens created**\r\n    - ``SecurityPointCut``\r\n    - See the source code in ``client.config.securityimpl.aop``\r\n\r\n\r\n- **Register error user messages as desired**\r\n    - ``ISecurityUserExceptionMessageService``\r\n    - See the source code in ``client.config.securityimpl.message``\r\n\r\n\r\n- **Customize the whole error payload as desired for all cases**\r\n    - What is \"all cases\"?\r\n        - Authorization Server (\"/oauth2/token\", \"/api/v1/traditional-oauth/token\") and Resource Server (Bearer token authentication : 401, authorization (permission) : 403)\r\n    - Customize errors of the following cases\r\n        - Login (/oauth2/token) : ``client.config.securityimpl.response.CustomAuthenticationFailureHandlerImpl``\r\n        - Login (/api/v1/traditional-oauth/token) : ``client.config.response.error.GlobalExceptionHandler.authenticationException`` (\"/api/v1/traditional-oauth/token\", Resource Server (Bearer token inspection))\r\n        - Resource Server (Bearer token expired or with a wrong value, 401) :``client.config.securityimpl.response.CustomAuthenticationEntryPointImpl``\r\n        - Resource Server (Permission, 403, @PreAuthorized on your APIs) ``client.config.response.error.GlobalExceptionHandler.authorizationException``\r\n\r\n- **Customize the whole success payload as desired for the only \"/oauth2/token\"**\r\n    - ``client.config.securityimpl.response.CustomAuthenticationSuccessHandlerImpl``\r\n    - The success response payload of \"/api/v1/traditional-oauth/token\" is in ``api.domain.traditionaloauth.dto`` and is not yet customizable.\r\n\r\n- **Customize the verification logic for UsernamePassword and Client as desired**\r\n    - ``IOauth2AuthenticationHashCheckService``\r\n\r\n- **Customize OpaqueTokenIntrospector as desired (!This is for Resource Servers)**\r\n    - ``client.config.securityimpl.introspector.CustomResourceServerTokenIntrospector``\r\n    - ```properties\r\n          # Introspection type configuration:\r\n          # - api: The Resource Server sends introspection requests to the Authorization Server.\r\n          #        Benefits: High scalability and real-time authorization checks.\r\n          #        Drawbacks: Increased traffic due to frequent API calls.\r\n          # \r\n          # - database: The Resource Server and Authorization Server share the same database.\r\n          #             Benefits: Minimal traffic and real-time authorization checks.\r\n          #             Drawbacks: Limited scalability due to direct database dependency.\r\n          # \r\n          # - decode: The Resource Server decodes the Access Token locally using the JWT algorithm.\r\n          #           Benefits: No traffic and high scalability.\r\n          #           Drawbacks: Lacks real-time authorization updates.\r\n          #\r\n          # [WARNING] api: Certain test cases are currently failing due to issues with the specified introspection URI calls.\r\n          patternhelloworld.securityhelper.oauth2.introspection.type=database\r\n          patternhelloworld.securityhelper.oauth2.introspection.uri=http://localhost:8370/oauth2/introspect\r\n          patternhelloworld.securityhelper.oauth2.introspection.client-id=client_customer\r\n          patternhelloworld.securityhelper.oauth2.introspection.client-secret=12345\r\n        ```\r\n## OAuth2 - ROPC\r\n* Refer to ``client/src/docs/asciidoc/api-app.adoc``\r\n\r\n## OAuth2 - Authorization Code\r\n- How to set it up\r\n    1. Create your own login page with the /login route as indicated in the client project (In the future, this address will be customisable):\r\n  ```java\r\n    @Controller\r\n    public class LoginWeb {\r\n        @GetMapping(\"/login\")\r\n        public String loginPage() {\r\n        return \"login\";\r\n        }\r\n    }\r\n  ```\r\n  ```properties\r\n    spring.mvc.view.prefix=/templates/\r\n    spring.mvc.view.suffix=.html\r\n  ```\r\n    2. Check the login page at the \"resources/templates/login.hml\"\r\n    3. Ensure the callback URL (http://localhost:8081/callback1) is properly set in the ``oauth2_registered_client`` table in the database.\r\n- How to use\r\n    1. Open the web browser by connecting to ``http://localhost:8370/oauth2/authorize?client_id=client_customer\u0026state=xxx\u0026scope=read\u0026redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Fcallback1\u0026code_challenge=HVoKJYs8JruAxs7hKcG4oLpJXCP-z1jJQtXpQte6GyA\u0026code_challenge_method=S256``, using the values from the ``oauth2_registered_client``\r\n        - PKCE (``code_challege, code_challege_METHOD``) is optional.\r\n        - PKCE adds a Code Verifier and a Code Challenge to the flow, enhancing the Authorization Code Grant Flow by preventing the issuance of an Access Token if the Authorization Code is compromised.\r\n    2. Login with ``cicd@test.com / 1234 ``\r\n    3. You will be redirected to \r\n       ``https://localhost:8081/callback1?code=215e9539-1dcb-4843-b1ea-b2d7be0a3c44\u0026state=xxx``\r\n       - However, if ``patternhelloworld.securityhelper.authorization-code.consent=Y``is set in the``application.properties``, it will be redirected to the consent page. \r\n    4. You can login with the API in the Postman\r\n        - ![img4.png](reference/docs/img.png)\r\n        - ``code_verifier`` sample : EAp91aanXdoMcoOc2Il55H3UDDIV909k9olEEcl6L24J6_9X\r\n\r\n## Running this App with Docker\r\n* Use the following module for Blue-Green deployment:\r\n    * https://github.com/patternhelloworld/docker-blue-green-runner\r\n* The above module references this app's Dockerfile and the entrypoint script in the .docker folder.\r\n\r\n## Contribution Guide\r\n* You can create a pull request directly to the main branch.\r\n* Integration tests in the client folder are sufficient for now, but you may add more if necessary.\r\n* There is a lack of unit tests, so contributions to unit test code are welcome, which will help improve the overall codebase.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatternhelloworld%2Fspring-oauth2-easyplus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpatternhelloworld%2Fspring-oauth2-easyplus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpatternhelloworld%2Fspring-oauth2-easyplus/lists"}