Merge pull request #2 from HideyoshiNakazone/devel
[v0.0.2] Adds Google and Github OAuth2 Authentication
This commit is contained in:
10
pom.xml
10
pom.xml
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.7.1</version>
|
<version>2.7.5</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.hideyoshi</groupId>
|
<groupId>com.hideyoshi</groupId>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
<version>2.7.3</version>
|
<version>2.7.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -34,6 +34,10 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.session</groupId>
|
<groupId>org.springframework.session</groupId>
|
||||||
<artifactId>spring-session-core</artifactId>
|
<artifactId>spring-session-core</artifactId>
|
||||||
@@ -46,7 +50,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.auth0</groupId>
|
<groupId>com.auth0</groupId>
|
||||||
<artifactId>java-jwt</artifactId>
|
<artifactId>java-jwt</artifactId>
|
||||||
<version>4.0.0</version>
|
<version>4.2.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.postgresql</groupId>
|
<groupId>org.postgresql</groupId>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.hideyoshi.backendportfolio.base.config;
|
package com.hideyoshi.backendportfolio.base.config;
|
||||||
|
|
||||||
|
import antlr.actions.python.CodeLexer;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -8,12 +9,13 @@ import org.springframework.web.cors.CorsConfigurationSource;
|
|||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class CorsConfig {
|
public class CorsConfig {
|
||||||
|
|
||||||
@Value("${com.hideyoshi.frontEndPath}")
|
@Value("${com.hideyoshi.frontendPath}")
|
||||||
private String FRONTEND_PATH;
|
private String FRONTEND_PATH;
|
||||||
|
|
||||||
@Value("${com.hideyoshi.frontendConnectionType}")
|
@Value("${com.hideyoshi.frontendConnectionType}")
|
||||||
@@ -32,10 +34,12 @@ public class CorsConfig {
|
|||||||
|
|
||||||
CorsConfiguration configuration = new CorsConfiguration();
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
configuration.setAllowedOrigins(List.of(connectionProtocol + FRONTEND_PATH));
|
configuration.setAllowedOrigins(List.of(connectionProtocol + FRONTEND_PATH));
|
||||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
configuration.setAllowedMethods(Collections.singletonList("*"));
|
||||||
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", "x-auth-token"));
|
configuration.setAllowedHeaders(Collections.singletonList("*"));
|
||||||
configuration.setAllowCredentials(true);
|
// 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.setExposedHeaders(List.of("x-auth-token"));
|
||||||
|
configuration.setAllowCredentials(true);
|
||||||
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
source.registerCorsConfiguration("/**", configuration);
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.hideyoshi.backendportfolio.base.config;
|
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.entity.Role;
|
||||||
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
||||||
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
|
import com.hideyoshi.backendportfolio.base.user.repo.UserRepository;
|
||||||
@@ -30,10 +31,11 @@ public class DefaultUserConfig {
|
|||||||
CommandLineRunner run(UserService userService, UserRepository userRepo) {
|
CommandLineRunner run(UserService userService, UserRepository userRepo) {
|
||||||
return args -> {
|
return args -> {
|
||||||
UserDTO defaultUser = UserDTO.builder()
|
UserDTO defaultUser = UserDTO.builder()
|
||||||
.fullname(ADMIN_NAME)
|
.name(ADMIN_NAME)
|
||||||
.email(ADMIN_EMAIL)
|
.email(ADMIN_EMAIL)
|
||||||
.username(ADMIN_USERNAME)
|
.username(ADMIN_USERNAME)
|
||||||
.password(ADMIN_PASSWORD)
|
.password(ADMIN_PASSWORD)
|
||||||
|
.provider(Provider.LOCAL)
|
||||||
.roles(new ArrayList<>())
|
.roles(new ArrayList<>())
|
||||||
.build();
|
.build();
|
||||||
if (!userRepo.findByUsername(defaultUser.getUsername()).isPresent()) {
|
if (!userRepo.findByUsername(defaultUser.getUsername()).isPresent()) {
|
||||||
|
|||||||
@@ -3,29 +3,31 @@ package com.hideyoshi.backendportfolio.base.security;
|
|||||||
import com.hideyoshi.backendportfolio.base.config.RestAuthenticationEntryPointConfig;
|
import com.hideyoshi.backendportfolio.base.config.RestAuthenticationEntryPointConfig;
|
||||||
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthenticationFilter;
|
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthenticationFilter;
|
||||||
import com.hideyoshi.backendportfolio.base.security.filter.CustomAuthorizationFilter;
|
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.base.security.service.AuthService;
|
||||||
|
import com.hideyoshi.backendportfolio.util.exception.AuthenticationInvalidException;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
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.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
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.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
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -37,6 +39,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
|
|
||||||
private final BCryptPasswordEncoder passwordEncoder;
|
private final BCryptPasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private final OAuthRequestRepository oAuthRequestRepository;
|
||||||
|
|
||||||
private final RestAuthenticationEntryPointConfig restAuthenticationEntryPointConfig;
|
private final RestAuthenticationEntryPointConfig restAuthenticationEntryPointConfig;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -48,22 +52,64 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
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 =
|
CustomAuthenticationFilter customAuthenticationFilter =
|
||||||
new CustomAuthenticationFilter(this.authenticationManager(), this.authService, this.restAuthenticationEntryPointConfig);
|
new CustomAuthenticationFilter(this.authenticationManager(), this.authService, this.restAuthenticationEntryPointConfig);
|
||||||
|
|
||||||
customAuthenticationFilter.setFilterProcessesUrl("/user/login");
|
customAuthenticationFilter.setFilterProcessesUrl("/user/login");
|
||||||
|
|
||||||
http.cors().and().csrf().disable()
|
http.authorizeRequests()
|
||||||
.authorizeRequests().antMatchers("/session/**").permitAll()
|
.antMatchers("/session/**").permitAll()
|
||||||
.and().authorizeRequests().antMatchers("/user/signup").permitAll()
|
.and().authorizeRequests().antMatchers("/user/signup").permitAll()
|
||||||
.and().authorizeRequests().antMatchers("/user/login/refresh").permitAll()
|
.and().authorizeRequests().antMatchers("/user/oauth/**").permitAll()
|
||||||
.and().authorizeRequests().antMatchers("/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
|
.and().authorizeRequests().antMatchers("/user/login/**").permitAll()
|
||||||
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
|
.and().authorizeRequests().antMatchers("/**").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")
|
||||||
.and().addFilter(customAuthenticationFilter)
|
|
||||||
.addFilterBefore(new CustomAuthorizationFilter(this.authService), UsernamePasswordAuthenticationFilter.class);
|
.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
|
@Bean
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||||
return super.authenticationManagerBean();
|
return super.authenticationManagerBean();
|
||||||
|
|||||||
@@ -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.TokenDTO;
|
||||||
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@@ -57,18 +58,12 @@ public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFi
|
|||||||
@Override
|
@Override
|
||||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException {
|
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException {
|
||||||
|
|
||||||
UserDTO user = (UserDTO) authentication.getPrincipal();
|
this.authService.loginUser(
|
||||||
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
|
request,
|
||||||
|
response,
|
||||||
|
(UserDTO) authentication.getPrincipal()
|
||||||
|
);
|
||||||
|
|
||||||
HashMap<String,TokenDTO> 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,24 +2,16 @@ package com.hideyoshi.backendportfolio.base.security.filter;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
|
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.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
|
||||||
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
import static org.springframework.http.HttpStatus.FORBIDDEN;
|
||||||
@@ -38,7 +30,7 @@ public class CustomAuthorizationFilter extends OncePerRequestFilter {
|
|||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
if (request.getServletPath().equals("/user/login")) {
|
if (this.isPathNotProtected(request.getServletPath())) {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
} else {
|
} else {
|
||||||
String authorizationHeader = request.getHeader(AUTHORIZATION);
|
String authorizationHeader = request.getHeader(AUTHORIZATION);
|
||||||
@@ -69,4 +61,13 @@ public class CustomAuthorizationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean isPathNotProtected(String path) {
|
||||||
|
|
||||||
|
List<String> notProtectedPaths = Arrays.asList(
|
||||||
|
"/user/login"
|
||||||
|
);
|
||||||
|
|
||||||
|
return notProtectedPaths.contains(path);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<OAuth2AuthorizationRequest> {
|
||||||
|
|
||||||
|
@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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.TokenDTO;
|
||||||
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
public interface AuthService {
|
public interface AuthService {
|
||||||
@@ -24,4 +26,12 @@ public interface AuthService {
|
|||||||
|
|
||||||
UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request);
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ import com.auth0.jwt.JWT;
|
|||||||
import com.auth0.jwt.JWTVerifier;
|
import com.auth0.jwt.JWTVerifier;
|
||||||
import com.auth0.jwt.algorithms.Algorithm;
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
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.TokenDTO;
|
||||||
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
||||||
import com.hideyoshi.backendportfolio.base.user.service.UserService;
|
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.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||||
|
|
||||||
@@ -23,10 +29,12 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
|
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@Service
|
@Service
|
||||||
@@ -66,6 +74,7 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
.sign(algorithm);
|
.sign(algorithm);
|
||||||
|
|
||||||
return new TokenDTO(accessToken, expirationDate);
|
return new TokenDTO(accessToken, expirationDate);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -160,16 +169,93 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
@Override
|
@Override
|
||||||
public UserDTO signupUser(@Valid UserDTO user, HttpServletRequest request) {
|
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());
|
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET.getBytes());
|
||||||
|
|
||||||
UserDTO userSaved = this.userService.saveUser(user);
|
HashMap<String, TokenDTO> tokens = this.generateTokens(user, algorithm, request);
|
||||||
HashMap<String, TokenDTO> tokens = this.generateTokens(userSaved, algorithm, request);
|
|
||||||
|
|
||||||
HttpSession httpSession = request.getSession();
|
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);
|
httpSession.setAttribute("user", userAuthenticated);
|
||||||
|
|
||||||
return 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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,19 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
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.stereotype.Controller;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.security.Provider;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
@@ -49,34 +53,6 @@ public class UserController {
|
|||||||
return ResponseEntity.created(uri).body(this.authService.signupUser(user, request));
|
return ResponseEntity.created(uri).body(this.authService.signupUser(user, request));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/delete/{id}")
|
|
||||||
@UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
|
|
||||||
public ResponseEntity<Void> 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<Void> 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")
|
@PostMapping("/login/refresh")
|
||||||
@UserResourceGuard(accessType = UserResourceGuardEnum.OPEN)
|
@UserResourceGuard(accessType = UserResourceGuardEnum.OPEN)
|
||||||
public ResponseEntity<UserDTO> refreshAccessToken(
|
public ResponseEntity<UserDTO> refreshAccessToken(
|
||||||
@@ -86,4 +62,18 @@ public class UserController {
|
|||||||
return ResponseEntity.ok(this.authService.refreshAccessToken(refreshToken.getToken(), request, 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/delete/{id}")
|
||||||
|
@UserResourceGuard(accessType = UserResourceGuardEnum.SAME_USER)
|
||||||
|
public ResponseEntity<Void> deleteUser(@PathVariable("id") Long id) {
|
||||||
|
this.userService.deleteUser(id);
|
||||||
|
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ public enum Provider {
|
|||||||
|
|
||||||
GOOGLE("google"),
|
GOOGLE("google"),
|
||||||
|
|
||||||
|
GITHUB("github"),
|
||||||
|
|
||||||
LOCAL("local");
|
LOCAL("local");
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
Provider(String name) {
|
Provider(String name) {
|
||||||
@@ -15,4 +18,13 @@ public enum Provider {
|
|||||||
return name;
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ public class User {
|
|||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@Column(
|
@Column(
|
||||||
name = "full_name",
|
name = "name",
|
||||||
nullable = false
|
nullable = false
|
||||||
)
|
)
|
||||||
private String fullname;
|
private String name;
|
||||||
|
|
||||||
@Column(
|
@Column(
|
||||||
name = "email",
|
name = "email",
|
||||||
@@ -52,6 +52,12 @@ public class User {
|
|||||||
)
|
)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
@Column(
|
||||||
|
name = "provider",
|
||||||
|
nullable = false
|
||||||
|
)
|
||||||
|
private String provider;
|
||||||
|
|
||||||
@Column(
|
@Column(
|
||||||
name = "roles",
|
name = "roles",
|
||||||
nullable = false
|
nullable = false
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class UserDTO implements UserDetails {
|
|||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
private String fullname;
|
private String name;
|
||||||
|
|
||||||
@NotEmpty
|
@NotEmpty
|
||||||
@ValidEmail
|
@ValidEmail
|
||||||
@@ -59,104 +59,82 @@ public class UserDTO implements UserDetails {
|
|||||||
|
|
||||||
private Provider provider;
|
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<Role> roles
|
|
||||||
) {
|
|
||||||
this.fullname = fullname;
|
|
||||||
this.email = email;
|
|
||||||
this.username = username;
|
|
||||||
this.password = password;
|
|
||||||
this.roles = roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserDTO(User entity) {
|
public UserDTO(User entity) {
|
||||||
this.id = entity.getId();
|
this.id = entity.getId();
|
||||||
this.fullname = entity.getFullname();
|
this.name = entity.getName();
|
||||||
this.email = entity.getEmail();
|
this.email = entity.getEmail();
|
||||||
this.username = entity.getUsername();
|
this.username = entity.getUsername();
|
||||||
this.password = entity.getPassword();
|
this.password = entity.getPassword();
|
||||||
|
this.provider = Provider.byValue(entity.getProvider());
|
||||||
this.roles = entity.getRoles();
|
this.roles = entity.getRoles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public User toEntity() {
|
public User toEntity() {
|
||||||
return new User(
|
return new User(
|
||||||
this.id,
|
this.id,
|
||||||
this.fullname,
|
this.name,
|
||||||
this.email,
|
this.email,
|
||||||
this.username,
|
this.username,
|
||||||
this.password,
|
this.password,
|
||||||
|
this.provider.getName(),
|
||||||
Objects.nonNull(this.roles) ? this.roles.stream()
|
Objects.nonNull(this.roles) ? this.roles.stream()
|
||||||
.map(role -> role.getDescription())
|
.map(role -> role.getDescription())
|
||||||
.collect(Collectors.joining("&")) : Role.USER.getDescription()
|
.collect(Collectors.joining("&")) : Role.USER.getDescription()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
return this.roles.stream()
|
return this.getRoles().stream()
|
||||||
.map(role -> new SimpleGrantedAuthority(role.getDescription()))
|
.map(role -> new SimpleGrantedAuthority(role.getDescription()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public boolean isAccountNonExpired() {
|
public boolean isAccountNonExpired() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public boolean isAccountNonLocked() {
|
public boolean isAccountNonLocked() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public boolean isCredentialsNonExpired() {
|
public boolean isCredentialsNonExpired() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
@Override
|
@Override
|
||||||
|
@JsonIgnore
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserDTO toResponse() {
|
public UserDTO toResponse() {
|
||||||
return UserDTO.builder()
|
return UserDTO.builder()
|
||||||
.fullname(this.fullname)
|
.name(this.name)
|
||||||
.email(this.email)
|
.email(this.email)
|
||||||
.username(this.username)
|
.username(this.username)
|
||||||
|
.provider(this.provider)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserDTO toResponse(TokenDTO accessToken, TokenDTO refreshToken) {
|
public UserDTO toResponse(TokenDTO accessToken, TokenDTO refreshToken) {
|
||||||
return UserDTO.builder()
|
return UserDTO.builder()
|
||||||
.id(this.id)
|
.id(this.id)
|
||||||
.fullname(this.fullname)
|
.name(this.name)
|
||||||
.email(this.email)
|
.email(this.email)
|
||||||
.username(this.username)
|
.username(this.username)
|
||||||
|
.provider(this.provider)
|
||||||
.roles(this.roles)
|
.roles(this.roles)
|
||||||
.accessToken(accessToken)
|
.accessToken(accessToken)
|
||||||
.refreshToken(refreshToken)
|
.refreshToken(refreshToken)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,9 +34,13 @@ public class UserServiceImpl implements UserService {
|
|||||||
throw new BadRequestException(String.format("User %s already exists. Try another UserName.", userOnDB.getUsername()));
|
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()));
|
UserDTO userSaved = new UserDTO(userRepo.save(user.toEntity()));
|
||||||
|
|
||||||
if (!userSaved.getRoles().contains(Role.USER)) {
|
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",
|
log.info(String.format("Adding to user %s the role %s",
|
||||||
userOnDB.getUsername(), newAuthority.getDescription()));
|
userOnDB.getUsername(), newAuthority.getDescription()));
|
||||||
|
|
||||||
if (roles.add(newAuthority)) {
|
roles.add(newAuthority);
|
||||||
userOnDB.setRoles(roles);
|
userOnDB.setRoles(roles);
|
||||||
this.alterUser(userOnDB.getId(), userOnDB);
|
|
||||||
}
|
this.alterUser(userOnDB.getId(), userOnDB);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.hideyoshi.backendportfolio.util.validator.password;
|
package com.hideyoshi.backendportfolio.util.validator.password;
|
||||||
|
|
||||||
|
import com.hideyoshi.backendportfolio.base.user.entity.Provider;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
import javax.validation.ConstraintValidator;
|
import javax.validation.ConstraintValidator;
|
||||||
@@ -9,17 +10,25 @@ import java.util.regex.Pattern;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
|
public class PasswordValidator implements ConstraintValidator<ValidPassword, String> {
|
||||||
|
|
||||||
|
Provider provider;
|
||||||
|
|
||||||
private final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
|
private final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(ValidPassword constraintAnnotation) {}
|
public void initialize(ValidPassword constraintAnnotation) {
|
||||||
|
this.provider = constraintAnnotation.provider();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isValid(String password, ConstraintValidatorContext context) {
|
public boolean isValid(String password, ConstraintValidatorContext context) {
|
||||||
|
|
||||||
return Pattern.compile(PASSWORD_PATTERN)
|
if (this.provider.equals(Provider.GOOGLE)) {
|
||||||
.matcher(password)
|
return true;
|
||||||
.matches();
|
} else {
|
||||||
|
return Pattern.compile(PASSWORD_PATTERN)
|
||||||
|
.matcher(password)
|
||||||
|
.matches();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.hideyoshi.backendportfolio.util.validator.password;
|
package com.hideyoshi.backendportfolio.util.validator.password;
|
||||||
|
|
||||||
|
import com.hideyoshi.backendportfolio.base.user.entity.Provider;
|
||||||
|
|
||||||
import javax.validation.Constraint;
|
import javax.validation.Constraint;
|
||||||
import javax.validation.Payload;
|
import javax.validation.Payload;
|
||||||
import java.lang.annotation.Documented;
|
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.ElementType.*;
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
|
||||||
@Target({TYPE, FIELD, ANNOTATION_TYPE})
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
@Constraint(validatedBy = PasswordValidator.class)
|
|
||||||
@Documented
|
@Documented
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
@Target({TYPE, FIELD, ANNOTATION_TYPE})
|
||||||
|
@Constraint(validatedBy = PasswordValidator.class)
|
||||||
public @interface ValidPassword {
|
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 {};
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
com:
|
com:
|
||||||
hideyoshi:
|
hideyoshi:
|
||||||
frontEndPath: ${FRONTEND_PATH}
|
frontendPath: ${FRONTEND_PATH}
|
||||||
frontendConnectionType: ${FRONTEND_CONNECTION_TYPE}
|
frontendConnectionType: ${FRONTEND_CONNECTION_TYPE}
|
||||||
tokenSecret: ${TOKEN_SECRET}
|
tokenSecret: ${TOKEN_SECRET}
|
||||||
accessTokenDuration: ${ACCESS_TOKEN_DURATION}
|
accessTokenDuration: ${ACCESS_TOKEN_DURATION}
|
||||||
@@ -17,6 +17,26 @@ server:
|
|||||||
|
|
||||||
spring:
|
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:
|
datasource:
|
||||||
url: jdbc:${DATABASE_URL}
|
url: jdbc:${DATABASE_URL}
|
||||||
username: ${DATABASE_USERNAME}
|
username: ${DATABASE_USERNAME}
|
||||||
|
|||||||
@@ -9,3 +9,13 @@ databaseChangeLog:
|
|||||||
path: sqls/db-table-model-client.sql
|
path: sqls/db-table-model-client.sql
|
||||||
relativeToChangelogFile: true
|
relativeToChangelogFile: true
|
||||||
dbms: postgresql
|
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
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.hideyoshi.backendportfolio.base.user.repo;
|
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.Role;
|
||||||
import com.hideyoshi.backendportfolio.base.user.entity.User;
|
import com.hideyoshi.backendportfolio.base.user.entity.User;
|
||||||
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
||||||
@@ -58,13 +59,14 @@ class UserRepositoryTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private User createEntity() {
|
private User createEntity() {
|
||||||
return new UserDTO(
|
return UserDTO.builder()
|
||||||
"Clark Kent",
|
.name("Clark Kent")
|
||||||
"superman@gmail.com",
|
.email("superman@gmail.com")
|
||||||
"Superman",
|
.username("Superman")
|
||||||
"password",
|
.password("password")
|
||||||
List.of(Role.USER)
|
.provider(Provider.LOCAL)
|
||||||
).toEntity();
|
.roles(List.of(Role.USER))
|
||||||
|
.build().toEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.hideyoshi.backendportfolio.base.user.service;
|
package com.hideyoshi.backendportfolio.base.user.service;
|
||||||
|
|
||||||
import com.hideyoshi.backendportfolio.base.security.service.AuthService;
|
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.Role;
|
||||||
import com.hideyoshi.backendportfolio.base.user.entity.User;
|
import com.hideyoshi.backendportfolio.base.user.entity.User;
|
||||||
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
import com.hideyoshi.backendportfolio.base.user.model.UserDTO;
|
||||||
@@ -349,15 +350,15 @@ class UserServiceImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private UserDTO createUser() {
|
private UserDTO createUser() {
|
||||||
UserDTO userCreated = new UserDTO(
|
return UserDTO.builder()
|
||||||
"Clark Kent",
|
.id(1L)
|
||||||
"superman@gmail.com",
|
.name("Clark Kent")
|
||||||
"Superman",
|
.email("superman@gmail.com")
|
||||||
"password",
|
.username("Superman")
|
||||||
List.of(Role.USER)
|
.password("password")
|
||||||
);
|
.provider(Provider.LOCAL)
|
||||||
userCreated.setId(1L);
|
.roles(List.of(Role.USER))
|
||||||
return userCreated;
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user