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

Jwt #11

Merged
merged 7 commits into from
Sep 24, 2024
Merged

Jwt #11

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
19 changes: 19 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<maven.checkstyle.plugin.configLocation>checkstyle.xml</maven.checkstyle.plugin.configLocation>
<lombok.mapstruct.binding.version>0.2.0</lombok.mapstruct.binding.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<jjwt.version>0.12.6</jjwt.version>
</properties>
<dependencies>

Expand Down Expand Up @@ -123,6 +124,24 @@
<version>2.6.0</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>

</dependencies>

<build>
Expand Down
31 changes: 25 additions & 6 deletions src/main/java/project/bookstore/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
package project.bookstore.config;

import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher;

import lombok.RequiredArgsConstructor;
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.configuration.AuthenticationConfiguration;
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.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;
import project.bookstore.security.JwtAuthenticationFilter;

@Configuration
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final UserDetailsService service;
private final JwtAuthenticationFilter jwtAuthenticationFilter;

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

@Bean
public SecurityFilterChain getSecurityFilterChain(HttpSecurity http) throws Exception {
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(
auth -> auth
.requestMatchers(
"/auth/**",
"/swagger-ui/**",
"/v3/api-docs/**"
antMatcher("/auth/**"),
antMatcher("/swagger-ui/**"),
antMatcher("/v3/api-docs/**")
)
.permitAll()
.anyRequest()
.authenticated()
)
.userDetailsService(userDetailsService)
.sessionManagement(
s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
.userDetailsService(service)
.build();
}

@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration
) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import project.bookstore.dto.user.UserLoginRequestDto;
import project.bookstore.dto.user.UserLoginResponseDto;
import project.bookstore.dto.user.UserRegistrationRequestDto;
import project.bookstore.dto.user.UserResponseDto;
import project.bookstore.exception.RegistrationException;
import project.bookstore.security.AuthenticationService;
import project.bookstore.service.UserService;

@Tag(name = "Authentication controller", description = "endpoints for authentication")
Expand All @@ -19,6 +22,7 @@
@RequestMapping("/auth")
public class AuthenticationController {
private final UserService userService;
private final AuthenticationService authenticationService;

@PostMapping("/register")
@Operation(
Expand All @@ -31,4 +35,13 @@ public UserResponseDto register(@RequestBody @Valid UserRegistrationRequestDto r

return userService.register(requestDto);
}

@PostMapping("/login")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add swagger annotation

@Operation(
summary = "login user",
description = "login user by fields: email, password"
)
public UserLoginResponseDto login(@RequestBody @Valid UserLoginRequestDto requestDto) {
return authenticationService.authenticate(requestDto);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class BookController {
private final BookMapper bookMapper;

@GetMapping
@PreAuthorize("hasRole('ROLE_USER')")
@Operation(
summary = "Get all books",
description = "Get all books from db")
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/project/bookstore/dto/user/UserLoginRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package project.bookstore.dto.user;

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

public record UserLoginRequestDto(
@NotBlank
@Size(min = 6, max = 20)
@Email
String email,
@NotBlank
@Size(min = 6, max = 20)
String password
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package project.bookstore.dto.user;

public record UserLoginResponseDto(String token) {
}
7 changes: 0 additions & 7 deletions src/main/java/project/bookstore/model/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,16 @@ public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String title;

@Column(nullable = false)
private String author;

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

@Column(nullable = false)
private BigDecimal price;

private String description;

private String coverImage;

@Column(nullable = false, columnDefinition = "TINYINT(1)")
private boolean isDeleted = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package project.bookstore.security;

import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import project.bookstore.dto.user.UserLoginRequestDto;
import project.bookstore.dto.user.UserLoginResponseDto;

@RequiredArgsConstructor
@Service
public class AuthenticationService {
private final JwtUtil jwtUtil;
private final AuthenticationManager authenticationManager;

public UserLoginResponseDto authenticate(UserLoginRequestDto requestDto) {
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(requestDto.email(), requestDto.password())
);
String token = jwtUtil.generateToken(authentication.getName());
return new UserLoginResponseDto(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package project.bookstore.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
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.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String TOKEN_HEADER = "Bearer ";
private final JwtUtil jwtUtil;
private final UserDetailsService service;

@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
String token = getToken(request);
if (token != null && jwtUtil.isValidToken(token)) {
UserDetails userDetails = service.loadUserByUsername(jwtUtil.getUserName(token));
Authentication authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}

private String getToken(HttpServletRequest request) {
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
return (StringUtils.hasText(token) && token.startsWith(TOKEN_HEADER))
? token.substring(TOKEN_HEADER.length()) : null;
}
}
52 changes: 52 additions & 0 deletions src/main/java/project/bookstore/security/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package project.bookstore.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.function.Function;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class JwtUtil {
@Value("${jwt.expiration}")
private long expiration;
private final SecretKey secret;

public JwtUtil(@Value(value = "${jwt.secret}") String secretString) {
secret = Keys.hmacShaKeyFor(secretString.getBytes(StandardCharsets.UTF_8));
}

public String generateToken(String username) {
return Jwts.builder()
.subject(username)
.expiration(new Date(System.currentTimeMillis() + expiration))
.signWith(secret)
.compact();
}

public boolean isValidToken(String token) {
try {
return !getClaimFromToken(token, Claims::getExpiration).before(new Date());
} catch (JwtException | IllegalArgumentException e) {
throw new JwtException("Expired or invalid JWT token", e);
}
}

public String getUserName(String token) {
return getClaimFromToken(token, Claims::getSubject);
}

private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = Jwts.parser()
.verifyWith((SecretKey) secret)
.build()
.parseSignedClaims(token)
.getPayload();
return claimsResolver.apply(claims);
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ spring.jpa.show-sql=true
spring.jpa.open-in-view=false

server.servlet.context-path=/api

jwt.expiration=1000000
jwt.secret=qwertyuiopasdfghjklzxcvbnm34567890
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ databaseChangeLog:
nullable: false
- column:
name: shipping_address
type: varchar(255)
type: varchar(255)
- column:
name: is_deleted
type: tinyint
defaultValueBoolean: false
constraints:
nullable: false

This file was deleted.

2 changes: 0 additions & 2 deletions src/main/resources/db/changelog/db.changelog-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ databaseChangeLog:
file: db/changelog/changes/04-create-users-roles-table.yaml
- include:
file: db/changelog/changes/05-insert-users-to-db.yaml
- include:
file: db/changelog/changes/06-add-isDeleted-to-users.yaml
3 changes: 3 additions & 0 deletions src/test/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

jwt.expiration=1000000
jwt.secret=qwertyuiopasdfghjklzxcvbnm34567890
Loading