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

#4-ub-02-sign-in-user-into-application #51

Merged
merged 1 commit into from
Dec 24, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.shaderock.backend.auth.login;

import com.shaderock.backend.model.entity.user.AppUser;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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;
import org.springframework.web.server.ResponseStatusException;

@RestController
@RequestMapping("/api/login")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtUserDetailsService jwtUserDetailsService;
private final JwtTokenService jwtTokenService;

@PostMapping
public ResponseEntity<UserDTO> login(@RequestBody @Valid final LoginForm loginForm) {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginForm.getEmail(),
loginForm.getPassword()));
} catch (final BadCredentialsException ex) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}

AppUser appUser = (AppUser) jwtUserDetailsService.loadUserByUsername(loginForm.getEmail());
UserDTO response = UserDTO.builder()
.firstName(appUser.getFirstName())
.lastName(appUser.getLastName())
.token(jwtTokenService.generateToken(appUser))
.roles(appUser.getRoles())
.build();

return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.shaderock.backend.auth.login;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Optional;

@Component
@RequiredArgsConstructor
public class JWTRequestFilter extends OncePerRequestFilter {

private final JwtTokenService jwtTokenService;
private final JwtUserDetailsService jwtUserDetailsService;

@Override

protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws ServletException, IOException {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}

final String token = header.substring(7);
final Optional<String> optionalUsername = jwtTokenService.validateTokenAndGetUsername(token);
if (optionalUsername.isEmpty()) {
// validation failed or token expired
chain.doFilter(request, response);
return;
}

String username = optionalUsername.get();

// set user details on spring security context
final UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username);
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);

// continue with authenticated user
chain.doFilter(request, response);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.shaderock.backend.auth.login;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Optional;

@Service
public class JwtTokenService {
private final Algorithm hmac512;
private final JWTVerifier verifier;

public JwtTokenService(@Value("${jwt.secret}") final String secret) {
this.hmac512 = Algorithm.HMAC512(secret);
this.verifier = JWT.require(this.hmac512).build();
}

public String generateToken(final UserDetails userDetails) {
LocalDate date = LocalDate.now().plusMonths(1);

ZoneId zoneId = ZoneId.systemDefault();
Instant instant = date.atStartOfDay(zoneId).toInstant();

return JWT.create()
.withSubject(userDetails.getUsername())
.withExpiresAt(instant)
.sign(this.hmac512);
}

public Optional<String> validateTokenAndGetUsername(final String token) {
String result;

try {
result = verifier.verify(token).getSubject();
} catch (final JWTVerificationException verificationEx) {
result = null;
}

if (result != null) {
return Optional.of(result);
} else {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.shaderock.backend.auth.login;

import com.shaderock.backend.model.repository.AppUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class JwtUserDetailsService implements UserDetailsService {
private final AppUserRepository appUserRepository;

@Override
public UserDetails loadUserByUsername(final String username) {
return appUserRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=[%s} not found", username)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.shaderock.backend.auth.login;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;

@Data
public class LoginForm {
@NotNull(message = "Email is not provided")
@NotBlank(message = "Email is empty")
@Email
private String email;

@NotNull(message = "Password is not provided")
@Size(min = 8, max = 25, message = "Password is not between 8 and 25 characters length")
private String password;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.shaderock.backend.auth.login;

import com.shaderock.backend.model.type.Role;
import lombok.Builder;
import lombok.Data;

import java.util.Set;

@Data
@Builder
public class UserDTO {
private String token;
private String firstName;
private String lastName;
private Set<Role> roles;
}