Git Product home page Git Product logo

ivangfr / springboot-keycloak-openldap Goto Github PK

View Code? Open in Web Editor NEW
139.0 10.0 63.0 3.6 MB

The goal of this project is to create a simple Spring Boot REST API, called simple-service, and secure it with Keycloak. Furthermore, the API users will be loaded into Keycloak from OpenLDAP server.

Java 55.66% Shell 44.34%
spring-boot keycloak openldap graalvm oauth2-resource-server docker java phpldapadmin springdoc-openapi ldap

springboot-keycloak-openldap's Introduction

springboot-keycloak-openldap

The goal of this project is to create a simple Spring Boot REST API, called simple-service, and secure it with Keycloak. Furthermore, the API users will be loaded into Keycloak from OpenLDAP server.

Note: In springboot-react-keycloak repository, we have implemented a movies-app using Keycloak (with PKCE). This application consists of two services: the backend that was implemented using Spring Boot and the frontend implemented with ReactJS.

Proof-of-Concepts & Articles

On ivangfr.github.io, I have compiled my Proof-of-Concepts (PoCs) and articles. You can easily search for the technology you are interested in by using the filter. Who knows, perhaps I have already implemented a PoC or written an article about what you are looking for.

Additional Readings

Project Diagram

project-diagram

Application

  • simple-service

    Spring Boot Web Java application that exposes the following endpoints:

    • GET /api/public: it's a not secured endpoint, everybody can access it;
    • GET /api/private: it's a secured endpoint, only accessible by users that provide a JWT access token issued by Keycloak and the token must contain the role USER;
    • GET /actuator/*: they are not secured endpoint, used to expose operational information about the application.

Prerequisites

Start Environment

  • Open a terminal and inside springboot-keycloak-openldap root folder run

    docker compose up -d
    
  • Wait for Docker containers to be up and running. To check it, run

    docker compose ps
    

Import OpenLDAP Users

The LDIF file that we will use, springboot-keycloak-openldap/ldap/ldap-mycompany-com.ldif, contains a pre-defined structure for mycompany.com. Basically, it has 2 groups (developers and admin) and 4 users (Bill Gates, Steve Jobs, Mark Cuban and Ivan Franchin). Besides, it's defined that Bill Gates, Steve Jobs and Mark Cuban belong to developers group and Ivan Franchin belongs to admin group.

Bill Gates > username: bgates, password: 123
Steve Jobs > username: sjobs, password: 123
Mark Cuban > username: mcuban, password: 123
Ivan Franchin > username: ifranchin, password: 123

There are two ways to import those users: running a script or using phpldapadmin website

Running a script

  • In a terminal and inside springboot-keycloak-openldap root folder run

    ./import-openldap-users.sh
    
  • The command below can be used to check the users imported

    ldapsearch -x -D "cn=admin,dc=mycompany,dc=com" \
      -w admin -H ldap://localhost:389 \
      -b "ou=users,dc=mycompany,dc=com" \
      -s sub "(uid=*)"
    

Using phpldapadmin website

  • Access https://localhost:6443

  • Login with the credentials

    Login DN: cn=admin,dc=mycompany,dc=com
    Password: admin
    
  • Import the file springboot-keycloak-openldap/ldap/ldap-mycompany-com.ldif

  • You should see a tree like the one shown in the picture below

    phpldapadmin

Configure Keycloak

There are two ways: running a script or using Keycloak website

Running a script

  • In a terminal, make sure you are inside springboot-keycloak-openldap root folder

  • Run the script below to configure Keycloak for simple-service application

    ./init-keycloak.sh
    

    It creates company-services realm, simple-service client, USER client role, ldap federation and the users bgates and sjobs with the role USER assigned.

  • Copy SIMPLE_SERVICE_CLIENT_SECRET value that is shown at the end of the script. It will be needed whenever we call Keycloak to get a JWT access token to access simple-service

Using Keycloak website

Please, have a look at this Medium article, Setting Up OpenLDAP With Keycloak For User Federation

Run simple-service using Maven

  • Open a new terminal and make sure you are in springboot-keycloak-openldap root folder

  • Start the application by running the following command

    ./mvnw clean spring-boot:run --projects simple-service -Dspring-boot.run.jvmArguments="-Dserver.port=9080"
    

Test using curl

  1. Open a new terminal

  2. Call the endpoint GET /api/public

    curl -i http://localhost:9080/api/public
    

    It should return

    HTTP/1.1 200
    It is public.
    
  3. Try to call the endpoint GET /api/private without authentication

    curl -i http://localhost:9080/api/private
    

    It should return

    HTTP/1.1 401
    
  4. Create an environment variable that contains the Client Secret generated by Keycloak to simple-service at Configure Keycloak step

    SIMPLE_SERVICE_CLIENT_SECRET=...
    
  5. Run the command below to get an access token for bgates user

    BGATES_ACCESS_TOKEN=$(curl -s -X POST \
      "http://localhost:8080/realms/company-services/protocol/openid-connect/token" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "username=bgates" \
      -d "password=123" \
      -d "grant_type=password" \
      -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
      -d "client_id=simple-service" | jq -r .access_token)
    

    Note: In jwt.io, you can decode and verify the JWT access token

  6. Call the endpoint GET /api/private

    curl -i http://localhost:9080/api/private -H "Authorization: Bearer $BGATES_ACCESS_TOKEN"
    

    It should return

    HTTP/1.1 200
    bgates, it is private.
    
  7. The access token default expiration period is 5 minutes. So, wait for this time and, using the same access token, try to call the private endpoint.

    It should return

    HTTP/1.1 401
    WWW-Authenticate: Bearer realm="company-services", error="invalid_token", error_description="Token is not active"
    

Test using Swagger

  1. Access http://localhost:9080/swagger-ui.html

    simple-service-swagger

  2. Click GET /api/public to open it. Then, click Try it out button and, finally, click Execute button

    It should return

    Code: 200
    Response Body: It is public.
    
  3. Now click GET /api/private secured endpoint. Let's try it without authentication. Then, click Try it out button and, finally, click Execute button

    It should return

    Code: 401
    Details: Error: response status is 401
    
  4. In order to access the private endpoint, you need an access token. So, open a terminal

  5. Create an environment variable that contains the Client Secret generated by Keycloak to simple-service at Configure Keycloak step

    SIMPLE_SERVICE_CLIENT_SECRET=...
    
  6. Run the following commands

    BGATES_ACCESS_TOKEN=$(curl -s -X POST \
      "http://localhost:8080/realms/company-services/protocol/openid-connect/token" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "username=bgates" \
      -d "password=123" \
      -d "grant_type=password" \
      -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
      -d "client_id=simple-service" | jq -r .access_token)
      
    echo $BGATES_ACCESS_TOKEN
    
  7. Copy the token generated and go back to Swagger

  8. Click Authorize button and paste the access token in the Value field. Then, click Authorize button and, to finalize, click Close

  9. Go to GET /api/private and call this endpoint again, now with authentication

    It should return

    Code: 200
    Response Body: bgates, it is private.
    

Using client_id and client_secret to get access token

You can get an access token to simple-service using client_id and client_secret

Configuration

  • Access http://localhost:8080
  • Click the dropdown button that contains Keycloak and select company-services
  • On the left menu, click Clients
  • Select simple-service client
  • In Settings tab
    • Go to Capability config and check Service accounts roles checkbox
    • Click Save button
  • In Service account roles tab
    • Click service-account-simple-service link present in the info message

      "To manage detail and group mappings, click on the username service-account-simple-service"

    • In Role mapping tab
      • Click Assign role button
      • Click Filter by realm roles dropdown button and select Filter by clients
      • In Search by role name type simple-service and press Enter
      • Select [simple-service] USER name and click Assign button
      • Now, service-account-simple-service has the role USER of simple-service assigned

Test

  1. Open a terminal

  2. Create an environment variable that contains the Client Secret generated by Keycloak to simple-service at Configure Keycloak step

    SIMPLE_SERVICE_CLIENT_SECRET=...
    
  3. Run the following command

    CLIENT_ACCESS_TOKEN=$(curl -s -X POST \
      "http://localhost:8080/realms/company-services/protocol/openid-connect/token" \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "grant_type=client_credentials" \
      -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \
      -d "client_id=simple-service" | jq -r .access_token)
    
  4. Try to call the endpoint GET /api/private

    curl -i http://localhost:9080/api/private -H "Authorization: Bearer $CLIENT_ACCESS_TOKEN"
    

    It should return

    HTTP/1.1 200
    service-account-simple-service, it is private.
    

Running simple-service as a Docker container

  • In a terminal, make sure you are in springboot-keycloak-openldap root folder

  • Build Docker Image

    • JVM
      ./docker-build.sh
      
    • Native
      ./docker-build.sh native
      
    Environment Variable Description
    KEYCLOAK_HOST Specify host of the Keycloak to use (default localhost)
    KEYCLOAK_PORT Specify port of the Keycloak to use (default 8080)
  • Run Docker Container

    docker run --rm --name simple-service \
      -p 9080:8080 \
      -e KEYCLOAK_HOST=keycloak \
      --network=springboot-keycloak-openldap_default \
      ivanfranchin/simple-service:1.0.0
    
  • Open a new terminal

  • Create an environment variable that contains the Client Secret generated by Keycloak to simple-service at Configure Keycloak step

    SIMPLE_SERVICE_CLIENT_SECRET=...
    
  • Run the commands below to get an access token for bgates user

    BGATES_TOKEN=$(
      docker run -t --rm -e CLIENT_SECRET=$SIMPLE_SERVICE_CLIENT_SECRET --network springboot-keycloak-openldap_default alpine/curl:latest sh -c '
        curl -s -X POST http://keycloak:8080/realms/company-services/protocol/openid-connect/token \
          -H "Content-Type: application/x-www-form-urlencoded" \
          -d "username=bgates" \
          -d "password=123" \
          -d "grant_type=password" \
          -d "client_secret=$CLIENT_SECRET" \
          -d "client_id=simple-service"')
    
    BGATES_ACCESS_TOKEN=$(echo $BGATES_TOKEN | jq -r .access_token)
    
  • Call the endpoint GET /api/private

    curl -i http://localhost:9080/api/private -H "Authorization: Bearer $BGATES_ACCESS_TOKEN"
    

    It should return

    HTTP/1.1 200
    bgates, it is private.
    

Shutdown

  • To stop simple-service application, go to the terminal where it is running and press Ctrl+C
  • To stop and remove docker compose containers, network and volumes, go to a terminal and inside springboot-keycloak-openldap root folder, run the following command
    docker compose down -v
    

Cleanup

To remove the Docker image create by this project, go to a terminal and, inside springboot-keycloak-openldap root folder, run the following script

./remove-docker-images.sh

References

springboot-keycloak-openldap's People

Contributors

anthonyraymond avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

springboot-keycloak-openldap's Issues

unclear Connection URL

Hi,
first of all ty for the detailed readme, this is exactly the project i am looking for.

but I need some clarification on the Topic Configuring Keycloak

At Point 5.

LDAP Integration: On Connection URL type ldap://machine-ip-address.

what exactly did you put in there. I got all your containers running from the docker-compose file. and didnt touch the yml file at all. but if I fill in the Connection URL and hit the Test connection button, then nothing happens.

I tried the following URLs:

ldap://localhost:389, ldap://0.0.0.0:389, ldap://ldap-service:389, ldap://127.0.0.1:389, ldap://localhost, ldap://0.0.0.0, ldap://ldap-service, ldap://127.0.0.1 or ldap://localhost

It works only if I dont use a keycloak docker container but the standalone keycloak server then i just have to insert ldap://localhost:389 and the click on Test connection button results in a "successful" notification.

I think somehow the 2 containers (keycloak and ldap) cant find each other, although they are in the same docker network.

Would be awesome if you could help me on this!

Run simple-service using Maven returns error

Starting the application with
./mvnw clean spring-boot:run --projects simple-service -Dspring-boot.run.jvmArguments="-Dserver.port=9080"

downloads the dependencies but ends with the following error:

[ERROR] Unknown lifecycle phase ".run.jvmArguments=-Dserver.port=9080". You must specify a valid lifecycle phase or a goal in the format <plugin-prefix>:<goal> or <plugin-group-id>:<plugin-artifact-id>[:<plugin-version>]:<goal>. Available lifecycle phases are: pre-clean, clean, post-clean, validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-site, site, post-site, site-deploy. -> [Help 1]

It seems like maven doesn't understand the specified goals/lifecycle phase clean or run? I also tried this with my local maven, bit it produced the same result.
I have

openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-86)
OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)

installed. Do you have an idea what the issue could be?
Maybe something in the formatting is not right?

PS: I am executing this on windows in powershell

error_description="Didn't find publicKey for specified kid"

For Bill Gates example, when POSTing to the private endpoint
curl -i -H "Authorization: Bearer $BGATES_ACCESS_TOKEN" http://localhost:$PORT/api/private

where $BGATES_ACCESS_TOKEN decoded is:

{
  "jti": "ffa73f05-4109-4a90-926a-ed7013278144",
  "exp": 1551843672,
  "nbf": 0,
  "iat": 1551843372,
  "iss": "http://localhost:8181/auth/realms/company-services",
  "aud": "account",
  "sub": "42e8dcf1-d1e1-4f65-b5d5-ef46180f8c6e",
  "typ": "Bearer",
  "azp": "simple-service",
  "auth_time": 0,
  "session_state": "57df6580-603a-4ac9-ba4c-2bb1864c15ad",
  "acr": "1",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    },
    "simple-service": {
      "roles": [
        "user"
      ]
    }
  },
  "scope": "profile email",
  "email_verified": false,
  "name": "Bill Gates gates",
  "preferred_username": "bgates",
  "given_name": "Bill Gates",
  "family_name": "gates"
}

The result of the curl request is

HTTP/1.1 401 
WWW-Authenticate: Bearer realm="company-services", error="invalid_token", error_description="Didn't find publicKey for specified kid"
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
WWW-Authenticate: Bearer realm="company-services", error="invalid_token", error_description="Didn't find publicKey for specified kid"
Content-Length: 0
Date: Wed, 06 Mar 2019 03:31:13 GMT

Is this something to do with a missing public key?

I am running the Sprint Boop app inside a docker container like so:

FROM openjdk:8-jdk-alpine

COPY . .

EXPOSE 8080

CMD ./mvnw clean spring-boot:run

then I build and run it with docker build -t springboot . && docker run -p $PORT:8080 springboot

error on phpldapadmin container

i can't get access to phpldapadmin dashboard (192.168.99.100:6443) due to an error on it's container :

Set apache2 https config...
No certificate file and certificate key provided, generate:
/container/service/phpldapadmin/assets/apache2/certs/phpldapadmin.crt and /container/service/phpldapadmin/assets/apache2/certs/phpldapadmin.key
2019/06/11 10:24:22 [INFO] generate received request
2019/06/11 10:24:22 [INFO] received CSR
2019/06/11 10:24:22 [INFO] generating key: ecdsa-384
2019/06/11 10:24:22 [INFO] encoded CSR
2019/06/11 10:24:22 [INFO] signed certificate with serial number 587292907324328512011019972735108992728466332616
Link /container/service/:ssl-tools/assets/default-ca/default-ca.pem to /container/service/phpldapadmin/assets/apache2/certs/ca.crt
Bootstap phpLDAPadmin...
tr: write error: Broken pipe
tr: write error
*** Set environment for container process
*** Remove file /container/environment/99-default/default.startup.yaml
*** Environment files will be proccessed in this order :
Caution: previously defined variables will not be overriden.
/container/environment/99-default/default.yaml

To see how this files are processed and environment variables values,
run this container with '--loglevel debug'
*** Running runit daemon...
[11-Jun-2019 10:24:24] NOTICE: fpm is running, pid 884
[11-Jun-2019 10:24:24] NOTICE: ready to handle connections
[11-Jun-2019 10:24:24] NOTICE: systemd monitor interval set to 10000ms
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.24.0.4. Set the 'ServerName' directive globally to suppress this message

is there any required network configuration to do ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.