diff --git a/pom.xml b/pom.xml
index 680343c..6285b0f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.boot
spring-boot-starter-parent
- 2.7.1
+ 2.7.5
com.hideyoshi
@@ -24,7 +24,7 @@
org.springframework.boot
spring-boot-starter-validation
- 2.7.3
+ 2.7.5
org.springframework.boot
@@ -34,6 +34,10 @@
org.springframework.boot
spring-boot-starter-security
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
org.springframework.session
spring-session-core
@@ -46,7 +50,7 @@
com.auth0
java-jwt
- 4.0.0
+ 4.2.1
org.postgresql
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/config/CorsConfig.java b/src/main/java/com/hideyoshi/backendportfolio/base/config/CorsConfig.java
index 04d9d68..636afd7 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/config/CorsConfig.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/config/CorsConfig.java
@@ -1,5 +1,6 @@
package com.hideyoshi.backendportfolio.base.config;
+import antlr.actions.python.CodeLexer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -8,12 +9,13 @@ import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
@Configuration
public class CorsConfig {
- @Value("${com.hideyoshi.frontEndPath}")
+ @Value("${com.hideyoshi.frontendPath}")
private String FRONTEND_PATH;
@Value("${com.hideyoshi.frontendConnectionType}")
@@ -32,10 +34,12 @@ public class CorsConfig {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of(connectionProtocol + FRONTEND_PATH));
- configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
- configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
- configuration.setAllowCredentials(true);
+ configuration.setAllowedMethods(Collections.singletonList("*"));
+ configuration.setAllowedHeaders(Collections.singletonList("*"));
+// configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
+// configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
configuration.setExposedHeaders(List.of("x-auth-token"));
+ configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/config/DefaultUserConfig.java b/src/main/java/com/hideyoshi/backendportfolio/base/config/DefaultUserConfig.java
index 2b30817..6c1b2a7 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/config/DefaultUserConfig.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/config/DefaultUserConfig.java
@@ -1,5 +1,6 @@
package com.hideyoshi.backendportfolio.base.config;
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
@@ -30,10 +31,11 @@ public class DefaultUserConfig {
CommandLineRunner run(UserService userService, UserRepository userRepo) {
return args -> {
UserDTO defaultUser = UserDTO.builder()
- .fullname(ADMIN_NAME)
+ .name(ADMIN_NAME)
.email(ADMIN_EMAIL)
.username(ADMIN_USERNAME)
.password(ADMIN_PASSWORD)
+ .provider(Provider.LOCAL)
.roles(new ArrayList<>())
.build();
if (!userRepo.findByUsername(defaultUser.getUsername()).isPresent()) {
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java
index f02c3de..42dc350 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/SecurityConfig.java
@@ -3,29 +3,31 @@ package com.hideyoshi.backendportfolio.base.security;
import com.hideyoshi.backendportfolio.base.config.RestAuthenticationEntryPointConfig;
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthenticationFilter;
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthorizationFilter;
+import com.hideyoshi.backendportfolio.base.security.oauth.repo.OAuthRequestRepository;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
+import com.hideyoshi.backendportfolio.util.exception.AuthenticationInvalidException;
import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
+import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-import org.springframework.web.servlet.HandlerExceptionResolver;
-import java.util.Arrays;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+@Log4j2
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@@ -37,6 +39,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final BCryptPasswordEncoder passwordEncoder;
+ private final OAuthRequestRepository oAuthRequestRepository;
+
private final RestAuthenticationEntryPointConfig restAuthenticationEntryPointConfig;
@Override
@@ -48,22 +52,64 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
+ http.cors().and().csrf().disable();
+
+ this.addSecurityToHttp(http);
+ this.addOAuthSecurityToHttp(http);
+ }
+
+ private void addSecurityToHttp(HttpSecurity http) throws Exception {
+
CustomAuthenticationFilter customAuthenticationFilter =
new CustomAuthenticationFilter(this.authenticationManager(), this.authService, this.restAuthenticationEntryPointConfig);
customAuthenticationFilter.setFilterProcessesUrl("/user/login");
- http.cors().and().csrf().disable()
- .authorizeRequests().antMatchers("/session/**").permitAll()
- .and().authorizeRequests().antMatchers("/user/signup").permitAll()
- .and().authorizeRequests().antMatchers("/user/login/refresh").permitAll()
- .and().authorizeRequests().antMatchers("/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
- .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
- .and().addFilter(customAuthenticationFilter)
- .addFilterBefore(new CustomAuthorizationFilter(this.authService), UsernamePasswordAuthenticationFilter.class);
+ http.authorizeRequests()
+ .antMatchers("/session/**").permitAll()
+ .and().authorizeRequests().antMatchers("/user/signup").permitAll()
+ .and().authorizeRequests().antMatchers("/user/oauth/**").permitAll()
+ .and().authorizeRequests().antMatchers("/user/login/**").permitAll()
+ .and().authorizeRequests().antMatchers("/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
+
+ .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
+ .and().addFilter(customAuthenticationFilter)
+
+ .addFilterBefore(new CustomAuthorizationFilter(this.authService), UsernamePasswordAuthenticationFilter.class);
}
+ private void addOAuthSecurityToHttp(HttpSecurity http) throws Exception {
+
+ http.oauth2Login()
+ .authorizationEndpoint()
+ .authorizationRequestRepository(this.oAuthRequestRepository)
+ .and().successHandler(this::successHandler)
+ .and().exceptionHandling()
+ .authenticationEntryPoint(this::authenticationEntryPoint);
+ }
+
+ private void successHandler(HttpServletRequest request,
+ HttpServletResponse response,
+ Authentication authentication ) throws IOException {
+
+ OAuth2User oauthUser = (OAuth2User) authentication.getPrincipal();
+
+ this.authService.loginOAuthUser(
+ request,
+ response,
+ oauthUser
+ );
+
+ }
+
+ private void authenticationEntryPoint(HttpServletRequest request,
+ HttpServletResponse response,
+ AuthenticationException authentication ) {
+ throw new AuthenticationInvalidException(authentication.getMessage());
+ }
+
+
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthenticationFilter.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthenticationFilter.java
index a265181..2beac74 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthenticationFilter.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthenticationFilter.java
@@ -7,6 +7,7 @@ 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 lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@@ -57,18 +58,12 @@ public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFi
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException {
- UserDTO user = (UserDTO) authentication.getPrincipal();
- Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
+ this.authService.loginUser(
+ request,
+ response,
+ (UserDTO) authentication.getPrincipal()
+ );
- HashMap tokens = this.authService.generateTokens(user, algorithm, request);
-
- HttpSession httpSession = request.getSession();
- UserDTO authenticatedUser = user.toResponse(tokens.get("accessToken"), tokens.get("refreshToken"));
- httpSession.setAttribute("user", authenticatedUser);
-
- response.setContentType(APPLICATION_JSON_VALUE);
- new ObjectMapper()
- .writeValue(response.getOutputStream(), authenticatedUser);
}
}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthorizationFilter.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthorizationFilter.java
index 012d5e8..1bb8d00 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthorizationFilter.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/filter/CustomAuthorizationFilter.java
@@ -2,24 +2,16 @@ package com.hideyoshi.backendportfolio.base.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
-import com.hideyoshi.backendportfolio.util.exception.BadRequestException;
-import lombok.extern.log4j.Log4j2;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.annotation.Bean;
-import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
-import org.springframework.web.servlet.HandlerExceptionResolver;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.HttpStatus.FORBIDDEN;
@@ -38,7 +30,7 @@ public class CustomAuthorizationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
- if (request.getServletPath().equals("/user/login")) {
+ if (this.isPathNotProtected(request.getServletPath())) {
filterChain.doFilter(request, response);
} else {
String authorizationHeader = request.getHeader(AUTHORIZATION);
@@ -69,4 +61,13 @@ public class CustomAuthorizationFilter extends OncePerRequestFilter {
}
}
+ private Boolean isPathNotProtected(String path) {
+
+ List notProtectedPaths = Arrays.asList(
+ "/user/login"
+ );
+
+ return notProtectedPaths.contains(path);
+ }
+
}
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
new file mode 100644
index 0000000..7f27aa4
--- /dev/null
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GithubOAuthMap.java
@@ -0,0 +1,23 @@
+package com.hideyoshi.backendportfolio.base.security.oauth.mapper;
+
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
+import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+@AllArgsConstructor
+public class GithubOAuthMap implements OAuthMap {
+
+ private OAuth2User oAuth2User;
+
+ @Override
+ public String getPrincipal() {
+ return oAuth2User.getAttribute("login");
+ }
+
+ @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
new file mode 100644
index 0000000..9aa4ffe
--- /dev/null
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/GoogleOAuthMap.java
@@ -0,0 +1,24 @@
+package com.hideyoshi.backendportfolio.base.security.oauth.mapper;
+
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
+import lombok.AllArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+@AllArgsConstructor
+public class GoogleOAuthMap implements OAuthMap {
+
+ private OAuth2User oauthUser;
+
+ @Override
+ public String getPrincipal() {
+ return this.oauthUser.getAttribute("given_name");
+ }
+
+ @Override
+ public Provider getProvider() {
+ 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
new file mode 100644
index 0000000..ff607c3
--- /dev/null
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMap.java
@@ -0,0 +1,12 @@
+package com.hideyoshi.backendportfolio.base.security.oauth.mapper;
+
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+public interface OAuthMap {
+
+ String getPrincipal();
+
+ 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/OAuthMapEnum.java
new file mode 100644
index 0000000..aad4182
--- /dev/null
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/mapper/OAuthMapEnum.java
@@ -0,0 +1,37 @@
+package com.hideyoshi.backendportfolio.base.security.oauth.mapper;
+
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
+
+public enum OAuthMapEnum {
+
+ GOOGLE(GoogleOAuthMap.class, Provider.GOOGLE),
+
+ GITHUB(GithubOAuthMap.class, Provider.GITHUB);
+
+ private Class oAuthMap;
+
+ private Provider provider;
+
+ private OAuthMapEnum(Class oAuthMap, Provider provider) {
+ this.oAuthMap = oAuthMap;
+ this.provider = provider;
+ }
+
+ public Class getMap() {
+ return oAuthMap;
+ }
+
+ public Provider getProvider() {
+ return provider;
+ }
+
+ public static OAuthMapEnum byValue(String name) {
+ for (OAuthMapEnum e : values()) {
+ if (e.getProvider().getName().equals(name)) {
+ return e;
+ }
+ }
+ throw new IllegalArgumentException("Argument not valid.");
+ }
+
+}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/repo/OAuthRequestRepository.java b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/repo/OAuthRequestRepository.java
new file mode 100644
index 0000000..6df7e6f
--- /dev/null
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/security/oauth/repo/OAuthRequestRepository.java
@@ -0,0 +1,70 @@
+package com.hideyoshi.backendportfolio.base.security.oauth.repo;
+
+import lombok.extern.log4j.Log4j2;
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.stereotype.Repository;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Objects;
+
+@Log4j2
+@Repository
+public class OAuthRequestRepository implements AuthorizationRequestRepository {
+
+ @Override
+ public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
+
+ String state = request.getParameter("state");
+ log.info(state);
+
+ if (Objects.nonNull(state)) {
+ return removeAuthorizationRequest(request);
+ }
+ return null;
+ }
+
+ @Override
+ public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
+
+ String state = authorizationRequest.getState();
+ log.info(state);
+
+ request.getSession().setAttribute(
+ String.format("state_%s", state),
+ authorizationRequest
+ );
+
+ }
+
+ @Override
+ public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
+
+ String state = request.getParameter("state");
+
+ OAuth2AuthorizationRequest authorizationRequest = null;
+ if (Objects.nonNull(state)) {
+ authorizationRequest = this.getAuthorizationRequestFromSession(request, state);
+ }
+
+ if (Objects.nonNull(authorizationRequest)) {
+ removeAuthorizationRequestFromSession(request, state);
+ return authorizationRequest;
+ }
+ return null;
+ }
+
+ private OAuth2AuthorizationRequest getAuthorizationRequestFromSession(HttpServletRequest request, String state) {
+ return (OAuth2AuthorizationRequest) request.getSession().getAttribute(
+ String.format("state_%s", state)
+ );
+ }
+
+ private void removeAuthorizationRequestFromSession(HttpServletRequest request, String state) {
+ request.getSession().removeAttribute(
+ String.format("state_%s", state)
+ );
+ }
+
+}
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 5194340..5a8561c 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
@@ -4,10 +4,12 @@ import com.auth0.jwt.algorithms.Algorithm;
import com.hideyoshi.backendportfolio.base.user.model.TokenDTO;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.oauth2.core.user.OAuth2User;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
+import java.io.IOException;
import java.util.HashMap;
public interface AuthService {
@@ -24,4 +26,12 @@ public interface AuthService {
UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request);
+ UserDTO generateUserWithTokens(UserDTO user, HttpServletRequest request);
+
+ UserDTO 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;
+
}
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 7ce5f27..1d0bcd6 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
@@ -4,6 +4,11 @@ import com.auth0.jwt.JWT;
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.oauth.mapper.OAuthMap;
+import com.hideyoshi.backendportfolio.base.security.oauth.mapper.OAuthMapEnum;
+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;
@@ -16,6 +21,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.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.HandlerExceptionResolver;
@@ -23,10 +29,12 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
+import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import static java.util.Arrays.stream;
+import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@Log4j2
@Service
@@ -66,6 +74,7 @@ public class AuthServiceImpl implements AuthService {
.sign(algorithm);
return new TokenDTO(accessToken, expirationDate);
+
}
@Override
@@ -160,16 +169,93 @@ public class AuthServiceImpl implements AuthService {
@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());
- UserDTO userSaved = this.userService.saveUser(user);
- HashMap tokens = this.generateTokens(userSaved, algorithm, request);
+ HashMap tokens = this.generateTokens(user, algorithm, request);
HttpSession httpSession = request.getSession();
- UserDTO userAuthenticated = userSaved.toResponse(tokens.get("accessToken"), tokens.get("refreshToken"));
+ UserDTO userAuthenticated = user.toResponse(tokens.get("accessToken"), tokens.get("refreshToken"));
+
httpSession.setAttribute("user", userAuthenticated);
return userAuthenticated;
}
+ @Override
+ public UserDTO processOAuthPostLogin(@Valid UserDTO user, HttpServletRequest request) {
+
+ if (Objects.nonNull(user.getId())) {
+ this.userService.alterUser(user.getId(), user);
+ } else {
+ this.userService.saveUser(user);
+ }
+
+ return this.generateUserWithTokens(user, request);
+
+ }
+
+ @Override
+ public void loginUser(HttpServletRequest request, HttpServletResponse response, @Valid UserDTO user) throws IOException {
+
+ UserDTO authenticatedUser = this.generateUserWithTokens(
+ user,
+ request
+ );
+
+ response.setContentType(APPLICATION_JSON_VALUE);
+ new ObjectMapper()
+ .writeValue(response.getOutputStream(), authenticatedUser);
+ }
+
+ public void loginOAuthUser(HttpServletRequest request,
+ HttpServletResponse response,
+ OAuth2User oauthUser) throws IOException {
+
+ String[] url = request.getRequestURL().toString().split("/");
+ String clientId = url[url.length-1];
+
+ OAuthMap oauthMap = null;
+ try {
+ oauthMap = (OAuthMap) OAuthMapEnum.byValue(clientId).getMap()
+ .getDeclaredConstructor(OAuth2User.class).newInstance(oauthUser);
+ } catch (Exception e) {
+ throw new BadRequestException("No Such Provider");
+ }
+
+ UserDTO user = null;
+ try {
+ user = this.userService.getUser(oauthMap.getPrincipal());
+ } catch (BadRequestException e) {
+ user = UserDTO.builder()
+ .name(oauthUser.getAttribute("name"))
+ .username(oauthMap.getPrincipal())
+ .email(oauthUser.getAttribute("email"))
+ .roles(Arrays.asList(Role.USER))
+ .provider(oauthMap.getProvider())
+ .build();
+ }
+
+ UserDTO authenticatedUser = this.processOAuthPostLogin(
+ user,
+ request
+ );
+
+ response.setContentType(APPLICATION_JSON_VALUE);
+ new ObjectMapper()
+ .writeValue(response.getOutputStream(), authenticatedUser);
+
+ }
+
}
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 9abb532..f94bb5e 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
@@ -10,15 +10,19 @@ 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.validation.BindingResult;
+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
@@ -49,34 +53,6 @@ public class UserController {
return ResponseEntity.created(uri).body(this.authService.signupUser(user, request));
}
- @PostMapping("/delete/{id}")
- @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
- public ResponseEntity deleteUser(@PathVariable("id") Long id) {
- this.userService.deleteUser(id);
- return new ResponseEntity<>(HttpStatus.NO_CONTENT);
- }
-//
-// @PostMapping("/alter/{id}")
-// @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
-// public ResponseEntity alterUser(@PathVariable("id") Long id, @RequestBody @Valid UserDTO user) {
-// this.userService.alterUser(id, user);
-// return new ResponseEntity<>(HttpStatus.NO_CONTENT);
-// }
-//
-// @PostMapping("/alter/{id}/role/add")
-// @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
-// public ResponseEntity> addRoleToUser(@PathVariable("id") Long id, @RequestBody RoleToUserDTO filter) {
-// userService.addRoleToUser(id, filter.getRoleName());
-// return ResponseEntity.ok().build();
-// }
-//
-// @PostMapping("/alter/{id}/role/delete")
-// @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
-// public ResponseEntity> deleteRoleToUser(@PathVariable("id") Long id, @RequestBody RoleToUserDTO filter) {
-// userService.removeRoleFromUser(id, filter.getRoleName());
-// return ResponseEntity.ok().build();
-// }
-
@PostMapping("/login/refresh")
@UserResourceGuard(accessType = UserResourceGuardEnum.OPEN)
public ResponseEntity refreshAccessToken(
@@ -86,4 +62,18 @@ public class UserController {
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");
+ }
+
+ @PostMapping("/delete/{id}")
+ @UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
+ public ResponseEntity deleteUser(@PathVariable("id") Long id) {
+ this.userService.deleteUser(id);
+ return new ResponseEntity<>(HttpStatus.NO_CONTENT);
+ }
+
}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/Provider.java b/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/Provider.java
index df2cea1..25db4a3 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/Provider.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/Provider.java
@@ -4,7 +4,10 @@ public enum Provider {
GOOGLE("google"),
+ GITHUB("github"),
+
LOCAL("local");
+
private String name;
Provider(String name) {
@@ -15,4 +18,13 @@ public enum Provider {
return name;
}
+ public static Provider byValue(String name) {
+ for (Provider p : values()) {
+ if (p.getName().equals(name)) {
+ return p;
+ }
+ }
+ throw new IllegalArgumentException("Argument not valid.");
+ }
+
}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/User.java b/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/User.java
index 4470b66..a01d52f 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/User.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/user/entity/User.java
@@ -25,10 +25,10 @@ public class User {
private Long id;
@Column(
- name = "full_name",
+ name = "name",
nullable = false
)
- private String fullname;
+ private String name;
@Column(
name = "email",
@@ -52,6 +52,12 @@ public class User {
)
private String password;
+ @Column(
+ name = "provider",
+ nullable = false
+ )
+ private String provider;
+
@Column(
name = "roles",
nullable = false
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 51370eb..53d7ffc 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
@@ -36,7 +36,7 @@ public class UserDTO implements UserDetails {
private Long id;
@NotEmpty
- private String fullname;
+ private String name;
@NotEmpty
@ValidEmail
@@ -59,104 +59,82 @@ public class UserDTO implements UserDetails {
private Provider provider;
- public UserDTO(
- String fullname,
- String email,
- String username,
- String password
- ) {
- this.fullname = fullname;
- this.email = email;
- this.username = username;
- this.password = password;
- this.roles = List.of(Role.USER);
- }
-
- public UserDTO(
- String fullname,
- String email,
- String username,
- String password,
- List roles
- ) {
- this.fullname = fullname;
- this.email = email;
- this.username = username;
- this.password = password;
- this.roles = roles;
- }
-
public UserDTO(User entity) {
this.id = entity.getId();
- this.fullname = entity.getFullname();
+ this.name = entity.getName();
this.email = entity.getEmail();
this.username = entity.getUsername();
this.password = entity.getPassword();
+ this.provider = Provider.byValue(entity.getProvider());
this.roles = entity.getRoles();
}
public User toEntity() {
return new User(
this.id,
- this.fullname,
+ this.name,
this.email,
this.username,
this.password,
+ this.provider.getName(),
Objects.nonNull(this.roles) ? this.roles.stream()
.map(role -> role.getDescription())
.collect(Collectors.joining("&")) : Role.USER.getDescription()
);
}
- @JsonIgnore
@Override
+ @JsonIgnore
public Collection extends GrantedAuthority> getAuthorities() {
- return this.roles.stream()
+ return this.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getDescription()))
.collect(Collectors.toList());
}
- @JsonIgnore
@Override
+ @JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
- @JsonIgnore
@Override
+ @JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
- @JsonIgnore
@Override
+ @JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
- @JsonIgnore
@Override
+ @JsonIgnore
public boolean isEnabled() {
return true;
}
public UserDTO toResponse() {
return UserDTO.builder()
- .fullname(this.fullname)
+ .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)
- .fullname(this.fullname)
+ .name(this.name)
.email(this.email)
.username(this.username)
+ .provider(this.provider)
.roles(this.roles)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
+
}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImpl.java b/src/main/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImpl.java
index 3af3254..9573a1c 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImpl.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/base/user/service/UserServiceImpl.java
@@ -34,9 +34,13 @@ public class UserServiceImpl implements UserService {
throw new BadRequestException(String.format("User %s already exists. Try another UserName.", userOnDB.getUsername()));
});
- log.info(String.format("Saving to the database user of name: %s", user.getFullname()));
+ log.info(String.format("Saving to the database user of name: %s", user.getName()));
- user.setPassword(passwordEncoder.encode(user.getPassword()));
+ if (Objects.nonNull(user.getPassword())) {
+ user.setPassword(passwordEncoder.encode(user.getPassword()));
+ } else {
+ user.setPassword("");
+ }
UserDTO userSaved = new UserDTO(userRepo.save(user.toEntity()));
if (!userSaved.getRoles().contains(Role.USER)) {
@@ -81,10 +85,10 @@ public class UserServiceImpl implements UserService {
log.info(String.format("Adding to user %s the role %s",
userOnDB.getUsername(), newAuthority.getDescription()));
- if (roles.add(newAuthority)) {
- userOnDB.setRoles(roles);
- this.alterUser(userOnDB.getId(), userOnDB);
- }
+ roles.add(newAuthority);
+ userOnDB.setRoles(roles);
+
+ this.alterUser(userOnDB.getId(), userOnDB);
}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/PasswordValidator.java b/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/PasswordValidator.java
index e63d3c1..4def643 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/PasswordValidator.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/PasswordValidator.java
@@ -1,5 +1,6 @@
package com.hideyoshi.backendportfolio.util.validator.password;
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
import lombok.RequiredArgsConstructor;
import javax.validation.ConstraintValidator;
@@ -9,17 +10,25 @@ import java.util.regex.Pattern;
@RequiredArgsConstructor
public class PasswordValidator implements ConstraintValidator {
+ Provider provider;
+
private final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
@Override
- public void initialize(ValidPassword constraintAnnotation) {}
+ public void initialize(ValidPassword constraintAnnotation) {
+ this.provider = constraintAnnotation.provider();
+ }
@Override
public boolean isValid(String password, ConstraintValidatorContext context) {
- return Pattern.compile(PASSWORD_PATTERN)
- .matcher(password)
- .matches();
+ if (this.provider.equals(Provider.GOOGLE)) {
+ return true;
+ } else {
+ return Pattern.compile(PASSWORD_PATTERN)
+ .matcher(password)
+ .matches();
+ }
}
}
diff --git a/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/ValidPassword.java b/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/ValidPassword.java
index 35cc646..32e773c 100644
--- a/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/ValidPassword.java
+++ b/src/main/java/com/hideyoshi/backendportfolio/util/validator/password/ValidPassword.java
@@ -1,5 +1,7 @@
package com.hideyoshi.backendportfolio.util.validator.password;
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
+
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
@@ -9,13 +11,16 @@ import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
-@Target({TYPE, FIELD, ANNOTATION_TYPE})
-@Retention(RUNTIME)
-@Constraint(validatedBy = PasswordValidator.class)
@Documented
+@Retention(RUNTIME)
+@Target({TYPE, FIELD, ANNOTATION_TYPE})
+@Constraint(validatedBy = PasswordValidator.class)
public @interface ValidPassword {
- String message() default "Invalid password";
+ String message() default
+ "The password must have at least: a special character, a number, a uppercase and a lowercase ";
+
+ Provider provider() default Provider.LOCAL;
Class>[] groups() default {};
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 4af4807..d0849fc 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,6 +1,6 @@
com:
hideyoshi:
- frontEndPath: ${FRONTEND_PATH}
+ frontendPath: ${FRONTEND_PATH}
frontendConnectionType: ${FRONTEND_CONNECTION_TYPE}
tokenSecret: ${TOKEN_SECRET}
accessTokenDuration: ${ACCESS_TOKEN_DURATION}
@@ -17,6 +17,26 @@ server:
spring:
+ security:
+ oauth2:
+ client:
+ registration:
+
+ google:
+ clientId: ${GOOGLE_CLIENT_ID}
+ clientSecret: ${GOOGLE_CLIENT_SECRET}
+ redirectUri: ${GOOGLE_REDIRECT_URL}
+ scope:
+ - email
+ - profile
+
+ github:
+ clientId: ${GITHUB_CLIENT_ID}
+ clientSecret: ${GITHUB_CLIENT_SECRET}
+ redirectUri: ${GITHUB_REDIRECT_URL}
+ scope:
+ - user
+
datasource:
url: jdbc:${DATABASE_URL}
username: ${DATABASE_USERNAME}
diff --git a/src/main/resources/db/changelog/client/db.changelog-client.yml b/src/main/resources/db/changelog/client/db.changelog-client.yml
index 3ba2038..db79302 100644
--- a/src/main/resources/db/changelog/client/db.changelog-client.yml
+++ b/src/main/resources/db/changelog/client/db.changelog-client.yml
@@ -8,4 +8,14 @@ databaseChangeLog:
encoding: utf8
path: sqls/db-table-model-client.sql
relativeToChangelogFile: true
- dbms: postgresql
\ No newline at end of file
+ dbms: postgresql
+
+ - changeSet:
+ id: adds-user-provider
+ author: vitor.h.n.batista@gmail.com
+ changes:
+ - sqlFile:
+ encoding: utf8
+ path: sqls/adds-user-provider.sql
+ relativeToChangelogFile: true
+ dbms: postgresql
diff --git a/src/main/resources/db/changelog/client/sqls/adds-user-provider.sql b/src/main/resources/db/changelog/client/sqls/adds-user-provider.sql
new file mode 100644
index 0000000..4c2dd2b
--- /dev/null
+++ b/src/main/resources/db/changelog/client/sqls/adds-user-provider.sql
@@ -0,0 +1,14 @@
+alter table if exists auth."user"
+ rename column "full_name" to "name";
+
+ALTER TABLE IF EXISTS auth.user
+ ADD COLUMN IF NOT EXISTS provider VARCHAR
+ CHECK ( provider IN ('google', 'github', 'local') ) DEFAULT 'local' NOT NULL;
+
+ALTER TABLE auth."user"
+ DROP CONSTRAINT IF EXISTS client_email_unique;
+
+ALTER TABLE auth."user"
+ DROP CONSTRAINT IF EXISTS user_email_provider_unique;
+ALTER TABLE auth."user"
+ ADD CONSTRAINT user_email_provider_unique UNIQUE (email, provider);
\ No newline at end of file
diff --git a/src/test/java/com/hideyoshi/backendportfolio/base/user/repo/UserRepositoryTest.java b/src/test/java/com/hideyoshi/backendportfolio/base/user/repo/UserRepositoryTest.java
index 0e526c4..0ff60f2 100644
--- a/src/test/java/com/hideyoshi/backendportfolio/base/user/repo/UserRepositoryTest.java
+++ b/src/test/java/com/hideyoshi/backendportfolio/base/user/repo/UserRepositoryTest.java
@@ -1,5 +1,6 @@
package com.hideyoshi.backendportfolio.base.user.repo;
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
@@ -58,13 +59,14 @@ class UserRepositoryTest {
}
private User createEntity() {
- return new UserDTO(
- "Clark Kent",
- "superman@gmail.com",
- "Superman",
- "password",
- List.of(Role.USER)
- ).toEntity();
+ return UserDTO.builder()
+ .name("Clark Kent")
+ .email("superman@gmail.com")
+ .username("Superman")
+ .password("password")
+ .provider(Provider.LOCAL)
+ .roles(List.of(Role.USER))
+ .build().toEntity();
}
}
\ No newline at end of file
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 033a427..5d18242 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
@@ -1,6 +1,7 @@
package com.hideyoshi.backendportfolio.base.user.service;
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
+import com.hideyoshi.backendportfolio.base.user.entity.Provider;
import com.hideyoshi.backendportfolio.base.user.entity.Role;
import com.hideyoshi.backendportfolio.base.user.entity.User;
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
@@ -349,15 +350,15 @@ class UserServiceImplTest {
}
private UserDTO createUser() {
- UserDTO userCreated = new UserDTO(
- "Clark Kent",
- "superman@gmail.com",
- "Superman",
- "password",
- List.of(Role.USER)
- );
- userCreated.setId(1L);
- return userCreated;
+ return UserDTO.builder()
+ .id(1L)
+ .name("Clark Kent")
+ .email("superman@gmail.com")
+ .username("Superman")
+ .password("password")
+ .provider(Provider.LOCAL)
+ .roles(List.of(Role.USER))
+ .build();
}
}
\ No newline at end of file