Tuesday, December 7, 2021

Secure Spring boot 2 with Keycloak 15

Secure Spring boot 2 with Keycloak 15

We'll cover the basics of setting up a keycloak server, connecting spring boot microservices, also using spring security.

1. Setting Up a keycloack server.

Please check the steps to install keycloak server here.

2. Creating a Realm

For default the console opens up Master realm, Let's navigate left corner Add realm button.













For this example I typed the name demo1. we'll click the button create.













3. Creating a client









Add a new client with the name springboot-keycloak with openid-connect. click button save.
































We'll be creating a spring boot microservices running at the port 8081, we've used a redirect URL of http://localhost:8081 above on image.

4. Creating a Role








Add next roles, "admin" and "user".
















5. Creating a Users

Add next users: user1 to role user and user2 to role admin.










Set a password. I typed the password for the same user1 and user2. For this example.






















Set a role(s).

















You'll do the same steps with de user2 however the role is "admin".







5. Creating a Spring Boot Application

In this examples we're intregation Spring Boot and keycloack.

  • Configuration Spring boot, Spring Data and Keycloack.
  • Define Datas Models, Repository.
  • Spring Rest Controllers  


Spring Boot Rest  API 

These are APIs that we need to provide:

MethodsUrls
POST/users
GET/users

Technology

  • Java 11
  • Spring Boot 2.6.1
  • Mysql 8.0.23
  • Maven 3.6.3
  • Postman v9.0.8
  • IntelliJ IDEA 2021.2.3 

Project Structure


























Configuration Spring Boot Keycloak project 


pom.xml


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Exclude the Tomcat dependency -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Use Jetty instead -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>

application.yml


server:
port: 8081

keycloak:
auth-server-url: http://127.0.0.1:9080/auth
realm: demo1
resource: springboot-keycloack
public-client: true
bearer-only: true

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/backend?allowPublicKeyRetrieval=true&useSSL=false
username: henry
password: *********
hikari:
initialization-fail-timeout: 0
jpa:
database-platform: org.hibernate.dialect.MySQL5Dialect
generate-ddl: true
show-sql: true
hibernate:
ddl-auto: create

Class Security configuration


I suggest to you, we 'll be reading Spring Boot Adapters, I copied from Spring Boot Adapters the configuration class.

package com.henry.keycloack.springboot2keycloak.configuration;

import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@KeycloakConfiguration
@Import(KeycloakSpringBootConfigResolver.class)
@EnableGlobalMethodSecurity(jsr250Enabled = true)//so i need the roles exist environment, @RolesAllowed
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter
{
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

KeycloakAuthenticationProvider keycloakAuthenticationProvider = new KeycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}

/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}

@Override
protected void configure(HttpSecurity http) throws Exception
{
super.configure(http);
http
.authorizeRequests()
.anyRequest().permitAll();
http.csrf().disable();
}
}


Data class model


package com.henry.keycloack.springboot2keycloak.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table
public class User {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
}

Repository Interface

package com.henry.keycloack.springboot2keycloak.repository;

import com.henry.keycloack.springboot2keycloak.model.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User,Integer> {
}


Spring Rest APIs Controller

package com.henry.keycloack.springboot2keycloak.controller;

import com.henry.keycloack.springboot2keycloak.model.User;
import com.henry.keycloack.springboot2keycloak.repository.UserRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.security.RolesAllowed;

@RestController
public class UserController {

private final UserRepository userRepository;

public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}

@PostMapping("/users")
@RolesAllowed({"user"})
public ResponseEntity<User> save(@RequestBody User user){
return ResponseEntity.ok(userRepository.save(user));
}

@GetMapping("/users")
@RolesAllowed("admin")
public ResponseEntity<Iterable<User>> findAll(){
return ResponseEntity.ok(userRepository.findAll());
}
}

Run & Test


Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.

Keycloak provides a REST API for generating and refreshing access tokens. 

Let's go.


1. We need to acquire an access token from Keycloak from this url. 

  • 127.0.0.1 if you want will change to localhost.
  • Port: 9080 in my case.
  • auth/realms url defined by keycloak.
  • demo1 is the name of your realm.
  • protocol/openid-connect/token url defined by keycloak.

 http://127.0.0.1:9080/auth/realms/demo1/protocol/openid-connect/token


2. Configurate on Postman.


GET. 

I used the user "user2" role "admin", we'll check images. 















Access Token url. http://127.0.0.1:9080/auth/realms/demo1/protocol/openid-connect/token















Click on Get New Acces token.


















Click Use Token.






































POST. 

I used the user "user1" role "user", we'll check images. 




































Source Code


Here on Github.




References.

https://www.baeldung.com/spring-boot-keycloak
https://www.youtube.com/watch?v=rcvAmBoDlLk






Friday, December 3, 2021

Install keycloak 15.0.2 on ubuntu 20.04

Keycloak

Keycloak is an open source identity and access management for modern applications and services, no need to deal with storing users or authenbticating users. it's all available out of the box.

you'll even get advanced features such as user federation (LDAP or kerberos) and social login.


Downloading and installing keycloack

Let's download the keycloak-15.0.2 Standalone server distribution from the official source.

let unzip i suggest change default port 8080, when you run standalone.sh add next argumets port-offset=1000 then (8080+1000=9080).


unzip keycloak-15.0.2.zip
cd keycloak-15.0.2/bin
./standalone.sh -Djboss.socket.binding.port-offset=1000

