Skip to content
Open
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
Expand Up @@ -5,6 +5,8 @@
import com.code4ro.nextdoor.authentication.dto.RegistrationRequest;
import com.code4ro.nextdoor.authentication.entity.User;
import com.code4ro.nextdoor.authentication.service.AuthenticationService;
import com.code4ro.nextdoor.authentication.service.RefreshTokenService;
import com.code4ro.nextdoor.security.entity.UserPrincipal;
import com.code4ro.nextdoor.security.jwt.JwtTokenProvider;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
Expand All @@ -14,10 +16,7 @@
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
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.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.validation.Valid;
Expand All @@ -30,17 +29,20 @@ public class AuthenticationController {
private final AuthenticationService authenticationService;
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider tokenProvider;
private final RefreshTokenService refreshTokenService;

@Autowired
public AuthenticationController(final AuthenticationService authenticationService,
final AuthenticationManager authenticationManager,
final JwtTokenProvider tokenProvider) {
final JwtTokenProvider tokenProvider,
final RefreshTokenService refreshTokenService) {
this.authenticationService = authenticationService;
this.authenticationManager = authenticationManager;
this.tokenProvider = tokenProvider;
this.refreshTokenService = refreshTokenService;
}

@ApiOperation(value = "Register an user")
@ApiOperation("Register an user")
@PostMapping("/register")
public ResponseEntity<Void> register(@RequestBody RegistrationRequest signUpRequest) {
final User savedUser = authenticationService.register(signUpRequest);
Expand All @@ -51,15 +53,25 @@ public ResponseEntity<Void> register(@RequestBody RegistrationRequest signUpRequ
return ResponseEntity.created(location).build();
}

@ApiOperation(value = "Login user in application")
@ApiOperation("Login user in application")
@PostMapping("/login")
public ResponseEntity<JwtAuthenticationResponse> login(@Valid @RequestBody final LoginRequest loginRequest) {
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword()));

SecurityContextHolder.getContext().setAuthentication(authentication);

final String jwt = tokenProvider.generateToken(authentication);
return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
final UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Copy link

Choose a reason for hiding this comment

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

Why is a custom implementation for this and not a normal authentication flow?
Default SpringSecurity Authentication using JWT is super simple to implement without extra work needed.
And for the long run is the way to go

final String accessTokenJwt = tokenProvider.generateAccessToken(userPrincipal.getId());
final String refreshTokenJwt = refreshTokenService.generate(userPrincipal.getId());

return ResponseEntity.ok(new JwtAuthenticationResponse(accessTokenJwt, refreshTokenJwt));
}

@ApiOperation("Request a new authentication token based on a refresh token")
@GetMapping("/token")
public ResponseEntity<JwtAuthenticationResponse> getAccessToken(@RequestParam String refreshToken) {
final String accessToken = authenticationService.getAccessToken(refreshToken);
return ResponseEntity.ok(new JwtAuthenticationResponse(accessToken, refreshToken));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
@Setter
public class JwtAuthenticationResponse {
private String accessToken;
private String refreshToken;
private String tokenType = "Bearer";

public JwtAuthenticationResponse(String accessToken) {
public JwtAuthenticationResponse(final String accessToken, final String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.code4ro.nextdoor.authentication.entity;

import com.code4ro.nextdoor.core.entity.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.Entity;
import java.util.Date;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class RefreshToken extends BaseEntity {
private String userId;
private String token;
private Date expiryDate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.code4ro.nextdoor.authentication.repository;


import com.code4ro.nextdoor.authentication.entity.RefreshToken;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.UUID;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, UUID> {
List<RefreshToken> findAllByUserId(String userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

public interface AuthenticationService {
User register(RegistrationRequest registrationRequest);

String getAccessToken(String refreshTokenJwt);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.code4ro.nextdoor.authentication.service;

import java.util.UUID;

public interface RefreshTokenService {
boolean isValid(UUID userId, String jti);

String generate(UUID userId);
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
package com.code4ro.nextdoor.authentication.service.impl;

import com.code4ro.nextdoor.core.exception.NextDoorValidationException;
import com.code4ro.nextdoor.authentication.dto.RegistrationRequest;
import com.code4ro.nextdoor.authentication.entity.User;
import com.code4ro.nextdoor.authentication.repository.UserRepository;
import com.code4ro.nextdoor.authentication.service.AuthenticationService;
import com.code4ro.nextdoor.authentication.service.RefreshTokenService;
import com.code4ro.nextdoor.core.exception.NextDoorValidationException;
import com.code4ro.nextdoor.security.jwt.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.UUID;

@Service
public class AuthenticationServiceImpl implements AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider tokenProvider;
private final RefreshTokenService refreshTokenService;

@Autowired
public AuthenticationServiceImpl(final UserRepository userRepository,
final PasswordEncoder passwordEncoder) {
final PasswordEncoder passwordEncoder,
final JwtTokenProvider tokenProvider,
final RefreshTokenService refreshTokenService) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.tokenProvider = tokenProvider;
this.refreshTokenService = refreshTokenService;
}

@Transactional
Expand All @@ -34,4 +45,20 @@ public User register(final RegistrationRequest registrationRequest) {
final User user = new User(registrationRequest.getEmail(), encodedPassword);
return userRepository.save(user);
}

@Override
public String getAccessToken(final String refreshTokenJwt) {
if (!StringUtils.hasText(refreshTokenJwt) || !tokenProvider.isValid(refreshTokenJwt)) {
throw new NextDoorValidationException("authentication.refreshToken.invalid", HttpStatus.BAD_REQUEST);
}

final String refreshToken = tokenProvider.getRefreshTokenFromJWT(refreshTokenJwt);
final UUID userId = tokenProvider.getUserIdFromJWT(refreshTokenJwt);

if (!refreshTokenService.isValid(userId, refreshToken)) {
throw new NextDoorValidationException("authentication.refreshToken.invalid", HttpStatus.BAD_REQUEST);
}

return tokenProvider.generateAccessToken(userId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.code4ro.nextdoor.authentication.service.impl;

import com.code4ro.nextdoor.authentication.entity.RefreshToken;
import com.code4ro.nextdoor.authentication.repository.RefreshTokenRepository;
import com.code4ro.nextdoor.authentication.service.RefreshTokenService;
import com.code4ro.nextdoor.security.entity.RefreshTokenHolder;
import com.code4ro.nextdoor.security.jwt.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
public class RefreshTokenServiceImpl implements RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepository;
private final JwtTokenProvider tokenProvider;
private final PasswordEncoder passwordEncoder;

@Autowired
public RefreshTokenServiceImpl(final RefreshTokenRepository refreshTokenRepository,
final JwtTokenProvider tokenProvider,
final PasswordEncoder passwordEncoder) {
this.refreshTokenRepository = refreshTokenRepository;
this.tokenProvider = tokenProvider;
this.passwordEncoder = passwordEncoder;
}

@Override
public boolean isValid(final UUID userId, final String jti) {
final List<RefreshToken> refreshTokens = refreshTokenRepository.findAllByUserId(userId.toString());
final Optional<RefreshToken> refreshTokenOptional = refreshTokens.stream()
.filter(rt -> passwordEncoder.matches(jti, rt.getToken()))
.findFirst();
if (refreshTokenOptional.isEmpty()) {
return false;
}

final RefreshToken refreshToken = refreshTokenOptional.get();
final boolean isValid = refreshToken.getExpiryDate().after(new Date());
if (!isValid) {
refreshTokenRepository.delete(refreshToken);
}

return isValid;
}

@Override
public String generate(final UUID userId) {
final String refreshToken = UUID.randomUUID().toString();
final RefreshTokenHolder refreshTokenHolder = tokenProvider.generateRefreshToken(userId, refreshToken);
final RefreshToken refreshTokenEntity = refreshTokenHolder.getRefreshToken();
refreshTokenEntity.setToken(passwordEncoder.encode(refreshToken));
refreshTokenRepository.save(refreshTokenEntity);

return refreshTokenHolder.getRefreshTokenJwt();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Table(name = "groups")
@Table(name = "nd_groups")
@Entity
@Getter
@Setter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import com.code4ro.nextdoor.group.dto.GroupDto;
import com.code4ro.nextdoor.group.dto.GroupUpdateDto;
import com.code4ro.nextdoor.group.entity.Group;
import com.code4ro.nextdoor.group.entity.GroupSecurityPolicy;
import com.code4ro.nextdoor.group.repository.GroupRepository;
import com.code4ro.nextdoor.group.service.GroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -18,12 +20,15 @@
public class GroupServiceImpl implements GroupService {
private final GroupRepository groupRepository;
private final MapperService mapperService;
private final PasswordEncoder passwordEncoder;

@Autowired
public GroupServiceImpl(final GroupRepository groupRepository,
final MapperService mapperService) {
final MapperService mapperService,
final PasswordEncoder passwordEncoder) {
this.groupRepository = groupRepository;
this.mapperService = mapperService;
this.passwordEncoder = passwordEncoder;
}

@Override
Expand All @@ -32,6 +37,10 @@ public GroupDto create(final GroupCreateDto createDto) {
final Group group = mapperService.map(createDto, Group.class);
if (group.getOpen()) {
group.setSecurityPolicy(null);
} else {
final GroupSecurityPolicy securityPolicy = group.getSecurityPolicy();
final String rawAnswer = securityPolicy.getAnswer();
securityPolicy.setAnswer(passwordEncoder.encode(rawAnswer));
}

final Group savedGroup = groupRepository.save(group);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.code4ro.nextdoor.security.entity;

import com.code4ro.nextdoor.authentication.entity.RefreshToken;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class RefreshTokenHolder {
private final String refreshTokenJwt;
private final RefreshToken refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ protected void doFilterInternal(final HttpServletRequest request,
final FilterChain filterChain) throws ServletException, IOException {
try {
final String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
if (StringUtils.hasText(jwt) && tokenProvider.isValid(jwt)) {
final UUID userId = tokenProvider.getUserIdFromJWT(jwt);
final UserDetails userDetails = customUserDetailsService.loadUserById(userId);
final UsernamePasswordAuthenticationToken authentication =
Expand Down
Loading