Skip to content

syt5-gek961-cloud-datenmanagement-bohaczyk_rychkov created by GitHub Classroom

Notifications You must be signed in to change notification settings

TGM-HIT/syt5-gek961-cloud-datenmanagement-bohaczyk_rychkov

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GK961 Middleware Engineering: "Cloud Data-Interface" (BORM)

Authors:
Kacper Bohaczyk
Sergej Rychkov

Date: 17 October 2024


Introduction

The objective of this exercise is to implement a web-based user authentication system. Users should be able to register and log in to the system. Communication between the client and the service will be established using a REST API.

Prerequisites

  • Java 17 (or higher)
  • Gradle as the build tool
  • PostgreSQL as the database
  • Spring Boot with the following dependencies:
    • Spring Web
    • Spring Security
    • Spring Data JPA
    • Lombok
    • PostgreSQL JDBC Driver
    • JSON Web Token (JWT)

Project Structure and Configuration

1. Creating the Project

Create a new Spring Boot project using Spring Initializr or manually with Gradle. The key dependencies are:

  • Spring Web: For the REST API.
  • Spring Data JPA: For database communication.
  • Spring Security: For authentication and authorization.
  • PostgreSQL JDBC Driver: For connecting to the PostgreSQL database.
  • Lombok: To reduce boilerplate code.
  • jjwt: For JWT token handling.

Visible in commit number 1

Implementation

In this version, we have opted for a login system for users based on PostgreSQL.

Setup

  1. Pull the Docker PostgreSQL image and create a database.
  2. Run the server.

Update build.gradle

plugins {
    id 'org.springframework.boot' version '3.2.5'
    id 'io.spring.dependency-management' version '1.1.0'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security' // Ensure this line is present
    implementation 'org.postgresql:postgresql'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
    implementation 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Update application.properties

spring.datasource.url=jdbc:postgresql://localhost:5432/postgres-gk961
spring.datasource.username=postgres
spring.datasource.password=sergej
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

Create UserMan Entity

UserMan.java

package com.example.demo; // Update this to your package name

import jakarta.persistence.*;
import lombok.Data;

import java.util.List;

@Entity
@Data // Lombok annotation to generate getters, setters, equals, hashCode, and toString methods
public class UserMan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String email;

    private String name;
    private String password;

    @ElementCollection(fetch = FetchType.EAGER)
    private List<String> roles; // ADMIN, READER, MODERATOR
}

Create UserMan Repository

UserManRepository.java

package com.example.demo;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserManRepository extends JpaRepository<UserMan, Long> {
    UserMan findByEmail(String email);
}

Security Configuration

SecurityConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/auth/admin/register").hasRole("ADMIN")
            .antMatchers("/auth/signin").permitAll()
            .antMatchers("/auth/verify").authenticated()
            .and()
            .httpBasic();
    }
}

Authentication Controller

AuthController.java

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/auth")
public class AuthController {

    @Autowired
    private UserManRepository userManRepository;

    private final String SECRET_KEY = "geheimeschluessel"; // Use a secure secret!

    @PostMapping("/admin/register")
    public ResponseEntity<String> register(@RequestBody UserMan userMan) {
        // Encrypt password
        userMan.setPassword(new BCryptPasswordEncoder().encode(userMan.getPassword()));
        userManRepository.save(userMan);
        return ResponseEntity.ok("User registered successfully.");
    }

    @PostMapping("/signin")
    public ResponseEntity<Map<String, Object>> signin(@RequestBody UserMan loginRequest) {
        UserMan userMan = userManRepository.findByEmail(loginRequest.getEmail());
        
        if (userMan != null && new BCryptPasswordEncoder().matches(loginRequest.getPassword(), userMan.getPassword())) {
            String jwt = generateToken(userMan);
            Map<String, Object> response = new HashMap<>();
            response.put("token", jwt);
            response.put("roles", userMan.getRoles());
            return ResponseEntity.ok(response);
        }
        return ResponseEntity.status(401).body(Map.of("message", "Invalid credentials"));
    }

    @GetMapping("/verify")
    public ResponseEntity<String> verify(@RequestHeader("Authorization") String token) {
        String email = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token.replace("Bearer ", ""))
                .getBody()
                .getSubject();
        
        if (email != null) {
            return ResponseEntity.ok("Token is valid for user: " + email);
        }
        return ResponseEntity.status(403).body("Unauthorized");
    }

    private String generateToken(UserMan userMan) {
        return Jwts.builder()
                .setSubject(userMan.getEmail())
                .claim("roles", userMan.getRoles())
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }
}

Data Initializer

DataInitializer.java

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.File;
import java.util.List;

@Component
public class DataInitializer implements CommandLineRunner {

    private final UserManRepository userManRepository;

    public DataInitializer(UserManRepository userManRepository) {
        this.userManRepository = userManRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        ObjectMapper objectMapper = new ObjectMapper();
        UserMan[] users = objectMapper.readValue(new File("path/to/users.json"), UserMan[].class);
        for (UserMan userMan : users) {
            userMan.setPassword(new BCryptPasswordEncoder().encode(userMan.getPassword()));
            userManRepository.save(userMan);
        }
    }
}

User JSON Example

Create a JSON file with users:

[
    {
        "email": "admin@example.com",
        "name": "Admin User",
        "password": "admin123",
        "roles": ["ADMIN"]
    },
    {
        "email": "reader@example.com",
        "name": "Reader User",
        "password": "reader123",
        "roles": ["READER"]
    }
]

Testing

Test Admin Registration

curl -X POST http://localhost:8080/auth/admin/register -H "Content-Type: application/json" -d '{
    "email": "admin@example.com",
    "name": "Admin User",
    "password": "admin123",
    "roles": ["ADMIN"]
}'

Test User Sign-In

curl -X POST http://localhost:8080/auth/signin -H "Content-Type: application/json" -d '{
    "email": "admin@example.com",
    "password": "admin123"
}'

Issues

  • Establishing a connection to the PostgreSQL database
    Solution: The error [3D000] FATAL: database "my-postgres" does not exist was resolved by creating the database with the correct name.

Resources

[1] "Structure and Processing Steps in Developing" . Accessed Oct. 24, 2024ChatGPT Resource].

About

syt5-gek961-cloud-datenmanagement-bohaczyk_rychkov created by GitHub Classroom

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages