{"id":20673231,"url":"https://github.com/coder966/spring-boot-starter-multi-security-realms","last_synced_at":"2026-06-11T19:31:10.626Z","repository":{"id":213858989,"uuid":"732492621","full_name":"coder966/spring-boot-starter-multi-security-realms","owner":"coder966","description":"Support multiple security realms in a single Spring Boot application","archived":false,"fork":false,"pushed_at":"2025-11-21T22:03:31.000Z","size":993,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-21T23:23:40.828Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/coder966.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2023-12-16T21:15:38.000Z","updated_at":"2025-11-21T22:03:34.000Z","dependencies_parsed_at":"2023-12-30T00:33:32.839Z","dependency_job_id":"bf32764d-f6e4-4c7f-a7ba-adf63397003c","html_url":"https://github.com/coder966/spring-boot-starter-multi-security-realms","commit_stats":null,"previous_names":["coder966/spring-boot-starter-multi-security-realms"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/coder966/spring-boot-starter-multi-security-realms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder966%2Fspring-boot-starter-multi-security-realms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder966%2Fspring-boot-starter-multi-security-realms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder966%2Fspring-boot-starter-multi-security-realms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder966%2Fspring-boot-starter-multi-security-realms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coder966","download_url":"https://codeload.github.com/coder966/spring-boot-starter-multi-security-realms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder966%2Fspring-boot-starter-multi-security-realms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34215253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","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":[],"created_at":"2024-11-16T20:40:34.361Z","updated_at":"2026-06-11T19:31:10.613Z","avatar_url":"https://github.com/coder966.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spring Multi Security Realms\n\n[![Maven Central](https://img.shields.io/maven-central/v/net.coder966.spring/spring-boot-starter-multi-security-realms)](https://central.sonatype.com/artifact/net.coder966.spring/spring-boot-starter-multi-security-realms)\n\nSupport multiple security realms in a single Spring Boot application.\n\n## What is a Security Realm\n\nA realm is a scope of operations. A security realm is a security scope which defines protected resources and users in that realm.\n\nFor example, suppose you have a multi-tenant online e-store application. This application probably support these types of users:\n\n- admin users / helpdesk users (realm)\n- store owner users (realm)\n- store customer users (realm)\n\nThese different user types are probably authenticated (login mechanism/flow/steps) and authorized (protected APIs) differently.\nConfiguring this in Spring can be tricky and a bit complicated. You can even potentially introduce security bugs if you try to implement these features\nmanually.\n\n## Multi-Factor Authentication (MFA) / Multi-Step Authentication\n\n#### For example: username \u0026 password step, then OTP step, etc...\n\nOften it's hard to implement MFA securely. Some issues we let you avoid when using this library:\n- Protect against user jumping steps (directly calling the final step api).\n- Protect against completed-step data being sent for subsequent step api call.\n  For example, we don't need and shouldn't send username \u0026 password to the otp step. This is often a discouraged approach, which we handle for you.\n- Stateless, no need to keep track of user current step in your db or any persistence layer.\n\n## Other Features\nIn addition to supporting multiple realms and MFA, we also provide these features:\n\n- Declarative approach using annotations. No manual configurations.\n- Ability to define public apis directly using `@AnonymousAccess`.\n  - Just annotate your `@GetMapping` or similar methods with `@AnonymousAccess` and you are good to go.\n  - No need to get your hands dirty with `SecurityFilterChain`.\n- You still have full control and can define custom `SecurityFilterChain`s if you wish.\n  By default, this library creates a default `SecurityFilterChain` and injects the multi realm support into it.\n\n## Usage\n\n### Requirements\n\n- Spring Boot \u003e= 3.x.x || 4.x.x\n\n### Installation\n\nMaven:\n\n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003enet.coder966.spring\u003c/groupId\u003e\n    \u003cartifactId\u003espring-boot-starter-multi-security-realms\u003c/artifactId\u003e\n    \u003cversion\u003e0.5.3\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nGradle:\n\n```groovy\nimplementation 'net.coder966.spring:spring-boot-starter-multi-security-realms:0.5.3'\n```\n\n## Usage\n\n### Define security realms\n\nTo define a realm, simply create a Spring component and annotate it with `@SecurityRealm`.\nHere in this example, we define two realms (normal-user \u0026 admin-user).\n\n#### NormalUserSecurityRealm.java\n\n```java\nimport java.time.Duration;\n\n@Slf4j\n@SecurityRealm(\n        name = \"NORMAL_USER\",\n        authenticationEndpoint = \"/normal-user/auth\",\n        firstStepName = StepNames.USERNAME_AND_PASSWORD\n        //    signingSecret = \"\", // not specified, will use default configured under security-realm.*\n        //    fullyAuthenticatedTokenTtl = \"\" // not specified, will use default configured under security-realm.*\n)\npublic class NormalUserSecurityRealm {\n\n    @Autowired\n    private NormalUserRepo normalUserRepo;\n\n    @Transactional\n    @AuthenticationStep(StepNames.USERNAME_AND_PASSWORD)\n    public SecurityRealmAuthentication firstAuthenticationStep(@RequestBody AuthUsernameAndPasswordStepRequest request) {\n        Optional\u003cNormalUser\u003e optionalUser = normalUserRepo.findByUsername(request.getUsername());\n        if (optionalUser.isEmpty()) {\n            // Error description (the second argument) is optional\n            throw new SecurityRealmAuthenticationException(ErrorCodes.BAD_CREDENTIALS, \"Username or password is incorrect\");\n        }\n        NormalUser user = optionalUser.get();\n\n        // WARNING: FOR DEMO PURPOSE ONLY\n        if (!user.getPassword().equals(request.getPassword())) {\n            // Since error description (the second argument to SecurityRealmAuthenticationException) is optional, we skipped it\n            throw new SecurityRealmAuthenticationException(ErrorCodes.BAD_CREDENTIALS);\n        }\n\n        // TODO: send otp to mobile\n        String otp = \"1234\"; // random\n        user.setOtp(otp);\n        user = normalUserRepo.save(user);\n\n        // here we specify the next step name and the temp token ttl (not fully authenticated, there is still a next step, so the ttl here is 5 minutes)\n        // if this is the final step, then use the overloaded constructor SecurityRealmAuthentication(name, authorities) which does not take in token ttl, because that is specified at the realm level\n        return new SecurityRealmAuthentication(user.getUsername(), null, StepNames.OTP, Duration.ofMinutes(5));\n    }\n\n    @Transactional\n    @AuthenticationStep(StepNames.OTP)\n    public SecurityRealmAuthentication otpAuthenticationStep(@RequestBody AuthOtpStepRequest request) {\n        SecurityRealmAuthentication previousStepAuth = (SecurityRealmAuthentication) SecurityContextHolder.getContext().getAuthentication();\n\n        String otp = request.getOtp();\n\n        NormalUser user = normalUserRepo.findByUsername(previousStepAuth.getName()).get();\n\n        if (!user.getOtp().equals(otp)) {\n            throw new SecurityRealmAuthenticationException(ErrorCodes.BAD_OTP);\n        }\n\n        // clear otp\n        user.setOtp(null);\n        user = normalUserRepo.save(user);\n\n        return new SecurityRealmAuthentication(user.getUsername(), null);\n    }\n}\n```\n\n#### AdminUserSecurityRealm.java\n\n```java\n@Slf4j\n@SecurityRealm(\n        name = \"ADMIN_USER\",\n        authenticationEndpoint = \"/admin-user/auth\",\n        firstStepName = StepNames.USERNAME_AND_PASSWORD,\n        signingSecret = \"${my-app.admin-realm-jwt-secret}\",\n        fullyAuthenticatedTokenTtl = \"5m\" // 5 minutes\n)\npublic class AdminUserSecurityRealm {\n\n    @Autowired\n    private AdminUserRepo adminUserRepo;\n\n    @Transactional\n    @AuthenticationStep(StepNames.USERNAME_AND_PASSWORD)\n    public SecurityRealmAuthentication firstAuthenticationStep(@RequestBody AuthUsernameAndPasswordStepRequest request) {\n        Optional\u003cAdminUser\u003e optionalUser = adminUserRepo.findByUsername(request.getUsername());\n        if (optionalUser.isEmpty()) {\n            throw new SecurityRealmAuthenticationException(ErrorCodes.BAD_CREDENTIALS);\n        }\n        AdminUser user = optionalUser.get();\n\n        // WARNING: FOR DEMO PURPOSE ONLY\n        if (!user.getPassword().equals(request.getPassword())) {\n            throw new SecurityRealmAuthenticationException(ErrorCodes.BAD_CREDENTIALS);\n        }\n\n        // TODO: send otp to mobile\n        String otp = \"1234\"; // random\n        user.setOtp(otp);\n        user = adminUserRepo.save(user);\n\n        // here we specify the next step name and the temp token ttl (not fully authenticated, there is still a next step, so the ttl here is 5 minutes)\n        // if this is the final step, then use the overloaded constructor SecurityRealmAuthentication(name, authorities) which does not take in token ttl, because that is specified at the realm level\n        return new SecurityRealmAuthentication(user.getUsername(), null, StepNames.OTP, Duration.ofMinutes(5));\n    }\n\n    @Transactional\n    @AuthenticationStep(StepNames.OTP)\n    public SecurityRealmAuthentication otpAuthenticationStep(@RequestBody AuthOtpStepRequest request) {\n        SecurityRealmAuthentication previousStepAuth = (SecurityRealmAuthentication) SecurityContextHolder.getContext().getAuthentication();\n\n        String otp = request.getOtp();\n\n        AdminUser user = adminUserRepo.findByUsername(previousStepAuth.getName()).get();\n\n        if (!user.getOtp().equals(otp)) {\n            throw new SecurityRealmAuthenticationException(ErrorCodes.BAD_OTP);\n        }\n\n        // clear otp\n        user.setOtp(null);\n        user = adminUserRepo.save(user);\n\n        return new SecurityRealmAuthentication(user.getUsername(), null);\n    }\n}\n```\n\n### Client Application (Frontend)\n\n- The client app should call the realm authentication endpoint.\n- You will receive an access token in the response body field `token`, store it and pass it to all subsequent requests in the `Authorization` header.\n- If the realm requires additional authentication steps from you (MFA),\n  you will see the required authentication step name in the response body field `nextAuthenticationStep`. Redirect the user or\n  render this step form and again submit to the same authentication endpoint.\n- In any case, if there is an error in the authentication (for example, bad credentials, bad otp, etc...), you will receive the error in the response body field\n  `error`, and the HTTP status will be `400`.\n\n#### Sample Response (success)\n\nRendered username+password form and submitted, and got:\n\n```json\n{\n    \"name\": \"khalid\",\n    \"authorities\": [],\n    \"nextAuthenticationStep\": \"OTP\",\n    \"realm\": \"ADMIN_USER\",\n    \"token\": \"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJraGFsaWQiLCJyZWFsbSI6IkFETUlOX1VTRVIiLCJuZXh0QXV0aGVudGljYXRpb25TdGVwIjoiT1RQIiwiYXV0aG9yaXRpZXMiOltdLCJleHAiOjE3NDYxMzcwMDN9.T-C2LO5DmawUXG6XuhyqTH9hxc8VIE4nF1u2_u2a_Xqw4SRbMpJ7Aq--AwcEA-jzSj6Si9_O1V21P-mkKU31FQ\",\n    \"tokenType\": \"Bearer\",\n    \"expiresInSeconds\": 300,\n    \"extras\": {}\n}\n```\n\nRendered OTP form and submitted again, and got:\n\n```json\n{\n    \"name\": \"khalid\",\n    \"authorities\": [],\n    \"nextAuthenticationStep\": null,\n    \"realm\": \"ADMIN_USER\",\n    \"token\": \"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJraGFsaWQiLCJyZWFsbSI6IkFETUlOX1VTRVIiLCJuZXh0QXV0aGVudGljYXRpb25TdGVwIjpudWxsLCJhdXRob3JpdGllcyI6W10sImV4cCI6MTc0NjEzNzA3OX0.OYwacoHwO6iS-t3JXe0Fw0xKMIjBTypaasNJIghrdPW9RZMGzaghxCw1GYSz5p6E7c8dIubLKkvRf-QAhGIxVA\",\n    \"tokenType\": \"Bearer\",\n    \"expiresInSeconds\": 300,\n    \"extras\": {}\n}\n```\n\nTypescript:\n\n```typescript\ntype SuccessResponse = {\n    realm: string\n    token: string\n    tokenType: string\n    expiresInSeconds: number\n    name: string\n    authorities: any[]\n    nextAuthenticationStep: string\n    extras: Record\u003cstring, any\u003e\n}\n```\n\n#### Sample Response (error) status = 400\n\n```json\n{\n    \"error\": \"BAD_CREDENTIALS\",\n    \"errorDescription\": \"Username or password is incorrect\"\n}\n```\n\nTypescript:\n\n```typescript\ntype ErrorResponse = {\n    error: string\n    errorDescription: string\n}\n```\n\n#### Full Sample Client (React App NextJS)\n\n```tsx\n'use client'\n\nimport {RruForm, RruTextInput} from \"react-rich-ui\";\nimport validationSchemas from \"@/utils/validationSchemas\";\nimport * as yup from \"yup\";\nimport React, {useEffect} from \"react\";\nimport Image from \"next/image\";\nimport LoadingService from \"@/service/LoadingService\";\nimport {useRouter} from \"next/navigation\";\nimport AuthApis, {AuthenticationStep} from \"@/client/AuthApis\";\nimport DialogService from \"@/service/DialogService\";\n\nexport default function Login() {\n    const router = useRouter();\n    const [step, setStep] = React.useState\u003cAuthenticationStep\u003e('USERNAME_PASSWORD');\n\n    useEffect(() =\u003e {\n        if (!step) {\n            router.replace(\"/protected\");\n        }\n    }, [step]);\n\n    const usernamePasswordFormSchema = yup.object().shape({\n        username: validationSchemas.username(true),\n        password: validationSchemas.password(true),\n    })\n\n    const otpFormSchema = yup.object().shape({\n        otp: validationSchemas.otp(true),\n    })\n\n    const onSubmitUsernamePassword = async (form) =\u003e {\n        try {\n            LoadingService.start();\n            const res = await AuthApis.usernamePassword(form.username, form.password);\n            localStorage.setItem(\"token\", res.token);\n            setStep(res.nextAuthenticationStep);\n        } catch (error) {\n            DialogService.showError(error);\n        } finally {\n            LoadingService.stop();\n        }\n    }\n\n    const onSubmitOtp = async (form) =\u003e {\n        try {\n            LoadingService.start();\n            const res = await AuthApis.otp(form.otp);\n            localStorage.setItem(\"token\", res.token);\n            setStep(res.nextAuthenticationStep);\n        } catch (error) {\n            DialogService.showError(error);\n        } finally {\n            LoadingService.stop();\n        }\n    }\n\n    return (\n            \u003cdiv className=\"container d-flex align-items-center justify-content-center min-vh-100\"\u003e\n                \u003cdiv className=\"w-100\" style={{maxWidth: '400px'}}\u003e\n\n                    \u003cImage src={'/images/logo.svg'} width={70} height={70} className=\"ms-auto me-auto mb-4\" alt={'logo'}/\u003e\n\n                    {step === 'USERNAME_PASSWORD' \u0026\u0026 (\n                            \u003cRruForm onSubmit={onSubmitUsernamePassword} yupValidationSchema={usernamePasswordFormSchema}\u003e\n                                \u003cdiv className=\"mb-3\"\u003e\n                                    \u003cRruTextInput name={'username'} label={'Username'} autoComplete={'username'} requiredAsterisk={true}/\u003e\n                                \u003c/div\u003e\n                                \u003cdiv className=\"mb-3\"\u003e\n                                    \u003cRruTextInput name={'password'} label={'Password'} autoComplete={'password'} requiredAsterisk={true}/\u003e\n                                \u003c/div\u003e\n                                \u003cbutton type=\"submit\" className=\"btn btn-primary w-100\"\u003eLogin\u003c/button\u003e\n                            \u003c/RruForm\u003e\n                    )}\n\n                    {step === 'OTP' \u0026\u0026 (\n                            \u003cRruForm onSubmit={onSubmitOtp} yupValidationSchema={otpFormSchema}\u003e\n                                \u003cdiv className=\"mb-3\"\u003e\n                                    \u003cRruTextInput name={'otp'} label={'OTP'} autoComplete={'otp'} requiredAsterisk={true} maxLength={4}/\u003e\n                                \u003c/div\u003e\n                                \u003cbutton type=\"submit\" className=\"btn btn-primary w-100\"\u003eLogin\u003c/button\u003e\n                            \u003c/RruForm\u003e\n                    )}\n\n                \u003c/div\u003e\n            \u003c/div\u003e\n    );\n}\n```\n\n### Realm Protected APIs\n\nTo protect an api so that it can only be used by a certain realm users, you can use `@PreAuthorize(\"permitRealm('\u003crealm-role-name\u003e')\")`.\n\nExample:\n\n```java\n// adding this here, will apply it for all the endpoints in this controller\n@PreAuthorize(\"permitRealm('ADMIN_USER')\")\n@RestController\npublic class AdminUserController {\n\n    // OR it can be defined here at the method level\n    @PreAuthorize(\"permitRealm('ADMIN_USER')\")\n    @GetMapping(\"/admin-user/my-name\")\n    public String myName() {\n        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();\n        return authentication.getName(); // username\n    }\n\n}\n```\n\n### Public APIs\n\nTo indicate an api is public and can be accessed without authentication, use `@AnonymousAccess`.\n\n#### Example:\n\n```java\n@RestController\n@RequestMapping(\"/lookup\")\npublic class PublicController {\n\n    @AnonymousAccess\n    @GetMapping(\"/cities\")\n    public List\u003cString\u003e getCities() {\n        return \"This endpoint is open to everyone.\";\n    }\n}\n```\n\n#### Note:\nPlease note that this annotation is to bypass authentication not authorization, so in order for this to work, you should NOT annotate the controller with `@PreAuthorize`. \nException is `@PreAuthorize(\"permitRealm('sample-realm')\")` as we handle this case as it is a widespread case.\nOnly define authorization rules on the method level, otherwise the public api should be defined in another controller where there are no authorization rules defined at the controller level.\n\n## Tips\n\n### Configure JWT secret and TTL\n\nYou can specify a different signing secret and a TTL for each realm separately (using the `@SecurityRealm` annotation).\nYou can also specify global values using the configuration properties:\n\n- `security-realm.signing-secret`\n- `security-realm.fully-authenticated-token-ttl`\n\n### Pass extra data to the response in success authentication\n\nYou can put extra data (key-value pairs) in the authentication object, which will appear in the authentication response under the key `extras`.\n\n#### Example code\n\n```java\n\n@AuthenticationStep(Constants.StepNames.OTP)\npublic SecurityRealmAuthentication otpAuthenticationStep(@RequestBody AuthOtpStepRequest request) {\n    SecurityRealmAuthentication previousStepAuth = (SecurityRealmAuthentication) SecurityContextHolder.getContext().getAuthentication();\n\n    // code\n\n    return new SecurityRealmAuthentication(user.getUsername(), null).addExtra(\"countBadges\", user.getBadges().size());\n}\n```\n\n#### Example response\n\n```json\n{\n    \"realm\": \"ADMIN_USER\",\n    \"token\": \"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJyZWFsbSI6IkFETUlOX1VTRVIiLCJzdWIiOiJraGFsaWQiLCJhdXRob3JpdGllcyI6W10sIm5leHRBdXRoZW50aWNhdGlvblN0ZXAiOm51bGwsImV4cCI6MTc0NjI5MjU4MX0.JWxJKEU5mOsDA4czZV1ZdpxqLpNefrkeVmc-wmb3r4YmT5pVS3EBJi8jW-0ohne7q8VsQ5WYx4e3vW0OIgw4ig\",\n    \"tokenType\": \"Bearer\",\n    \"expiresInSeconds\": 300,\n    \"name\": \"khalid\",\n    \"authorities\": [],\n    \"nextAuthenticationStep\": null,\n    \"extras\": {\n        \"countBadges\": 0\n    }\n}\n```\n\n### I want to define my own `SecurityFilterChain`\n\nIf you want to define a custom `SecurityFilterChain` then you need to add this filter `MultiSecurityRealmAuthenticationFilter`\nbefore `AnonymousAuthenticationFilter`.\n\n```java\n\n@Slf4j\n@Configuration\n@EnableMethodSecurity\n@RequiredArgsConstructor\npublic class SecurityConfig {\n\n    @Bean\n    protected SecurityFilterChain globalSecurityFilterChain(\n            HttpSecurity http,\n            MultiSecurityRealmAuthenticationFilter multiSecurityRealmAuthenticationFilter // inject this filter\n    ) throws Exception {\n\n        // this is optional. If you don't have a custom SecurityFilterChain then you don't need to do all of this\n        // A default SecurityFilterChain is configured out of the box.\n\n        // add it before AnonymousAuthenticationFilter\n        http.addFilterBefore(multiSecurityRealmAuthenticationFilter, AnonymousAuthenticationFilter.class);\n\n\n        // session should be disabled\n        http.sessionManagement(configurer -\u003e configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));\n\n\n        // disable csrf, because JWT token is not stored in the cookies, so CSRF protection is not needed\n        // you can still enable it, but you have to support it in your client application (frontend)\n        http.csrf(AbstractHttpConfigurer::disable);\n\n\n        // the reset of your configuration ....\n\n        return http.build();\n    }\n\n}\n```\n\n### WebSocket Support\n\nIf you have a websocket endpoint configured in your application, you have two options to connect to it:\n- If the websocket should be available for anonymous users, make the endpoint open (as explained above).\n- If the websocket should be available for authorized users only, then pass the token as a query parameter in the ws connection url. The parameter name could either be `Authorization` or `token`. Please note that this is case-sensitive.\n  - Example 1: `ws://localhost:8080/ws?Authorization=eyJhbG.......`\n  - Example 2: `ws://localhost:8080/ws?token=eyJhbG.......`\n\n#### Special note on websocket security:\nThis library only supports authentication and authorization for the initial websocket hande-shake.\nIf you need to handle security for STOMP protocol (SUBSCRIBE/SEND), you need to configure your app accordingly.\nYou can still access `SecurityContextHolder` which will be populated by this library.\n\n\n## License\n\n```txt\nCopyright 2023 Khalid H. Alharisi\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder966%2Fspring-boot-starter-multi-security-realms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoder966%2Fspring-boot-starter-multi-security-realms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder966%2Fspring-boot-starter-multi-security-realms/lists"}