Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing User Authentication #2

Merged
merged 11 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic"
}
70 changes: 70 additions & 0 deletions postrify-backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,76 @@
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>

<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.5.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,63 @@
package com.postrify.postrifybackend.config;

import com.postrify.postrifybackend.security.JwtAuthenticationEntryPoint;
import com.postrify.postrifybackend.security.JwtAuthenticationFilter;
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.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@org.springframework.context.annotation.Configuration
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

@Autowired private UserDetailsService userDetailsService;

@Autowired private JwtAuthenticationEntryPoint unauthorizedHandler;

@Bean
public JwtAuthenticationFilter authenticationJwtTokenFilter() {
return new JwtAuthenticationFilter();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public AuthenticationManager authenticationManagerBean(final HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}

@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
http.csrf(csrf -> csrf.disable())
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(
session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(
auth -> auth.requestMatchers("/**").permitAll().anyRequest().authenticated());

http.addFilterBefore(
authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.postrify.postrifybackend.controller;

import com.postrify.postrifybackend.model.JwtResponse;
import com.postrify.postrifybackend.model.LoginRequest;
import com.postrify.postrifybackend.model.MessageResponse;
import com.postrify.postrifybackend.model.SignUpRequest;
import com.postrify.postrifybackend.model.User;
import com.postrify.postrifybackend.security.JwtTokenProvider;
import com.postrify.postrifybackend.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired private UserService userService;

@Autowired private AuthenticationManager authenticationManager;

@Autowired private JwtTokenProvider jwtTokenProvider;

@PostMapping(value = "/signup", produces = "application/json")
public ResponseEntity<?> registerUser(@Validated @RequestBody final SignUpRequest signUpRequest) {
System.out.println("Received signUpRequest: " + signUpRequest.getUsername());
User user =
new User(
signUpRequest.getUsername(), signUpRequest.getEmail(), signUpRequest.getPassword());
userService.registerUser(user);
System.out.println("Returning success message");
return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
}

@PostMapping(value = "/signin", produces = "application/json")
public ResponseEntity<?> authenticateUser(
@Validated @RequestBody final LoginRequest loginRequest) {
try {
System.out.println("Received loginRequest: " + loginRequest.getUsername());
Authentication authentication =
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = jwtTokenProvider.generateToken(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println("Returning jwt response");
return ResponseEntity.ok(new JwtResponse(jwt, userDetails.getUsername()));
} catch (Exception e) {
System.out.println("Exception: " + e);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(new MessageResponse("Invalid credentials!"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.postrify.postrifybackend.model;

public class JwtResponse {
private String token;
private String username;

public JwtResponse(final String token, final String username) {
this.token = token;
this.username = username;
}

public String getToken() {
return token;
}

public String getUsername() {
return username;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.postrify.postrifybackend.model;

import javax.validation.constraints.NotBlank;

public class LoginRequest {
@NotBlank private String username;

@NotBlank private String password;

public LoginRequest(final String username, final String password) {
this.username = username;
this.password = password;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.postrify.postrifybackend.model;

public class MessageResponse {
private String message;

public MessageResponse(final String message) {
this.message = message;
}

public String getMessage() {
return message;
}

public void setMessage(final String message) {
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.postrify.postrifybackend.model;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

public class SignUpRequest {
@NotBlank
@Size(min = 3, max = 20)
private String username;

@NotBlank
@Size(max = 50)
@Email
private String email;

@NotBlank
@Size(min = 6, max = 40)
private String password;

public SignUpRequest(final String username, final String email, final String password) {
this.username = username;
this.email = email;
this.password = password;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public String getEmail() {
return email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.postrify.postrifybackend.model;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String username;

@Column(nullable = false)
private String password;

@Column(nullable = false, unique = true)
private String email;

public User() {}

public User(final String username, final String email, final String password) {
this.username = username;
this.email = email;
this.password = password;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public void setPassword(final String password) {
this.password = password;
}

public String getEmail() {
return email;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.postrify.postrifybackend.repository;

import com.postrify.postrifybackend.model.User;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);

Boolean existsByUsername(String username);

Boolean existsByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.postrify.postrifybackend.security;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(
final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException)
throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
Loading
Loading