Prerequisites
Before we start, ensure you have the following:
- Java Development Kit (JDK) installed
- Apache Maven installed
- Node.js and npm installed
- Angular CLI installed (
npm install -g @angular/cli
) - An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed
Step 1: Setting Up the Spring Boot Backend
1.1 Create a Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
Configure Project Metadata:
- Project: Maven Project
- Language: Java
- Spring Boot: Select the latest version of Spring Boot 3.2
- Group: com.example
- Artifact: user-service
- Name: user-service
- Description: User Service
- Package Name: com.example.userservice
- Packaging: Jar
- Java Version: 17 (or your preferred version)
- Click
Next
.
-
Select Dependencies:
- On the
Dependencies
screen, select the dependencies you need:- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database
- Click
Next
.
- On the
-
Generate the Project:
- Click
Generate
to download the project zip file. - Extract the zip file to your desired location.
- Click
-
Open the Project in Your IDE:
- Open your IDE and import the project as a Maven project.
1.2 Update application.properties
Open the application.properties
file located in the src/main/resources
directory and add the following configuration:
server.port=8080
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
Explanation:
server.port=8080
: Sets the server port to 8080.spring.datasource.url=jdbc:h2:mem:testdb
: Configures an in-memory H2 database.spring.datasource.driverClassName=org.h2.Driver
: Sets the H2 driver class.spring.datasource.username=sa
andspring.datasource.password=password
: Set the H2 database credentials.spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
: Configures Hibernate to use H2 dialect.spring.h2.console.enabled=true
: Enables the H2 console for database inspection.spring.jpa.hibernate.ddl-auto=update
: Automatically updates the database schema based on the JPA entities.
1.3 Create User Entity
Create a User
entity class in the com.example.userservice.model
package:
package com.example.userservice.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String email;
// Getters and Setters
}
Explanation:
@Entity
: Marks the class as a JPA entity.@Id
: Marks theid
field as the primary key.@GeneratedValue(strategy = GenerationType.IDENTITY)
: Configures auto-increment for theid
field.username
,password
,email
: Fields representing user attributes.- Getters and Setters: Methods to access and modify the fields.
1.4 Create User Repository
Create a UserRepository
interface in the com.example.userservice.repository
package:
package com.example.userservice.repository;
import com.example.userservice.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
Explanation:
@Repository
: Marks the interface as a Spring Data repository.- Extends
JpaRepository<User, Long>
: Provides CRUD operations for theUser
entity. findByUsername(String username)
: Custom query method to find a user by username.
1.5 Create User Service
Create a UserService
class in the com.example.userservice.service
package that implements UserDetailsService
:
package com.example.userservice.service;
import com.example.userservice.model.User;
import com.example.userservice.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
this.passwordEncoder = new BCryptPasswordEncoder();
}
public User saveUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
return userRepository.save(user);
}
public Optional<User> findByUsername(String username) {
return Optional.ofNullable(userRepository.findByUsername(username));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities("USER")
.build();
}
}
Explanation:
@Service
: Marks the class as a service component.- Implements
UserDetailsService
: Required by Spring Security to load user-specific data. loadUserByUsername(String username)
: Loads user details for authentication.saveUser(User user)
: Saves a new user with an encoded password.findByUsername(String username)
: Finds a user by username.
1.6 Create Security Configuration
Create a SecurityConfig
class in the com.example.userservice.config
package:
package com.example.userservice.config;
import com.example.userservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final UserService userService;
@Autowired
public SecurityConfig(UserService userService) {
this.userService = userService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Explanation:
@Configuration
: Marks the class as a configuration component.@EnableWebSecurity
: Enables Spring Security.securityFilterChain(HttpSecurity http)
: Configures HTTP security, disabling CSRF and permitting access to/auth/**
endpoints.configure(AuthenticationManagerBuilder auth)
: Configures the authentication manager with user details service and password encoder.passwordEncoder()
: Configures the password encoder.
1.7 Create Authentication Controller
Create an AuthController
class in the com.example.userservice.controller
package:
package com.example.userservice.controller;
import com.example.userservice.model.User;
import com.example.userservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/auth")
public class AuthController {
private final UserService userService;
@Autowired
public AuthController(UserService userService) {
this.userService = userService;
}
@PostMapping("/register")
public ResponseEntity<User> registerUser(@RequestBody User
user) {
if (userService.findByUsername(user.getUsername()).isPresent()) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(userService.saveUser(user));
}
@GetMapping("/login")
public ResponseEntity<User> getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentUsername = authentication.getName();
return userService.findByUsername(currentUsername)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.badRequest().build());
}
}
Explanation:
@RestController
: Marks the class as a REST controller.@RequestMapping("/auth")
: Maps requests to/auth
URL.registerUser(@RequestBody User user)
: Handles user registration.getCurrentUser()
: Returns the current authenticated user.- The
@CrossOrigin
annotation on the controller allows CORS requests from the specified origin(http://localhost:4200)
.
Step 2: Setting Up the Angular Frontend
2.1 Create an Angular Project
- Open a terminal and run the following command to create a new Angular project:
ng new user-registration
- Navigate to the project directory:
cd user-registration
2.2 Install Dependencies
Install Bootstrap for styling:
npm install bootstrap
Add Bootstrap to angular.json
:
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
2.3 Create Angular Services and Components
2.3.1 Create Auth Service
Generate the AuthService:
ng generate service services/auth
Edit auth.service.ts
:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class AuthService {
private baseUrl = 'http://localhost:8080/auth';
constructor(private http: HttpClient) { }
register(user: any): Observable<any> {
return this.http.post(`${this.baseUrl}/register`, user);
}
login(credentials: any): Observable<any> {
return this.http.post(`${this.baseUrl}/login`, credentials);
}
}
Explanation:
@Injectable({ providedIn: 'root' })
: Marks the service as injectable and available throughout the app.HttpClient
: Service for making HTTP requests.register(user: any)
: Sends a POST request to register a new user.login(credentials: any)
: Sends a POST request to log in a user.
2.3.2 Create Registration Component
Generate the Registration component:
ng generate component components/register
Edit register.component.ts
:
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent {
user: any = {};
constructor(private authService: AuthService, private router: Router) { }
register() {
this.authService.register(this.user).subscribe(() => {
this.router.navigate(['/login']);
}, error => {
console.error('Registration error: ', error);
});
}
}
Edit register.component.html
:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Registration</div>
<div class="card-body">
<form (ngSubmit)="register()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" [(ngModel)]="user.username" name="username">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" class="form-control" id="email" [(ngModel)]="user.email" name="email">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" [(ngModel)]="user.password" name="password">
</div>
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
</div>
</div>
</div>
</div>
Explanation:
register()
: Calls the AuthService to register a new user.[(ngModel)]="user.username"
,[(ngModel)]="user.email"
,[(ngModel)]="user.password"
: Two-way data binding for form fields.
2.3.3 Create Login Component
Generate the Login component:
ng generate component components/login
Edit login.component.ts
:
import { Component } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
credentials: any = {};
constructor(private authService: AuthService, private router: Router) { }
login() {
this.authService.login(this.credentials).subscribe(() => {
this.router.navigate(['/home']);
}, error => {
console.error('Login error: ', error);
});
}
}
Edit login.component.html
:
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">Login</div>
<div class="card-body">
<form (ngSubmit)="login()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" [(ngModel)]="credentials.username" name="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" [(ngModel)]="credentials.password" name="password">
</div>
<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
</div>
</div>
</div>
</div>
Explanation:
login()
: Calls the AuthService to log in a user.[(ngModel)]="credentials.username"
,[(ngModel)]="credentials.password"
: Two-way data binding for form fields.
2.4 Update Angular Routing
Edit app-routing.module.ts
:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
const routes: Routes = [
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: '', redirectTo: '/register', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Explanation:
- Defines routes for the registration and login components.
- Redirects the root path to the registration component.
2.5 Update Angular App Module
Edit app.module.ts
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { RegisterComponent } from './components/register/register.component';
import { LoginComponent } from './components/login/login.component';
@NgModule({
declarations: [
AppComponent,
RegisterComponent,
LoginComponent
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Explanation:
- Imports necessary modules for the Angular app.
- Declares the components used in the app.
- Sets up the app's root module.
2.6 Run the Angular Application
Open a terminal in the Angular project directory and run the application:
ng serve
Visit http://localhost:4200
in your web browser to see the application.
Conclusion
In this tutorial, we created a Spring Boot backend and an Angular frontend to handle user registration and login functionalities. The backend handles user data and authentication using Spring Security 6.1, while the frontend provides a user-friendly interface using Angular 17. By following this structure, you can extend and customize the application as needed.
Comments
Post a Comment
Leave Comment