diff --git a/pom.xml b/pom.xml index af74d5a..36ff7da 100644 --- a/pom.xml +++ b/pom.xml @@ -102,7 +102,12 @@ junit test - + + org.apache.httpcomponents + httpclient + 4.5.13 + + diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/config/SecurityConfig.java similarity index 98% rename from src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java rename to src/main/java/com/hideyoshi/backendportfolio/base/security/config/SecurityConfig.java index 1e7196e..655d51f 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.hideyoshi.backendportfolio.base.security; +package com.hideyoshi.backendportfolio.base.security.config; import com.hideyoshi.backendportfolio.base.config.RestAuthenticationEntryPointConfig; import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthenticationFilter; diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/model/AuthDTO.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/model/AuthDTO.java new file mode 100644 index 0000000..710ae06 --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/model/AuthDTO.java @@ -0,0 +1,55 @@ +package com.hideyoshi.backendportfolio.base.security.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.hideyoshi.backendportfolio.base.user.entity.Provider; +import com.hideyoshi.backendportfolio.base.user.entity.Role; +import com.hideyoshi.backendportfolio.base.user.model.TokenDTO; +import com.hideyoshi.backendportfolio.base.user.model.UserDTO; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import javax.validation.constraints.Size; +import java.io.Serializable; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class AuthDTO implements Serializable { + + private Long id; + + private String name; + + private String email; + + private String username; + + private String profilePictureUrl; + + private List roles; + + private Provider provider; + + private TokenDTO accessToken; + + private TokenDTO refreshToken; + + public AuthDTO(UserDTO user, TokenDTO accessToken, TokenDTO refreshToken) { + this.id = user.getId(); + this.name = user.getName(); + this.email = user.getEmail(); + this.username = user.getUsername(); + this.roles = user.getRoles(); + this.provider = user.getProvider(); + this.profilePictureUrl = user.getProfilePictureUrl(); + + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + +} diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GithubOAuthMap.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GithubOAuthMap.java index 7f27aa4..e83095a 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GithubOAuthMap.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GithubOAuthMap.java @@ -15,6 +15,11 @@ public class GithubOAuthMap implements OAuthMap { return oAuth2User.getAttribute("login"); } + @Override + public String getProfilePicture() { + return this.oAuth2User.getAttribute("avatar_url"); + } + @Override public Provider getProvider() { return Provider.GITHUB; diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GoogleOAuthMap.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GoogleOAuthMap.java index 9aa4ffe..5f90fdb 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GoogleOAuthMap.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GoogleOAuthMap.java @@ -8,11 +8,16 @@ import org.springframework.security.oauth2.core.user.OAuth2User; @AllArgsConstructor public class GoogleOAuthMap implements OAuthMap { - private OAuth2User oauthUser; + private OAuth2User oAuth2User; @Override public String getPrincipal() { - return this.oauthUser.getAttribute("given_name"); + return this.oAuth2User.getAttribute("given_name"); + } + + @Override + public String getProfilePicture() { + return this.oAuth2User.getAttribute("picture"); } @Override @@ -20,5 +25,4 @@ public class GoogleOAuthMap implements OAuthMap { return Provider.GOOGLE; } - } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMap.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMap.java index ff607c3..9a770b6 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMap.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMap.java @@ -7,6 +7,8 @@ public interface OAuthMap { String getPrincipal(); + String getProfilePicture(); + Provider getProvider(); } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapEnum.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapper.java similarity index 71% rename from src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapEnum.java rename to src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapper.java index 6e63b00..7a0db8d 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapEnum.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapper.java @@ -1,8 +1,9 @@ package com.hideyoshi.backendportfolio.base.security.oauth.mapper; import com.hideyoshi.backendportfolio.base.user.entity.Provider; +import lombok.Getter; -public enum OAuthMapEnum { +public enum OAuthMapper { GOOGLE(GoogleOAuthMap.class, Provider.GOOGLE), @@ -10,9 +11,10 @@ public enum OAuthMapEnum { private final Class oAuthMap; + @Getter private final Provider provider; - private OAuthMapEnum(Class oAuthMap, Provider provider) { + private OAuthMapper(Class oAuthMap, Provider provider) { this.oAuthMap = oAuthMap; this.provider = provider; } @@ -21,12 +23,8 @@ public enum OAuthMapEnum { return oAuthMap; } - public Provider getProvider() { - return provider; - } - - public static OAuthMapEnum byValue(String name) { - for (OAuthMapEnum e : values()) { + public static OAuthMapper byValue(String name) { + for (OAuthMapper e : values()) { if (e.getProvider().getName().equals(name)) { return e; } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthService.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthService.java index 5a8561c..4f11ecb 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthService.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthService.java @@ -1,6 +1,7 @@ package com.hideyoshi.backendportfolio.base.security.service; import com.auth0.jwt.algorithms.Algorithm; +import com.hideyoshi.backendportfolio.base.security.model.AuthDTO; import com.hideyoshi.backendportfolio.base.user.model.TokenDTO; import com.hideyoshi.backendportfolio.base.user.model.UserDTO; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -22,16 +23,18 @@ public interface AuthService { UsernamePasswordAuthenticationToken verifyAccessToken(String authorizationHeader); - UserDTO refreshAccessToken(String refreshToken, HttpServletRequest request, HttpServletResponse response); + AuthDTO refreshAccessToken(String refreshToken, HttpServletRequest request, HttpServletResponse response); - UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request); + AuthDTO signupUser(@Valid UserDTO user, HttpServletRequest request); - UserDTO generateUserWithTokens(UserDTO user, HttpServletRequest request); + AuthDTO generateUserWithTokens(UserDTO user, HttpServletRequest request); - UserDTO processOAuthPostLogin(@Valid UserDTO user, HttpServletRequest request); + AuthDTO processOAuthPostLogin(@Valid UserDTO user, HttpServletRequest request); void loginUser(HttpServletRequest request, HttpServletResponse response, @Valid UserDTO user) throws IOException; void loginOAuthUser(HttpServletRequest request, HttpServletResponse response, OAuth2User user) throws IOException; + UserDTO getLoggedUser(); + } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthServiceImpl.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthServiceImpl.java index 1d0bcd6..9b8cd41 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthServiceImpl.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/service/AuthServiceImpl.java @@ -5,13 +5,15 @@ import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.fasterxml.jackson.databind.ObjectMapper; +import com.hideyoshi.backendportfolio.base.security.model.AuthDTO; import com.hideyoshi.backendportfolio.base.security.oauth.mapper.OAuthMap; -import com.hideyoshi.backendportfolio.base.security.oauth.mapper.OAuthMapEnum; +import com.hideyoshi.backendportfolio.base.security.oauth.mapper.OAuthMapper; import com.hideyoshi.backendportfolio.base.user.entity.Provider; import com.hideyoshi.backendportfolio.base.user.entity.Role; import com.hideyoshi.backendportfolio.base.user.model.TokenDTO; import com.hideyoshi.backendportfolio.base.user.model.UserDTO; import com.hideyoshi.backendportfolio.base.user.service.UserService; +import com.hideyoshi.backendportfolio.microservice.storageService.service.StorageService; import com.hideyoshi.backendportfolio.util.exception.BadRequestException; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; @@ -21,6 +23,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import org.springframework.web.servlet.HandlerExceptionResolver; @@ -55,6 +58,8 @@ public class AuthServiceImpl implements AuthService { private final UserService userService; + private final StorageService storageService; + @Autowired @Qualifier("handlerExceptionResolver") private HandlerExceptionResolver resolver; @@ -98,7 +103,7 @@ public class AuthServiceImpl implements AuthService { TokenDTO accessToken = generateAccessToken(user, algorithm, request); TokenDTO refreshToken = generateRefreshToken(user, algorithm, request); - HashMap tokens = new HashMap<>(); + HashMap tokens = new HashMap<>(); tokens.put("accessToken", accessToken); tokens.put("refreshToken", refreshToken); @@ -108,54 +113,81 @@ public class AuthServiceImpl implements AuthService { @Override public UsernamePasswordAuthenticationToken verifyAccessToken(String authorizationHeader) { - if (authorizationHeader.startsWith(AUTHORIZATION_TYPE_STRING)) { - - String authorizationToken = authorizationHeader.substring(AUTHORIZATION_TYPE_STRING.length()); - Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes()); - - JWTVerifier verifier = JWT.require(algorithm).build(); - DecodedJWT decodedJWT = verifier.verify(authorizationToken); - - String username = decodedJWT.getSubject(); - String[] roles = decodedJWT.getClaim("roles").asArray(String.class); - - Collection authorities = new ArrayList<>(); - stream(roles).forEach(role -> { - authorities.add(new SimpleGrantedAuthority(role)); - }); - return new UsernamePasswordAuthenticationToken(username, null, authorities); + if (!authorizationHeader.startsWith(AUTHORIZATION_TYPE_STRING)) { + return null; } - return null; + + String authorizationToken = authorizationHeader.substring(AUTHORIZATION_TYPE_STRING.length()); + Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes()); + + JWTVerifier verifier = JWT.require(algorithm).build(); + DecodedJWT decodedJWT = verifier.verify(authorizationToken); + + String username = decodedJWT.getSubject(); + String[] roles = decodedJWT.getClaim("roles").asArray(String.class); + + Collection authorities = new ArrayList<>(); + stream(roles).forEach(role -> { + authorities.add(new SimpleGrantedAuthority(role)); + }); + + return new UsernamePasswordAuthenticationToken(username, null, authorities); } @Override - public UserDTO refreshAccessToken(String refreshToken, HttpServletRequest request, HttpServletResponse response) { + public AuthDTO generateUserWithTokens(UserDTO user, HttpServletRequest request) { - if (Objects.nonNull(refreshToken)) { + Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes()); - Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes()); + HashMap tokens = this.generateTokens(user, algorithm, request); - JWTVerifier verifier = JWT.require(algorithm).build(); - DecodedJWT decodedJWT = verifier.verify(refreshToken); + HttpSession httpSession = request.getSession(); + AuthDTO authObject = new AuthDTO(user, tokens.get("accessToken"), tokens.get("refreshToken")); - UserDTO user = this.userService.getUser(decodedJWT.getSubject()); + httpSession.setAttribute("user", authObject); - if (Objects.nonNull(user)) { + return authObject; + } - HttpSession httpSession = request.getSession(); - UserDTO authenticatedUser = user.toResponse( - this.generateAccessToken(user, algorithm, request), - new TokenDTO( - refreshToken, - decodedJWT.getExpiresAt() - ) - ); - httpSession.setAttribute("user", authenticatedUser); + @Override + public AuthDTO signupUser(@Valid UserDTO user, HttpServletRequest request) { - return authenticatedUser; - } + user.setProvider(Provider.LOCAL); - } else { + UserDTO authenticatedUser = this.userService.saveUser(user); + authenticatedUser.setProfilePictureUrl( + this.storageService.getFileUrl(authenticatedUser.getUsername(), "profile") + .getPresignedUrl() + ); + + return this.generateUserWithTokens( + authenticatedUser, + request + ); + + } + + @Override + public void loginUser(HttpServletRequest request, HttpServletResponse response, @Valid UserDTO user) throws IOException { + user.setProfilePictureUrl( + this.storageService.getFileUrl(user.getUsername(), "profile") + .getPresignedUrl() + ); + + AuthDTO authObject = this.generateUserWithTokens( + user, + request + ); + + response.setContentType(APPLICATION_JSON_VALUE); + new ObjectMapper() + .writeValue(response.getOutputStream(), authObject); + } + + @Override + public AuthDTO refreshAccessToken(String refreshToken, HttpServletRequest request, HttpServletResponse response) { + + if (!Objects.nonNull(refreshToken)) { resolver.resolveException( request, response, @@ -163,38 +195,35 @@ public class AuthServiceImpl implements AuthService { new BadRequestException("Invalid Refresh Token. Please authenticate first.") ); } - return null; - } - - @Override - public UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request) { - - user.setProvider(Provider.LOCAL); - - return this.generateUserWithTokens( - this.userService.saveUser(user), - request - ); - - } - - @Override - public UserDTO generateUserWithTokens(UserDTO user, HttpServletRequest request) { Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes()); - HashMap tokens = this.generateTokens(user, algorithm, request); + JWTVerifier verifier = JWT.require(algorithm).build(); + DecodedJWT decodedJWT = verifier.verify(refreshToken); + + UserDTO user = this.userService.getUser(decodedJWT.getSubject()); + user.setProfilePictureUrl( + this.storageService.getFileUrl(user.getUsername(), "profile") + .getPresignedUrl() + ); HttpSession httpSession = request.getSession(); - UserDTO userAuthenticated = user.toResponse(tokens.get("accessToken"), tokens.get("refreshToken")); + AuthDTO authenticatedUser = new AuthDTO( + user, + this.generateAccessToken(user, algorithm, request), + new TokenDTO( + refreshToken, + decodedJWT.getExpiresAt() + ) + ); + httpSession.setAttribute("user", authenticatedUser); - httpSession.setAttribute("user", userAuthenticated); + return authenticatedUser; - return userAuthenticated; } @Override - public UserDTO processOAuthPostLogin(@Valid UserDTO user, HttpServletRequest request) { + public AuthDTO processOAuthPostLogin(@Valid UserDTO user, HttpServletRequest request) { if (Objects.nonNull(user.getId())) { this.userService.alterUser(user.getId(), user); @@ -203,37 +232,48 @@ public class AuthServiceImpl implements AuthService { } return this.generateUserWithTokens(user, request); - } @Override - public void loginUser(HttpServletRequest request, HttpServletResponse response, @Valid UserDTO user) throws IOException { + public void loginOAuthUser(HttpServletRequest request, + HttpServletResponse response, + OAuth2User oauthUser) throws IOException { - UserDTO authenticatedUser = this.generateUserWithTokens( - user, + String clientId = this.getClientFromUrl(request.getRequestURL().toString()); + + OAuthMap oauthMap = this.generateOAuthMap(clientId, oauthUser); + + AuthDTO authObject = this.processOAuthPostLogin( + this.generateUserFromAuthUser(oauthMap, oauthUser), request ); response.setContentType(APPLICATION_JSON_VALUE); new ObjectMapper() - .writeValue(response.getOutputStream(), authenticatedUser); + .writeValue(response.getOutputStream(), authObject); } - public void loginOAuthUser(HttpServletRequest request, - HttpServletResponse response, - OAuth2User oauthUser) throws IOException { + @Override + public UserDTO getLoggedUser() { + String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return userService.getUser(username); + } - String[] url = request.getRequestURL().toString().split("/"); - String clientId = url[url.length-1]; + private String getClientFromUrl(String url) { + String[] urlPartition = url.split("/"); + return urlPartition[urlPartition.length - 1]; + } - OAuthMap oauthMap = null; + private OAuthMap generateOAuthMap(String clientId, OAuth2User oauthUser) { try { - oauthMap = (OAuthMap) OAuthMapEnum.byValue(clientId).getMap() + return (OAuthMap) OAuthMapper.byValue(clientId).getMap() .getDeclaredConstructor(OAuth2User.class).newInstance(oauthUser); } catch (Exception e) { - throw new BadRequestException("No Such Provider"); + throw new BadRequestException("Unsupported OAuth Client."); } + } + private UserDTO generateUserFromAuthUser(OAuthMap oauthMap, OAuth2User oauthUser) { UserDTO user = null; try { user = this.userService.getUser(oauthMap.getPrincipal()); @@ -246,16 +286,9 @@ public class AuthServiceImpl implements AuthService { .provider(oauthMap.getProvider()) .build(); } + user.setProfilePictureUrl(oauthMap.getProfilePicture()); - UserDTO authenticatedUser = this.processOAuthPostLogin( - user, - request - ); - - response.setContentType(APPLICATION_JSON_VALUE); - new ObjectMapper() - .writeValue(response.getOutputStream(), authenticatedUser); - + return user; } } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/session/api/SessionController.java b/src/main/java/com/hideyoshi/backendportfolio/base/session/api/SessionController.java index 580c864..f856a80 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/session/api/SessionController.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/session/api/SessionController.java @@ -1,5 +1,6 @@ package com.hideyoshi.backendportfolio.base.session.api; +import com.hideyoshi.backendportfolio.base.security.model.AuthDTO; import com.hideyoshi.backendportfolio.base.session.service.SessionManagerService; import com.hideyoshi.backendportfolio.base.user.model.UserDTO; import lombok.RequiredArgsConstructor; @@ -19,7 +20,7 @@ public class SessionController { private final SessionManagerService sessionManagerService; @GetMapping(path = "/validate") - public ResponseEntity validateCurrentSession(HttpSession session) { + public ResponseEntity validateCurrentSession(HttpSession session) { return ResponseEntity.ok(this.sessionManagerService.validateSession(session)); } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerService.java b/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerService.java index 6136149..add7bac 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerService.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerService.java @@ -1,12 +1,12 @@ package com.hideyoshi.backendportfolio.base.session.service; -import com.hideyoshi.backendportfolio.base.user.model.UserDTO; +import com.hideyoshi.backendportfolio.base.security.model.AuthDTO; import javax.servlet.http.HttpSession; public interface SessionManagerService { - UserDTO validateSession(HttpSession session); + AuthDTO validateSession(HttpSession session); void destroySession(HttpSession session); diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerServiceImpl.java b/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerServiceImpl.java index 36d8542..c21ee03 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerServiceImpl.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/session/service/SessionManagerServiceImpl.java @@ -1,30 +1,19 @@ package com.hideyoshi.backendportfolio.base.session.service; -import com.hideyoshi.backendportfolio.base.user.model.UserDTO; +import com.hideyoshi.backendportfolio.base.security.model.AuthDTO; import com.hideyoshi.backendportfolio.base.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import javax.servlet.http.HttpSession; -import java.util.Objects; @Service @RequiredArgsConstructor public class SessionManagerServiceImpl implements SessionManagerService { - private final UserService userService; - @Override - public UserDTO validateSession(HttpSession session) { - - UserDTO sessionObjects = (UserDTO) session.getAttribute("user"); - - if (Objects.nonNull(sessionObjects)) { - return this.userService.getUser(sessionObjects.getUsername()) - .toResponse(sessionObjects.getAccessToken(), sessionObjects.getRefreshToken()); - } - - return null; + public AuthDTO validateSession(HttpSession session) { + return (AuthDTO) session.getAttribute("user"); } @Override diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/user/api/UserController.java b/src/main/java/com/hideyoshi/backendportfolio/base/user/api/UserController.java index ef5f90c..e95c29a 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/user/api/UserController.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/user/api/UserController.java @@ -1,28 +1,28 @@ package com.hideyoshi.backendportfolio.base.user.api; +import com.hideyoshi.backendportfolio.base.security.model.AuthDTO; import com.hideyoshi.backendportfolio.base.security.service.AuthService; import com.hideyoshi.backendportfolio.base.user.model.TokenDTO; import com.hideyoshi.backendportfolio.base.user.model.UserDTO; import com.hideyoshi.backendportfolio.base.user.service.UserService; +import com.hideyoshi.backendportfolio.microservice.storageService.enums.FileTypeEnum; +import com.hideyoshi.backendportfolio.microservice.storageService.model.StorageServiceDownloadResponse; +import com.hideyoshi.backendportfolio.microservice.storageService.model.StorageServiceUploadResponse; +import com.hideyoshi.backendportfolio.microservice.storageService.service.StorageService; import com.hideyoshi.backendportfolio.util.guard.UserResourceGuard; import com.hideyoshi.backendportfolio.util.guard.UserResourceGuardEnum; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; -import java.io.IOException; import java.net.URI; -import java.security.Provider; import java.util.List; @Log4j2 @@ -36,6 +36,8 @@ public class UserController { private final AuthService authService; + private final StorageService storageService; + @GetMapping @UserResourceGuard(accessType = UserResourceGuardEnum.ADMIN_USER) public ResponseEntity> getUsers() { @@ -44,7 +46,7 @@ public class UserController { @PostMapping("/signup") @UserResourceGuard(accessType = UserResourceGuardEnum.OPEN) - public ResponseEntity signupUser(@RequestBody @Valid UserDTO user, HttpServletRequest request) { + public ResponseEntity signupUser(@RequestBody @Valid UserDTO user, HttpServletRequest request) { URI uri = URI.create( ServletUriComponentsBuilder .fromCurrentContextPath() @@ -55,20 +57,13 @@ public class UserController { @PostMapping("/login/refresh") @UserResourceGuard(accessType = UserResourceGuardEnum.OPEN) - public ResponseEntity refreshAccessToken( + public ResponseEntity refreshAccessToken( @RequestBody @Valid TokenDTO refreshToken, HttpServletRequest request, HttpServletResponse response) { return ResponseEntity.ok(this.authService.refreshAccessToken(refreshToken.getToken(), request, response)); } - @GetMapping("/login/callback") - @UserResourceGuard(accessType = UserResourceGuardEnum.OPEN) - public void oauthCallback(HttpServletResponse response) throws IOException { - log.info("Teste"); - response.sendRedirect("http://localhost:4200"); - } - @DeleteMapping("/delete/{id}") @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER) public ResponseEntity deleteUser(@PathVariable("id") Long id) { @@ -76,4 +71,27 @@ public class UserController { return new ResponseEntity<>(HttpStatus.NO_CONTENT); } + @PostMapping("/profile-picture") + @UserResourceGuard(accessType = UserResourceGuardEnum.USER) + public StorageServiceUploadResponse addProfilePicture( + @RequestParam FileTypeEnum fileType + ) { + UserDTO user = this.authService.getLoggedUser(); + return this.storageService.getNewFileUrl( + user.getUsername(), + "profile", + fileType + ); + } + + @PostMapping("/profile-picture/proccess") + @UserResourceGuard(accessType = UserResourceGuardEnum.USER) + public void processProfilePicture() { + UserDTO user = this.authService.getLoggedUser(); + this.storageService.processFile( + user.getUsername(), + "profile" + ); + } + } diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/user/model/TokenDTO.java b/src/main/java/com/hideyoshi/backendportfolio/base/user/model/TokenDTO.java index c293e12..e290f56 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/user/model/TokenDTO.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/user/model/TokenDTO.java @@ -1,6 +1,8 @@ package com.hideyoshi.backendportfolio.base.user.model; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.*; import javax.validation.constraints.NotNull; @@ -10,6 +12,8 @@ import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class TokenDTO implements Serializable { @NotNull(message = "Invalid AccessToken. Please Authenticate first.") diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/user/model/UserDTO.java b/src/main/java/com/hideyoshi/backendportfolio/base/user/model/UserDTO.java index 53d7ffc..9e4ce86 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/base/user/model/UserDTO.java +++ b/src/main/java/com/hideyoshi/backendportfolio/base/user/model/UserDTO.java @@ -53,9 +53,7 @@ public class UserDTO implements UserDetails { @Size(min=1) private List roles; - private TokenDTO accessToken; - - private TokenDTO refreshToken; + private String profilePictureUrl; private Provider provider; @@ -116,24 +114,13 @@ public class UserDTO implements UserDetails { } public UserDTO toResponse() { - return UserDTO.builder() - .name(this.name) - .email(this.email) - .username(this.username) - .provider(this.provider) - .build(); - } - - public UserDTO toResponse(TokenDTO accessToken, TokenDTO refreshToken) { return UserDTO.builder() .id(this.id) .name(this.name) .email(this.email) .username(this.username) .provider(this.provider) - .roles(this.roles) - .accessToken(accessToken) - .refreshToken(refreshToken) + .profilePictureUrl(this.profilePictureUrl) .build(); } diff --git a/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/config/StorageServiceConfig.java b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/config/StorageServiceConfig.java new file mode 100644 index 0000000..4e5a9ca --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/config/StorageServiceConfig.java @@ -0,0 +1,13 @@ +package com.hideyoshi.backendportfolio.microservice.storageService.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + + +@Getter +@Configuration +public class StorageServiceConfig { + @Value("${com.hideyoshi.microservice.storageServicePath}") + private String fileServicePath; +} diff --git a/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/enums/FileTypeEnum.java b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/enums/FileTypeEnum.java new file mode 100644 index 0000000..62f6606 --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/enums/FileTypeEnum.java @@ -0,0 +1,30 @@ +package com.hideyoshi.backendportfolio.microservice.storageService.enums; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.hideyoshi.backendportfolio.util.exception.BadRequestException; +import lombok.Getter; + +@Getter +@JsonFormat(shape = JsonFormat.Shape.STRING) +public enum FileTypeEnum { + PNG("png"), + + JPEG("jpeg"); + + + private final String fileExtension; + + FileTypeEnum(String fileExtension) { + this.fileExtension = fileExtension; + } + + public static FileTypeEnum fromValue(String value) { + for (FileTypeEnum e: FileTypeEnum.values()) { + if (e.getFileExtension().equals(value)) { + return e; + } + } + throw new BadRequestException("Invalid FileType."); + } + +} diff --git a/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/enums/FileTypeEnumConverter.java b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/enums/FileTypeEnumConverter.java new file mode 100644 index 0000000..49f2365 --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/enums/FileTypeEnumConverter.java @@ -0,0 +1,12 @@ +package com.hideyoshi.backendportfolio.microservice.storageService.enums; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.stereotype.Component; + +@Component +public class FileTypeEnumConverter implements Converter { + @Override + public FileTypeEnum convert(String value) { + return FileTypeEnum.fromValue(value); + } +} \ No newline at end of file diff --git a/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/model/StorageServiceDownloadResponse.java b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/model/StorageServiceDownloadResponse.java new file mode 100644 index 0000000..1ff869b --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/model/StorageServiceDownloadResponse.java @@ -0,0 +1,15 @@ +package com.hideyoshi.backendportfolio.microservice.storageService.model; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class StorageServiceDownloadResponse { + + @JsonProperty("presigned_url") + private String presignedUrl; + +} diff --git a/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/model/StorageServiceUploadResponse.java b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/model/StorageServiceUploadResponse.java new file mode 100644 index 0000000..6044aa6 --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/model/StorageServiceUploadResponse.java @@ -0,0 +1,20 @@ +package com.hideyoshi.backendportfolio.microservice.storageService.model; + + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NonNull; + +@Getter +@AllArgsConstructor +public class StorageServiceUploadResponse { + + @JsonProperty("presigned_url") + private String presignedUrl; + + @JsonProperty("file_key") + private String fileKey; + +} diff --git a/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/service/StorageService.java b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/service/StorageService.java new file mode 100644 index 0000000..075d27e --- /dev/null +++ b/src/main/java/com/hideyoshi/backendportfolio/microservice/storageService/service/StorageService.java @@ -0,0 +1,166 @@ +package com.hideyoshi.backendportfolio.microservice.storageService.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.hideyoshi.backendportfolio.microservice.storageService.config.StorageServiceConfig; +import com.hideyoshi.backendportfolio.microservice.storageService.enums.FileTypeEnum; +import com.hideyoshi.backendportfolio.microservice.storageService.model.StorageServiceDownloadResponse; +import com.hideyoshi.backendportfolio.microservice.storageService.model.StorageServiceUploadResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.DefaultRedirectStrategy; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.LaxRedirectStrategy; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class StorageService { + + private final ObjectMapper objectMapper; + + private final StorageServiceConfig storageServiceConfig; + + private final String PARAMETER_USERNAME = "username"; + + private final String PARAMETER_FILE_POSTFIX = "file_postfix"; + + private final String PARAMETER_FILE_TYPE = "file_type"; + + private final String PARAMETER_KEY_STRING = "string_url"; + + public StorageServiceUploadResponse getNewFileUrl(String username, String filePostfix, FileTypeEnum fileTypeEnum) { + HashMap values = new HashMap<>() {{ + put(PARAMETER_USERNAME, username); + put(PARAMETER_FILE_POSTFIX, filePostfix); + put(PARAMETER_FILE_TYPE, fileTypeEnum.getFileExtension()); + }}; + + String requestBody = null; + try { + requestBody = objectMapper + .writeValueAsString(values); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + HttpPost request = new HttpPost(URI.create(storageServiceConfig.getFileServicePath() + "/new_file_url")); + request.setHeader("Content-Type", "application/json"); + + try { + request.setEntity(new ByteArrayEntity(requestBody.getBytes("UTF-8"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setRedirectStrategy(new LaxRedirectStrategy()).build(); + + try { + return httpClient.execute( + request, + response -> { + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + return objectMapper.readValue(responseString, StorageServiceUploadResponse.class); + } + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public StorageServiceDownloadResponse getFileUrl(String username, String filePostfix) { + URI uri = null; + try { + uri = new URIBuilder(storageServiceConfig.getFileServicePath() + "/file_url") + .addParameter(PARAMETER_USERNAME, username) + .addParameter(PARAMETER_FILE_POSTFIX, filePostfix) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + HttpGet request = new HttpGet(uri); + request.setHeader("Content-Type", "application/json"); + + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setRedirectStrategy(new LaxRedirectStrategy()).build(); + + try { + return httpClient.execute( + request, + response -> { + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + return objectMapper.readValue(responseString, StorageServiceDownloadResponse.class); + } + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void processFile(String username, String filePostfix) { + HashMap values = new HashMap<>() {{ + put(PARAMETER_USERNAME, username); + put(PARAMETER_FILE_POSTFIX, filePostfix); + }}; + + ObjectMapper objectMapper = new ObjectMapper(); + + String requestBody = null; + try { + requestBody = objectMapper + .writeValueAsString(values); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + HttpPost request = new HttpPost(URI.create(storageServiceConfig.getFileServicePath() + "/process_file")); + request.setHeader("Content-Type", "application/json"); + + try { + request.setEntity(new ByteArrayEntity(requestBody.getBytes("UTF-8"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setRedirectStrategy(new LaxRedirectStrategy()).build(); + + try { + httpClient.execute( + request, + response -> { + String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); + return objectMapper.readValue(responseString, StorageServiceUploadResponse.class); + } + ); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/com/hideyoshi/backendportfolio/util/guard/UserResourceGuardEnum.java b/src/main/java/com/hideyoshi/backendportfolio/util/guard/UserResourceGuardEnum.java index 1cc77e1..ce08014 100644 --- a/src/main/java/com/hideyoshi/backendportfolio/util/guard/UserResourceGuardEnum.java +++ b/src/main/java/com/hideyoshi/backendportfolio/util/guard/UserResourceGuardEnum.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.hideyoshi.backendportfolio.base.user.entity.Role; import com.hideyoshi.backendportfolio.base.user.model.UserDTO; import com.hideyoshi.backendportfolio.base.user.service.UserService; +import lombok.Getter; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.servlet.HandlerMapping; @@ -11,6 +12,7 @@ import org.springframework.web.servlet.HandlerMapping; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; +@Getter public enum UserResourceGuardEnum { USER("user") { @@ -64,10 +66,6 @@ public enum UserResourceGuardEnum { ObjectMapper objectMapper, HttpServletRequest request); - public String getAccessType() { - return this.accessType; - } - public static UserResourceGuardEnum byValue(String accessType) { for (UserResourceGuardEnum o : values()) { if (o.getAccessType().equals(accessType)) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ffe8e02..2df6c08 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,6 +10,9 @@ com: username: ${DEFAULT_USER_USERNAME} password: ${DEFAULT_USER_PASSWORD} + microservice: + storageServicePath: ${STORAGE_SERVICE_PATH} + server: servlet: diff --git a/src/test/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImplTest.java b/src/test/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImplTest.java index 5d18242..03ad33c 100644 --- a/src/test/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImplTest.java +++ b/src/test/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImplTest.java @@ -40,8 +40,6 @@ class UserServiceImplTest { private PasswordEncoder passwordEncoder; - private AuthService authService; - @BeforeEach void setUp() {