Skip to content

Commit

Permalink
Merge pull request #51 from Shaderock/4-ub-02-sign-in-user-into-appli…
Browse files Browse the repository at this point in the history
…cation

#4-ub-02-sign-in-user-into-application
  • Loading branch information
Shaderock authored Dec 24, 2022
2 parents 5116fb2 + 60109cc commit 8a5544d
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 0 deletions.
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;
}

0 comments on commit 8a5544d

Please sign in to comment.