Open a browser  http://localhost:9080 or http://127.0.0.1:9080 result this.

















Open Create a initial admin user and the password.


























We can now proceed to the Administrative Console.


 


















































That it is.
















Thursday, November 18, 2021

Spring Boot 2 Spring Data Kotlin Cassandra CRUD

we're integration Spring Boot Kotlin Cassandra  CRUD.

  • Configuration Spring boot, Spring Data, Kotlin with Cassandra Database.
  • Define Data's Models, Repository and Services.
  • Spring Rest Controllers  

Spring Boot Kotlin Cassandra Rest CRUD API 

These are APIs that we need to provide:

MethodsUrls
POST /services/ws/save
GET /services/ws/users
PUT /services/ws/update/:email
DELETE /services/ws/delete/:email

Technology

  • Kotlin 1.5.31
  • Java 11
  • Spring Boot 2.5.6
  • Cassandra 4.0.1
  • Cqlsh 6.0.0
  • Maven 3.6.3
  • IntelliJ IDEA 2021.2.3 
  • Docker

Project Structure






















Configuration Spring Boot kotlin Cassandra project 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>SpringBootCassandraKotlin</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringBootCassandraKotlin</name>
<description>Crud Cassandra with kotlin</description>
<properties>
<java.version>11</java.version>
<kotlin.version>1.5.31</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

</project>

application.yml

server:
servlet:
context-path: /services
port: 9000

spring:
data:
cassandra:
keyspace-name: test_keyspace
contact-points: localhost:9042
local-datacenter: datacenter1


Set up Cassandra by Docker


docker pull cassandra

 docker run --name cassandra -p 127.0.0.1:9042:9042 -p 127.0.0.1:9160:9160   -d cassandra

 docker exec -it cassandra /bin/bash

 #cqlsh






Check datacenter name:

cqlsh> use system;
cqlsh:system> select data_center from local;

 data_center
-------------
 datacenter1

(1 rows)
cqlsh:system>

 
We Created keyspaces: test_keyspace.

create keyspace test_keyspace with replication={'class':'SimpleStrategy', 'replication_factor':1};

use test_keyspace;

create table users
(
    email varchar primary key,
    name varchar,
    age int
);


Data class model

package com.henry.model

import org.springframework.data.cassandra.core.mapping.PrimaryKey
import org.springframework.data.cassandra.core.mapping.Table

@Table
data class Users(@PrimaryKey
var email: String, var name: String, var age: Int)


Repository Interface

package com.henry.repository

import com.henry.model.Users
import org.springframework.data.cassandra.repository.CassandraRepository

interface UserRepository : CassandraRepository<Users,String> {
fun findByNameContaining(name: String?): List<Users?>?

}

Service Interface

package com.henry.services

import com.henry.model.Users

interface UserService {
fun byContName(name: String): List<Users?>?
fun findAllUsers(): MutableList<Users>;
fun saveUser(user: Users): Users
fun updateUser(email: String, user: Users): Users
fun deleteUser(email: String)
}

Service Class

package com.henry.services.impl

import com.henry.model.Users
import com.henry.repository.UserRepository
import com.henry.services.UserService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import java.util.*

@Service
class UserServiceImpl : UserService {

@Autowired
lateinit var userRepository: UserRepository;

override fun byContName(name: String): List<Users?>? {
return userRepository.findByNameContaining(name)
}

override fun findAllUsers(): MutableList<Users> {
return userRepository.findAll()
}

override fun saveUser(user: Users): Users {
return userRepository.save(user)
}

override fun updateUser(email: String, user: Users): Users {
val obj: Optional<Users> = userRepository.findById(email)
obj.get().age = user.age
obj.get().name = user.name
return userRepository.save(obj.get())

}

override fun deleteUser(email: String) {
userRepository.deleteById(email)
}
}

Spring Rest APIs Controller

package com.henry.controller

import com.henry.model.Users
import com.henry.services.UserService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*


@CrossOrigin(origins = ["http://localhost:9000"])
@RestController
@RequestMapping("/ws")
class UserController(private val userService: UserService) {

@GetMapping("/users")
fun getAllUsers(): ResponseEntity<MutableList<Users>>? {
return ResponseEntity(userService.findAllUsers(), HttpStatus.OK)
}

@PostMapping("/save")
fun save(@RequestBody user: Users): ResponseEntity<Users>?{
return ResponseEntity(userService.saveUser(user), HttpStatus.OK)
}

@PutMapping("/update/{email:.*}")
fun update(@PathVariable email: String, @RequestBody user: Users): ResponseEntity<Users>?{
return ResponseEntity(userService.updateUser(email, user), HttpStatus.OK)
}

@DeleteMapping("/delete/{email:.*}")
fun delete(@PathVariable email: String): ResponseEntity<Unit> {
return ResponseEntity(userService.deleteUser(email), HttpStatus.OK)
}




}

Run & Test

Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.

POST.








































GET.



































PUT.























DELETE.




















Source Code

Here on Github.


References.

https://www.bezkoder.com/spring-boot-cassandra-crud/
https://www.youtube.com/channel/UCHRnRlWQuZYDmnuB7SbXIpQ





Creating REST APIs with OpenAPI, Spring Boot 3.3.3, Java 21, and Jakarta

 Introduction In today's software landscape, designing robust and scalable REST APIs is a crucial aspect of application development. Wit...