Commit e9d13d16 authored by Filip Filchev's avatar Filip Filchev
Browse files

Add Updates

Add Cookies and fix Filter, Login and Logout
Fix Register and Upsert Request DTOs. Add Role to them and remove Role ignore from Mappers
Fix Bug in Upsert Methods. Fixed through adding Role to the DTO
Fix Role Changing. Remove Transactional because of Bugs with unique Username Field in Person
parent 599de7c6
Showing with 149 additions and 44 deletions
+149 -44
......@@ -26,7 +26,6 @@
<option value="$PROJECT_DIR$/data" />
<option value="$PROJECT_DIR$/mapper" />
<option value="$PROJECT_DIR$/model" />
<option value="$PROJECT_DIR$/security" />
</set>
</option>
</GradleProjectSettings>
......
......@@ -2,6 +2,7 @@ package studentmanagement.api.rest.controller.authentication;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
......@@ -17,6 +18,9 @@ import studentmanagement.model.dto.create.TeacherLoginRequest;
import studentmanagement.model.dto.create.TeacherRegisterRequest;
import studentmanagement.model.dto.response.LoginResponse;
import studentmanagement.service.authentication.AuthenticationService;
import studentmanagement.service.cookie.CookieUtil;
import java.util.function.Supplier;
@RestController
@RequestMapping(API.V1 + API.AUTH)
......@@ -26,36 +30,52 @@ public class AuthenticationController {
@Autowired
private final AuthenticationService authenticationService;
// @Autowired
@Autowired
private final CookieUtil cookieUtil;
// @Autowired
// private final AdministratorService administratorService;
//
// @PostMapping(API.REGISTER + API.ADMIN)
// public ResponseEntity<LoginResponse> registerAdmin(@Validated @RequestBody AdminRegisterRequest request) {
// return new ResponseEntity<>(administratorService.registerAdmin(request), HttpStatus.CREATED);
// return login(() -> administratorService.registerAdmin(request), HttpStatus.CREATED);
// }
@PostMapping(API.LOGIN + API.ADMIN)
public ResponseEntity<LoginResponse> loginAdmin(@Validated @RequestBody AdminLoginRequest request) {
return new ResponseEntity<>(authenticationService.loginAdmin(request), HttpStatus.ACCEPTED);
return login(() -> authenticationService.loginAdmin(request), HttpStatus.ACCEPTED);
}
@PostMapping(API.REGISTER + API.STUDENT)
public ResponseEntity<LoginResponse> registerStudent(@Validated @RequestBody StudentRegisterRequest request) {
return new ResponseEntity<>(authenticationService.registerStudent(request), HttpStatus.CREATED);
return login(() -> authenticationService.registerStudent(request), HttpStatus.CREATED);
}
@PostMapping(API.LOGIN + API.STUDENT)
public ResponseEntity<LoginResponse> loginStudent(@Validated @RequestBody StudentLoginRequest request) {
return new ResponseEntity<>(authenticationService.loginStudent(request), HttpStatus.ACCEPTED);
return login(() -> authenticationService.loginStudent(request), HttpStatus.ACCEPTED);
}
@PostMapping(API.REGISTER + API.TEACHER)
public ResponseEntity<LoginResponse> registerTeacher(@Validated @RequestBody TeacherRegisterRequest request) {
return new ResponseEntity<>(authenticationService.registerTeacher(request), HttpStatus.CREATED);
return login(() -> authenticationService.registerTeacher(request), HttpStatus.CREATED);
}
@PostMapping(API.LOGIN + API.TEACHER)
public ResponseEntity<LoginResponse> loginTeacher(@Validated @RequestBody TeacherLoginRequest request) {
return new ResponseEntity<>(authenticationService.loginTeacher(request), HttpStatus.ACCEPTED);
return login(() -> authenticationService.loginTeacher(request), HttpStatus.ACCEPTED);
}
private ResponseEntity<LoginResponse> login(Supplier<LoginResponse> supplier, HttpStatus status) {
LoginResponse response = supplier.get();
return ResponseEntity.status(status.value())
.header(
HttpHeaders.SET_COOKIE,
cookieUtil.createCookie(
response.getToken(),
CookieUtil.COOKIE_NAME
).toString()
).body(response);
}
}
......@@ -2,10 +2,12 @@ package studentmanagement.api.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
......@@ -14,6 +16,7 @@ import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import studentmanagement.model.person.Person;
import studentmanagement.model.token.Token;
import studentmanagement.service.cookie.CookieUtil;
import studentmanagement.service.jwt.JwtService;
import studentmanagement.service.user_details.DefaultUserDetailsService;
......@@ -28,6 +31,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private final JwtService jwtService;
@Autowired
private final CookieUtil cookieUtil;
@Override
protected void doFilterInternal(
......@@ -37,20 +43,27 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
) throws ServletException, IOException {
response.addHeader("Alabala", "Alabala");
Optional<String> optionalToken = jwtService.extractJwtToken(request);
Optional<Cookie> cookieOptional = cookieUtil.findServletCookie(request, CookieUtil.COOKIE_NAME);
if (optionalToken.isEmpty()) {
if (cookieOptional.isEmpty()) {
filterChain.doFilter(request, response);
return;
}
String jwtToken = optionalToken.get();
Cookie cookie = cookieOptional.get();
String jwtToken = cookie.getValue();
if (jwtToken.equals(JwtService.INVALID_TOKEN)) {
filterChain.doFilter(request, response);
return;
}
String username = jwtService.extractUsername(jwtToken);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
Person person = this.userDetailsService.loadUserByUsername(username);
Token token = jwtService.findTokenByPersonOrElseNull(person);
Token token = jwtService.findTokenByPersonOrElse(person, null);
// Check if this Token is the same as the one in the DB
if (token == null || !jwtService.isTokenValid(jwtToken, person)) {
......@@ -62,7 +75,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
token = renewToken(token, person);
}
response.addHeader("Authorization", token.getToken());
String cookieString = cookieUtil.createCookie(token.getToken(), CookieUtil.COOKIE_NAME).toString();
response.setHeader(HttpHeaders.SET_COOKIE, cookieString);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(
......
package studentmanagement.api.security;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.stereotype.Service;
import studentmanagement.model.person.Person;
import studentmanagement.model.token.Token;
import studentmanagement.service.cookie.CookieUtil;
import studentmanagement.service.jwt.JwtService;
import studentmanagement.service.user_details.DefaultUserDetailsService;
......@@ -24,15 +27,24 @@ public class LogoutUtil implements LogoutHandler {
@Autowired
private final DefaultUserDetailsService userDetailsService;
@Autowired
private final CookieUtil cookieUtil;
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
Optional<String> optionalToken = jwtService.extractJwtToken(request);
Optional<Cookie> cookieOptional = cookieUtil.findServletCookie(request, CookieUtil.COOKIE_NAME);
if (optionalToken.isEmpty()) {
if (cookieOptional.isEmpty()) {
return;
}
String jwtToken = optionalToken.get();
Cookie cookie = cookieOptional.get();
String jwtToken = cookie.getValue();
if (jwtToken.equals(JwtService.INVALID_TOKEN)) {
return;
}
String username = jwtService.extractUsername(jwtToken);
......@@ -42,13 +54,16 @@ public class LogoutUtil implements LogoutHandler {
Person userDetails = this.userDetailsService.loadUserByUsername(username);
Token token = jwtService.findTokenByPersonOrElseNull(userDetails);
Token token = jwtService.findTokenByPersonOrElse(userDetails, null);
if (token == null) {
return;
}
jwtService.delete(token);
response.setHeader(HttpHeaders.SET_COOKIE,
cookieUtil.createCookie(JwtService.INVALID_TOKEN, CookieUtil.COOKIE_NAME).toString());
logDelete(token);
}
......
......@@ -68,7 +68,7 @@ public class DefaultAdministratorService extends DefaultService<Admin> implement
private LoginResponse login(Supplier<Person> supplier) {
Person userDetails = supplier.get();
Token currentToken = jwtService.findTokenByPersonOrElseNull(userDetails);
Token currentToken = jwtService.findTokenByPersonOrElse(userDetails, null);
if (currentToken != null) {
return new LoginResponse(currentToken.getToken());
......@@ -97,7 +97,7 @@ public class DefaultAdministratorService extends DefaultService<Admin> implement
public AdminUpsertResponse upsert(AdminUpsertRequest request) {
Admin admin = adminUpsertRequestToAdmin(request);
return adminToAdminCreateResponse(create(admin));
return adminToAdminCreateResponse(save(admin));
}
@Override
......
......@@ -52,7 +52,7 @@ public class DefaultAuthenticationService implements AuthenticationService {
private LoginResponse login(Supplier<Person> supplier) {
Person userDetails = supplier.get();
Token currentToken = jwtService.findTokenByPersonOrElseNull(userDetails);
Token currentToken = jwtService.findTokenByPersonOrElse(userDetails, null);
if (currentToken != null) {
return new LoginResponse(currentToken.getToken());
......
package studentmanagement.service.cookie;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Optional;
@Service
public class CookieUtil {
public static final String COOKIE_NAME = "cookie";
public Optional<Cookie> findServletCookie(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies == null) {
return Optional.empty();
}
return Arrays.stream(request.getCookies())
.filter(cookie -> name.equals(cookie.getName()))
.findAny();
}
public ResponseCookie createCookie(String token, String cookieName) {
return ResponseCookie.from(cookieName, token)
.httpOnly(true)
.secure(true)
.path("/")
.maxAge(3600)
.domain("localhost")
.build();
}
public ResponseCookie deleteCookie() {
return ResponseCookie.from("user-id", "")
.maxAge(0)
.build();
}
}
\ No newline at end of file
......@@ -17,7 +17,7 @@ public class DefaultService<T extends IDable> implements Service<T> {
@Override
public T save(T idable) {
return repository.saveAndFlush(idable);
return repository.save(idable);
}
@Override
......
......@@ -6,6 +6,7 @@ import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
......@@ -18,6 +19,7 @@ import org.springframework.web.context.request.ServletRequestAttributes;
import studentmanagement.data.TokenRepository;
import studentmanagement.model.person.Person;
import studentmanagement.model.token.Token;
import studentmanagement.service.cookie.CookieUtil;
import java.security.Key;
import java.util.Collection;
......@@ -31,6 +33,8 @@ import java.util.function.Function;
@Service
@RequiredArgsConstructor
public class JwtService {
public static final String INVALID_TOKEN = "Gepihtitokena";
private static final long FIVE_MINUTES = 1000 * 60 * 5;
private static final long TEN_SECONDS = 1000 * 10;
private static final String SECRET_KEY = "h1tHrnp8s7WQXsrqRIU8Z5CVa9N/sUR4zE5WMLLD5Kk=";
......@@ -39,6 +43,8 @@ public class JwtService {
@Autowired
private final TokenRepository tokenRepository;
@Autowired
private final CookieUtil cookieUtil;
public String extractUsername(String token) {
try {
......@@ -114,13 +120,15 @@ public class JwtService {
}
public Optional<String> extractJwtToken(HttpServletRequest request) {
String authenticationHeader = request.getHeader(AUTHORIZATION_HEADER);
Optional<Cookie> cookieOptional = cookieUtil.findServletCookie(request, CookieUtil.COOKIE_NAME);
if (authenticationHeader == null || !authenticationHeader.startsWith(BEARER)) {
if (cookieOptional.isEmpty()) {
return Optional.empty();
}
String jwtToken = authenticationHeader.substring(7);
Cookie cookie = cookieOptional.get();
String jwtToken = cookie.getValue();
return Optional.of(jwtToken);
}
......@@ -162,13 +170,13 @@ public class JwtService {
}
@Transactional
public synchronized Token findTokenByPersonOrElseNull(Person userDetails) {
return findByUsernameOrElseNull(userDetails.getUsername());
public synchronized Token findTokenByPersonOrElse(Person userDetails, Token orElse) {
return findByUsernameOrElse(userDetails.getUsername(), orElse);
}
@Transactional
public synchronized Token findByUsernameOrElseNull(String username) {
return tokenRepository.findByPersonUsername(username).orElse(null);
public synchronized Token findByUsernameOrElse(String username, Token orElse) {
return tokenRepository.findByPersonUsername(username).orElse(orElse);
}
@Transactional
......
package studentmanagement.service.role;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
......@@ -34,20 +33,21 @@ public class RoleChangeService {
private final AdministratorService administratorService;
@Transactional
// @Transactional
// Can not be transactional because the unique constraint of the username stops the creating of a new entity
public RoleChangeResponse changeRole(RoleChangeRequest request) {
Person person = userDetailsService.loadUserByUsername(request.getUsername());
IDableKey key = new IDableKey(person.getId());
Person created = createToNewRole(
request.getNewRole().toUpperCase()
).apply(person);
Person deleted = deleteFromCurrentRole(
key,
request.getCurrentRole().toUpperCase()
).apply(person);
Person created = createToNewRole(
request.getNewRole().toUpperCase()
).apply(person);
return RoleChangeResponse.builder()
.id(created.getId())
.name(created.getName())
......
......@@ -46,7 +46,6 @@ public class DefaultStudentService extends DefaultService<Student> implements St
}
@Override
public Student findByUsername(String username) {
return studentRepository.findByUsername(username)
......@@ -65,7 +64,7 @@ public class DefaultStudentService extends DefaultService<Student> implements St
public StudentUpsertResponse upsert(StudentUpsertRequest dto) {
Student student = studentUpsertRequestToStudent(dto);
return studentToStudentCreateResponse(create(student));
return studentToStudentCreateResponse(save(student));
}
@Override
......
......@@ -61,7 +61,7 @@ public class DefaultTeacherService extends DefaultService<Teacher> implements Te
public TeacherUpsertResponse upsert(TeacherUpsertRequest dto) {
Teacher teacher = teacherUpsertRequestToTeacher(dto);
return teacherToTeacherCreateResponse(create(teacher));
return teacherToTeacherCreateResponse(save(teacher));
}
@Override
......
......@@ -24,7 +24,6 @@ public interface AdminMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "password", source = "password", qualifiedByName = "encodePassword")
@Mapping(target = "authority", ignore = true)
@Mapping(target = "authorities", ignore = true)
@Mapping(target = "token", ignore = true)
Admin adminRegisterRequestToAdmin(AdminRegisterRequest request, @Context BCryptPasswordEncoder passwordEncoder);
......@@ -48,7 +47,6 @@ public interface AdminMapper {
@Mapping(source = "course", target = "course")
TeacherCourseResponse newTeacherCourseResponse(Teacher teacher, Course course);
@Mapping(target = "authority", ignore = true)
@Mapping(target = "authorities", ignore = true)
@Mapping(target = "token", ignore = true)
Admin adminUpsertRequestToAdmin(AdminUpsertRequest request);
......
......@@ -18,7 +18,6 @@ public interface StudentMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "courses", ignore = true)
@Mapping(target = "password", source = "password", qualifiedByName = "encodePassword")
@Mapping(target = "authority", ignore = true)
@Mapping(target = "authorities", ignore = true)
@Mapping(target = "token", ignore = true)
Student studentCreateRequestToStudent(StudentRegisterRequest request,
......@@ -29,7 +28,6 @@ public interface StudentMapper {
return passwordEncoder.encode(password);
}
@Mapping(target = "authority", ignore = true)
@Mapping(target = "authorities", ignore = true)
@Mapping(target = "token", ignore = true)
@Mapping(target = "courses", ignore = true)
......
......@@ -16,7 +16,6 @@ import studentmanagement.model.person.teacher.Teacher;
public interface TeacherMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "authority", ignore = true)
@Mapping(target = "authorities", ignore = true)
@Mapping(target = "token", ignore = true)
@Mapping(target = "password", source = "password", qualifiedByName = "encodePassword")
......@@ -34,7 +33,6 @@ public interface TeacherMapper {
@Mapping(target = "degree", ignore = true)
Teacher personToTeacher(Person person);
@Mapping(target = "authority", ignore = true)
@Mapping(target = "authorities", ignore = true)
@Mapping(target = "token", ignore = true)
Teacher teacherUpsertRequestToTeacher(TeacherUpsertRequest request);
......
......@@ -5,7 +5,7 @@ import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.lang.NonNull;
import studentmanagement.model.role.Role;
@Data
@AllArgsConstructor
......@@ -22,4 +22,6 @@ public class AdminRegisterRequest {
@NotNull
@NotBlank
private String name;
private Role authority = Role.ADMIN;
}
......@@ -6,6 +6,7 @@ import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import studentmanagement.model.role.Role;
@AllArgsConstructor
@NoArgsConstructor
......@@ -26,4 +27,6 @@ public class AdminUpsertRequest {
@NotNull
@NotBlank
private String password;
private Role authority = Role.ADMIN;
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import studentmanagement.model.role.Role;
@AllArgsConstructor
@NoArgsConstructor
......@@ -30,4 +31,6 @@ public class StudentRegisterRequest {
@Positive
@Min(value = 18)
private Long age;
private Role authority = Role.STUDENT;
}
......@@ -8,6 +8,7 @@ import jakarta.validation.constraints.Positive;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import studentmanagement.model.role.Role;
@AllArgsConstructor
@NoArgsConstructor
......@@ -33,4 +34,6 @@ public class StudentUpsertRequest {
@Positive
@Min(value = 18)
private Long age;
private Role authority = Role.STUDENT;
}
......@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import studentmanagement.model.role.Role;
@Data
@NoArgsConstructor
......@@ -26,4 +27,6 @@ public class TeacherRegisterRequest {
@NotNull
@NotBlank
private String degree;
private Role authority = Role.TEACHER;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment