├── .editorconfig
├── .gitattributes
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── docker-compose.yml
├── pom.xml
└── src
└── main
├── docker
├── module.xml
└── startup-scripts
│ └── configure-email-provider.cli
├── java
└── com
│ └── github
│ └── conciso
│ └── keycloak
│ └── email
│ ├── EmailPrefixSenderProvider.java
│ └── EmailPrefixSenderProviderFactory.java
└── resources
└── META-INF
└── services
└── org.keycloak.email.EmailSenderProviderFactory
/.editorconfig:
--------------------------------------------------------------------------------
1 | root=true
2 | [*]
3 | charset=utf-8
4 | end_of_line=crlf
5 | insert_final_newline=true
6 | indent_style=space
7 | indent_size=2
8 | continuation_indent_size=4
9 | trim_trailing_whitespace=true
10 |
11 | [*.xml]
12 | indent_size=4
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default behavior, in case people don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Shell scripts need to always have nix line endings
5 | *.sh text eol=lf
6 | *.cli text eol=lf
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | pom.xml.tag
3 | pom.xml.releaseBackup
4 | pom.xml.versionsBackup
5 | pom.xml.next
6 | release.properties
7 | dependency-reduced-pom.xml
8 | buildNumber.properties
9 | .mvn/timing.properties
10 | .mvn/wrapper/maven-wrapper.jar
11 |
12 | # Windows thumbnail cache files
13 | Thumbs.db
14 | Thumbs.db:encryptable
15 | ehthumbs.db
16 | ehthumbs_vista.db
17 |
18 | # Dump file
19 | *.stackdump
20 |
21 | # Folder config file
22 | [Dd]esktop.ini
23 |
24 | # Recycle Bin used on file shares
25 | $RECYCLE.BIN/
26 |
27 | # Windows Installer files
28 | *.cab
29 | *.msi
30 | *.msix
31 | *.msm
32 | *.msp
33 |
34 | # Windows shortcuts
35 | *.lnk
36 |
37 |
38 | # General
39 | .DS_Store
40 | .AppleDouble
41 | .LSOverride
42 |
43 | # Icon must end with two \r
44 | Icon
45 |
46 |
47 | # Thumbnails
48 | ._*
49 |
50 | # Files that might appear in the root of a volume
51 | .DocumentRevisions-V100
52 | .fseventsd
53 | .Spotlight-V100
54 | .TemporaryItems
55 | .Trashes
56 | .VolumeIcon.icns
57 | .com.apple.timemachine.donotpresent
58 |
59 | # Directories potentially created on remote AFP share
60 | .AppleDB
61 | .AppleDesktop
62 | Network Trash Folder
63 | Temporary Items
64 | .apdisk
65 |
66 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
67 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
68 | .idea/
69 |
70 | # Gradle and Maven with auto-import
71 | # When using Gradle or Maven with auto-import, you should exclude module files,
72 | # since they will be recreated, and may cause churn. Uncomment if using
73 | # auto-import.
74 | *.iml
75 | *.ipr
76 |
77 | # CMake
78 | cmake-build-*/
79 |
80 | # File-based project format
81 | *.iws
82 |
83 | # IntelliJ
84 | out/
85 |
86 | # mpeltonen/sbt-idea plugin
87 | .idea_modules/
88 |
89 | # JIRA plugin
90 | atlassian-ide-plugin.xml
91 |
92 | # Crashlytics plugin (for Android Studio and IntelliJ)
93 | com_crashlytics_export_strings.xml
94 | crashlytics.properties
95 | crashlytics-build.properties
96 | fabric.properties
97 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | services:
3 | - docker
4 | sudo: false
5 | script: mvn clean verify
6 | jdk:
7 | - openjdk11
8 | cache:
9 | directories:
10 | - "$HOME/.m2"
11 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM jboss/keycloak:${version.keycloak}
2 |
3 | COPY target/classes/startup-scripts/* /opt/jboss/startup-scripts/
4 | COPY target/*.jar /opt/jboss/keycloak/modules/com/github/conciso/keycloak-spi-example/provider/main/
5 | COPY target/classes/module.xml /opt/jboss/keycloak/modules/com/github/conciso/keycloak-spi-example/provider/main/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Conciso GmbH
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Keycloak SPI example
2 |
3 | A simple example project for implementing Keycloak SPIs
4 |
5 | [](https://travis-ci.org/conciso/keycloak-spi-example)
6 |
7 | ## Webinar
8 |
9 | This demo is also available as a [free webinar](https://conciso.de/blog/2020/07/16/webinar-mit-spis-keycloak-erweitern/) (in German).
10 |
11 | ## Build
12 |
13 | For a build you need to have set up Maven, Java, and Docker.
14 |
15 | You can build this project with Maven running:
16 | ```bash
17 | mvn clean install
18 | ```
19 |
20 | ## Running the demo
21 |
22 | After you have build the Docker image, you may run the demo with Docker Compose from the root directory of the repository:
23 |
24 | ```bash
25 | docker-compose up
26 | ```
27 |
28 | This starts Keycloak and makes it available at [http://localhost:8080](http://localhost:8080).
29 | Use user `admin` and password `admin` for login.
30 |
31 | It also starts an additional container named `mail`. You may use it to check emails send by Keycloak. The web interface will be available at [http://localhost:9080](http://localhost:9080).
32 |
33 | NOTE: Make sure you have configured email settings in Keycloak correctly. Use `mail` as hostname and keep the port to `25` (default).
34 |
35 | ## Step by step
36 |
37 | * Step 1: [Register and enable provider](https://github.com/conciso/keycloak-spi-example/compare/demo-step-1...demo-step-2)
38 | * Step 2: [Add some logging to check if it works](https://github.com/conciso/keycloak-spi-example/compare/demo-step-2...demo-step-3)
39 | * Step 3: [Reuse default provider / Provider lookup](https://github.com/conciso/keycloak-spi-example/compare/demo-step-3...demo-step-4)
40 | * Step 4: [Configure provider](https://github.com/conciso/keycloak-spi-example/compare/demo-step-4...demo-step-5)
41 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | keycloak:
4 | container_name: keycloak
5 | image: conciso/keycloak-spi-example
6 | environment:
7 | KEYCLOAK_USER: admin
8 | KEYCLOAK_PASSWORD: admin
9 | SUBJECT_PREFIX: SSO
10 | ports:
11 | - 8080:8080
12 | mail:
13 | container_name: mail
14 | image: djfarrelly/maildev
15 | ports:
16 | - 9080:80
17 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.github.conciso
8 | keycloak-spi-example
9 | 0.0.1-SNAPSHOT
10 |
11 | Keycloak examples - SPI implementation
12 | An example for implementing an SPI in Keycloak
13 |
14 | 2020
15 |
16 | Conciso GmbH
17 | https://conciso.de
18 |
19 |
20 |
21 |
22 | sventorben
23 | Sven-Torben Janus
24 | sven-torben.janus@conciso.de
25 | https://github.com/sventorben
26 |
27 |
28 |
29 |
30 | https://github.com/conciso/keycloak-spi-example/issues
31 | GitHub Issues
32 |
33 |
34 |
35 | git@github.com:conciso/keycloak-spi-example.git
36 | scm:git:git://github.com/conciso/keycloak-spi-example.git
37 | scm:git:git@github.com:conciso/keycloak-spi-example.git
38 | HEAD
39 |
40 |
41 |
42 | UTF-8
43 | 11
44 | 11
45 | 10.0.2
46 |
47 |
48 |
49 |
50 |
51 | src/main/docker
52 | true
53 |
54 |
55 | src/main/resources
56 |
57 |
58 |
59 |
60 | org.apache.maven.plugins
61 | maven-compiler-plugin
62 | 3.8.1
63 |
64 |
65 | io.fabric8
66 | docker-maven-plugin
67 | 0.33.0
68 |
69 |
70 |
71 | conciso/${project.artifactId}:${project.version}
72 |
73 | ${project.basedir}
74 | true
75 |
76 | latest
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | package
85 |
86 | build
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | org.keycloak
97 | keycloak-core
98 | ${version.keycloak}
99 | provided
100 |
101 |
102 | org.keycloak
103 | keycloak-server-spi
104 | ${version.keycloak}
105 | provided
106 |
107 |
108 | org.keycloak
109 | keycloak-server-spi-private
110 | ${version.keycloak}
111 | provided
112 |
113 |
114 | org.keycloak
115 | keycloak-services
116 | ${version.keycloak}
117 | provided
118 |
119 |
120 |
121 | org.jboss.logging
122 | jboss-logging
123 | 3.4.1.Final
124 | provided
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/main/docker/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/main/docker/startup-scripts/configure-email-provider.cli:
--------------------------------------------------------------------------------
1 | embed-server --server-config=standalone-ha.xml --std-out=echo
2 |
3 | batch
4 |
5 | /subsystem=keycloak-server/:list-add(name=providers,value="module:com.github.conciso.keycloak-spi-example.provider")
6 |
7 | /subsystem=keycloak-server/spi=emailSender:add
8 | /subsystem=keycloak-server/spi=emailSender/provider=default:add(enabled=true)
9 | /subsystem=keycloak-server/spi=emailSender/provider=email-prefix:add(properties={subjectPrefix => "${env.SUBJECT_PREFIX:KEYCLOAK}:"},enabled=true)
10 | /subsystem=keycloak-server/spi=emailSender:write-attribute(name=default-provider, value=email-prefix)
11 |
12 | run-batch
13 |
14 | stop-embedded-server
15 |
--------------------------------------------------------------------------------
/src/main/java/com/github/conciso/keycloak/email/EmailPrefixSenderProvider.java:
--------------------------------------------------------------------------------
1 | package com.github.conciso.keycloak.email;
2 |
3 | import org.jboss.logging.Logger;
4 | import org.keycloak.email.DefaultEmailSenderProviderFactory;
5 | import org.keycloak.email.EmailException;
6 | import org.keycloak.email.EmailSenderProvider;
7 | import org.keycloak.models.KeycloakSession;
8 | import org.keycloak.models.UserModel;
9 |
10 | import java.util.Map;
11 |
12 | public class EmailPrefixSenderProvider implements EmailSenderProvider {
13 |
14 | private static final Logger logger = Logger.getLogger(EmailPrefixSenderProvider.class);
15 |
16 | private final KeycloakSession session;
17 | private final String subjectPrefix;
18 |
19 | EmailPrefixSenderProvider(KeycloakSession session, String subjectPrefix) {
20 | this.session = session;
21 | this.subjectPrefix = subjectPrefix;
22 | }
23 |
24 | public void send(Map config, UserModel user, String subject, String textBody, String htmlBody) throws EmailException {
25 | String prefixedSubject = subjectPrefix + subject;
26 | logger.infov("Sending email with prefixed subject: {0}", prefixedSubject);
27 | EmailSenderProvider defaultProvider = new DefaultEmailSenderProviderFactory().create(session);
28 | /*
29 | You can also lookup other providers from the session.
30 |
31 | ATTENTION: Looking up the default provider does not seem to work as of time writing this example.
32 | For details see: https://groups.google.com/forum/#!searchin/keycloak-dev/sven-torben%7Csort:date/keycloak-dev/HBJZCwdMsMk/phDSonVvAgAJ
33 |
34 | EmailSenderProvider defaultProvider = session.getProvider(EmailSenderProvider.class, "default");
35 | */
36 | defaultProvider.send(config, user, prefixedSubject, textBody, htmlBody);
37 | }
38 |
39 | public void close() {
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/github/conciso/keycloak/email/EmailPrefixSenderProviderFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.conciso.keycloak.email;
2 |
3 | import org.jboss.logging.Logger;
4 | import org.keycloak.Config;
5 | import org.keycloak.email.EmailSenderProvider;
6 | import org.keycloak.email.EmailSenderProviderFactory;
7 | import org.keycloak.models.KeycloakSession;
8 | import org.keycloak.models.KeycloakSessionFactory;
9 | import org.keycloak.provider.ServerInfoAwareProviderFactory;
10 |
11 | import java.util.Map;
12 |
13 | public class EmailPrefixSenderProviderFactory implements EmailSenderProviderFactory, ServerInfoAwareProviderFactory {
14 |
15 | private static final Logger logger = Logger.getLogger(EmailPrefixSenderProviderFactory.class);
16 | private static final String CONFIG_SUBJECT_PREFIX = "subjectPrefix";
17 |
18 | private Config.Scope config;
19 |
20 | public EmailSenderProvider create(KeycloakSession session) {
21 | String subjectPrefix = config.get(CONFIG_SUBJECT_PREFIX);
22 | return new EmailPrefixSenderProvider(session, subjectPrefix);
23 | }
24 |
25 | public void init(Config.Scope config) {
26 | this.config = config;
27 | }
28 |
29 | public void postInit(KeycloakSessionFactory factory) {
30 | logger.infov("{0} initialzed", getClass().getSimpleName());
31 | }
32 |
33 | public void close() {
34 | logger.debugv("{0} closed", getClass().getSimpleName());
35 | }
36 |
37 | public String getId() {
38 | return "email-prefix";
39 | }
40 |
41 | public Map getOperationalInfo() {
42 | String prefix = config.get(CONFIG_SUBJECT_PREFIX, "");
43 | return Map.of(CONFIG_SUBJECT_PREFIX, prefix);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/org.keycloak.email.EmailSenderProviderFactory:
--------------------------------------------------------------------------------
1 | com.github.conciso.keycloak.email.EmailPrefixSenderProviderFactory
2 |
--------------------------------------------------------------------------------