diff --git a/src/main/java/io/hexlet/typoreporter/config/SecurityConfig.java b/src/main/java/io/hexlet/typoreporter/config/SecurityConfig.java index 41fee1f7..cb1aec3e 100644 --- a/src/main/java/io/hexlet/typoreporter/config/SecurityConfig.java +++ b/src/main/java/io/hexlet/typoreporter/config/SecurityConfig.java @@ -1,10 +1,12 @@ package io.hexlet.typoreporter.config; -import io.hexlet.typoreporter.config.oauth2.OAuth2ConfigurationProperties; +import io.hexlet.typoreporter.config.oauth2.GithubConfigurationProperties; +import io.hexlet.typoreporter.handler.OAuth2LoginFailureHandler; +import io.hexlet.typoreporter.handler.OAuth2LoginSuccessHandler; import io.hexlet.typoreporter.handler.exception.ForbiddenDomainException; -import io.hexlet.typoreporter.handler.exception.OAuth2Exception; import io.hexlet.typoreporter.handler.exception.WorkspaceNotFoundException; import io.hexlet.typoreporter.security.service.AccountDetailService; +import io.hexlet.typoreporter.security.service.CustomOAuth2UserService; import io.hexlet.typoreporter.security.service.SecuredWorkspaceService; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -13,7 +15,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; @@ -23,11 +24,15 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider; +import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.context.DelegatingSecurityContextRepository; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; @@ -48,7 +53,7 @@ @EnableMethodSecurity public class SecurityConfig { @Autowired - private OAuth2ConfigurationProperties oAuth2ConfigurationProperties; + private GithubConfigurationProperties githubConfigurationProperties; @Bean public PasswordEncoder bCryptPasswordEncoder() { @@ -73,12 +78,21 @@ public DaoAuthenticationProvider accountProvider(AccountDetailService userDetail return accountProvider; } + @Bean + public OAuth2LoginAuthenticationProvider githubProvider(CustomOAuth2UserService customOAuth2UserService) { + var responseClient = new DefaultAuthorizationCodeTokenResponseClient(); + return new OAuth2LoginAuthenticationProvider(responseClient, customOAuth2UserService); + } + @Bean public AuthenticationManager authenticationManager(DaoAuthenticationProvider apiProvider, - DaoAuthenticationProvider accountProvider) { - return new ProviderManager(apiProvider, accountProvider); + DaoAuthenticationProvider accountProvider, + OAuth2LoginAuthenticationProvider githubProvider + ) { + return new ProviderManager(apiProvider, accountProvider, githubProvider); } + @Bean public SecurityContextRepository securityContextRepository() { return new DelegatingSecurityContextRepository( @@ -99,7 +113,6 @@ public SecurityFilterChain filterChain(HttpSecurity http, http.authorizeHttpRequests(authz -> authz .requestMatchers(GET, "/webjars/**", "/widget/**", "/fragments/**", "/img/**").permitAll() .requestMatchers("/", "/login", "/signup", "/error", "/about").permitAll() - .requestMatchers("/oauth/**").permitAll() .requestMatchers("/login/oauth/code/**").permitAll() .anyRequest().authenticated() ) @@ -111,6 +124,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, ) .oauth2Login(config -> config .loginPage("/login") + .successHandler(getOAuth2LoginSuccessHandler()) + .failureHandler(getOAuth2LoginFailureHandler()) ) .csrf(csrf -> csrf .ignoringRequestMatchers( @@ -126,16 +141,26 @@ public SecurityFilterChain filterChain(HttpSecurity http, return http.build(); } + @Bean + public AuthenticationSuccessHandler getOAuth2LoginSuccessHandler() { + return new OAuth2LoginSuccessHandler(); + } + + @Bean + public AuthenticationFailureHandler getOAuth2LoginFailureHandler() { + return new OAuth2LoginFailureHandler(); + } @Bean public ClientRegistrationRepository getClientRegistrationRepository() { return new InMemoryClientRegistrationRepository(githubClientRegistration()); } + private ClientRegistration githubClientRegistration() { return CommonOAuth2Provider.GITHUB.getBuilder("github") - .clientId(oAuth2ConfigurationProperties.getClientId()) - .clientSecret(oAuth2ConfigurationProperties.getClientSecret()) - .scope(oAuth2ConfigurationProperties.getScope()) + .clientId(githubConfigurationProperties.getClientId()) + .clientSecret(githubConfigurationProperties.getClientSecret()) + .scope(githubConfigurationProperties.getScope()) .build(); } @@ -177,12 +202,6 @@ protected void doFilterInternal(HttpServletRequest request, response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } catch (WorkspaceNotFoundException e) { response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); - } catch (OAuth2Exception e) { - if (e.getStatusCode() == HttpStatus.BAD_REQUEST) { - response.sendRedirect("/oauth/exception/name"); - } else { - response.sendRedirect("/oauth/exception"); - } } } }; diff --git a/src/main/java/io/hexlet/typoreporter/config/oauth2/OAuth2ConfigurationProperties.java b/src/main/java/io/hexlet/typoreporter/config/oauth2/GithubConfigurationProperties.java similarity index 85% rename from src/main/java/io/hexlet/typoreporter/config/oauth2/OAuth2ConfigurationProperties.java rename to src/main/java/io/hexlet/typoreporter/config/oauth2/GithubConfigurationProperties.java index 7a2f3121..a36f7d00 100644 --- a/src/main/java/io/hexlet/typoreporter/config/oauth2/OAuth2ConfigurationProperties.java +++ b/src/main/java/io/hexlet/typoreporter/config/oauth2/GithubConfigurationProperties.java @@ -12,12 +12,14 @@ @ConfigurationProperties(prefix = "spring.security.oauth2.client.registration.github") @Getter @Setter -public class OAuth2ConfigurationProperties { +public class GithubConfigurationProperties { @Value("clientId") private String clientId; @Value("clientSecret") private String clientSecret; @Value("scope") private HashSet scope; + @Value("redirect-uri") + private String redirectUri; } diff --git a/src/main/java/io/hexlet/typoreporter/controller/AccountController.java b/src/main/java/io/hexlet/typoreporter/controller/AccountController.java index 12b2f301..cc758d4d 100644 --- a/src/main/java/io/hexlet/typoreporter/controller/AccountController.java +++ b/src/main/java/io/hexlet/typoreporter/controller/AccountController.java @@ -35,6 +35,7 @@ public class AccountController { @GetMapping public String getAccountInfoPage(final Model model, final Authentication authentication) { + System.out.println("customAuth2-: " + authentication.toString()); final var accountInfo = accountService.getInfoAccount(authentication.getName()); final var workspaceInfos = accountService.getWorkspacesInfoListByEmail(accountInfo.email()); model.addAttribute("workspaceRoleInfoList", workspaceInfos); diff --git a/src/main/java/io/hexlet/typoreporter/controller/OAuth2Controller.java b/src/main/java/io/hexlet/typoreporter/controller/OAuth2Controller.java deleted file mode 100644 index 30b7a5dc..00000000 --- a/src/main/java/io/hexlet/typoreporter/controller/OAuth2Controller.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.hexlet.typoreporter.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; - -@Controller -@RequestMapping("/oauth") -public class OAuth2Controller { - - @GetMapping("/exception") - public String handle(final Model model) { - model.addAttribute("isOAuth2Fail", true); - return "/login"; - } - - @GetMapping("/exception/name") - public String handleExceptionName(final Model model) { - model.addAttribute("isFullNameException", true); - return "/login"; - } -} diff --git a/src/main/java/io/hexlet/typoreporter/handler/GlobalExceptionHandler.java b/src/main/java/io/hexlet/typoreporter/handler/GlobalExceptionHandler.java index 2e749797..9d5d0396 100644 --- a/src/main/java/io/hexlet/typoreporter/handler/GlobalExceptionHandler.java +++ b/src/main/java/io/hexlet/typoreporter/handler/GlobalExceptionHandler.java @@ -1,10 +1,6 @@ package io.hexlet.typoreporter.handler; -import io.hexlet.typoreporter.handler.exception.AccountAlreadyExistException; -import io.hexlet.typoreporter.handler.exception.AccountNotFoundException; -import io.hexlet.typoreporter.handler.exception.NewPasswordTheSameException; -import io.hexlet.typoreporter.handler.exception.OldPasswordWrongException; -import io.hexlet.typoreporter.handler.exception.WorkspaceAlreadyExistException; +import io.hexlet.typoreporter.handler.exception.*; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -52,4 +48,9 @@ public ResponseEntity handleTypoNotFoundException(TypeNotPresentExceptio public ResponseEntity handleWorkSpaceAlreadyExistException(WorkspaceAlreadyExistException ex) { return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage()); } + + @ExceptionHandler(OAuth2Exception.class) + public ResponseEntity handleOAuth2Exception(OAuth2Exception ex) { + return ResponseEntity.status(ex.getStatusCode()).body(ex.getMessage()); + } } diff --git a/src/main/java/io/hexlet/typoreporter/handler/OAuth2LoginFailureHandler.java b/src/main/java/io/hexlet/typoreporter/handler/OAuth2LoginFailureHandler.java new file mode 100644 index 00000000..849f9a23 --- /dev/null +++ b/src/main/java/io/hexlet/typoreporter/handler/OAuth2LoginFailureHandler.java @@ -0,0 +1,19 @@ +package io.hexlet.typoreporter.handler; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; + +import java.io.IOException; + +public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { + @Override + public void onAuthenticationFailure(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + request.getSession().setAttribute("isOAuth2Fail", true); + response.sendRedirect("/login"); + } +} diff --git a/src/main/java/io/hexlet/typoreporter/handler/OAuth2LoginSuccessHandler.java b/src/main/java/io/hexlet/typoreporter/handler/OAuth2LoginSuccessHandler.java new file mode 100644 index 00000000..5293a8df --- /dev/null +++ b/src/main/java/io/hexlet/typoreporter/handler/OAuth2LoginSuccessHandler.java @@ -0,0 +1,27 @@ +package io.hexlet.typoreporter.handler; + +import io.hexlet.typoreporter.domain.account.CustomOAuth2User; +import io.hexlet.typoreporter.service.AccountService; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; + +import java.io.IOException; + +public class OAuth2LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { + @Autowired + private AccountService accountService; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws ServletException, IOException { + CustomOAuth2User user = (CustomOAuth2User) authentication.getPrincipal(); + accountService.createOrUpdate(user); + request.getSession().setAttribute("isOAuth2Fail", false); + response.sendRedirect("/workspaces"); + } +} diff --git a/src/main/java/io/hexlet/typoreporter/security/service/CustomOAuth2UserService.java b/src/main/java/io/hexlet/typoreporter/security/service/CustomOAuth2UserService.java index 4adff603..cb245fa0 100644 --- a/src/main/java/io/hexlet/typoreporter/security/service/CustomOAuth2UserService.java +++ b/src/main/java/io/hexlet/typoreporter/security/service/CustomOAuth2UserService.java @@ -1,12 +1,9 @@ package io.hexlet.typoreporter.security.service; import io.hexlet.typoreporter.domain.account.CustomOAuth2User; -import io.hexlet.typoreporter.service.AccountService; import io.hexlet.typoreporter.service.GithubService; +import io.hexlet.typoreporter.service.dto.oauth2.PrivateEmail; import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -15,18 +12,14 @@ @Service @RequiredArgsConstructor public class CustomOAuth2UserService extends DefaultOAuth2UserService { - private final AccountService accountService; private final GithubService githubService; public OAuth2User loadUser(OAuth2UserRequest userRequest) { - OAuth2User user = super.loadUser(userRequest); - var email = githubService.getPrivateEmail(userRequest.getAccessToken().getTokenValue()); - var customUser = new CustomOAuth2User(user, email); - Authentication authentication = new UsernamePasswordAuthenticationToken( - customUser, customUser.getPassword(), customUser.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - accountService.processOAuthPostLogin(customUser); - - return customUser; + OAuth2User oAuth2User = super.loadUser(userRequest); + PrivateEmail privateEmail = githubService.getPrivateEmail(userRequest.getAccessToken().getTokenValue()); + if (privateEmail.getEmail() == null || privateEmail.getEmail().isEmpty()) { + return null; + } + return new CustomOAuth2User(oAuth2User, privateEmail); } } diff --git a/src/main/java/io/hexlet/typoreporter/service/AccountService.java b/src/main/java/io/hexlet/typoreporter/service/AccountService.java index 03bf0c14..5e77153a 100644 --- a/src/main/java/io/hexlet/typoreporter/service/AccountService.java +++ b/src/main/java/io/hexlet/typoreporter/service/AccountService.java @@ -143,19 +143,19 @@ public Account updatePassword(final UpdatePassword updatePassword, final String return sourceAccount; } @Transactional - public void processOAuthPostLogin(CustomOAuth2User user) { + public void createOrUpdate(CustomOAuth2User user) { if (user.getFirstName().isEmpty() || user.getLastName().isEmpty()) { throw new OAuth2Exception(HttpStatus.BAD_REQUEST, ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Firstname or lastname is empty"), null); } - var existUser = accountRepository.existsByEmail(user.getEmail()); - if (!existUser) { + //var existUser = accountRepository.existsByEmail(user.getEmail()); + //if (!existUser) { SignupAccount signupAccount = new SignupAccount( user.getLogin(), user.getEmail(), passwordEncoder.encode(user.getPassword()), user.getFirstName(), user.getLastName()); Account account = accountMapper.toAccount(signupAccount); account.setAuthProvider(AuthProvider.GITHUB); accountRepository.save(account); - } + //} } } diff --git a/src/main/resources/templates/account/signup.html b/src/main/resources/templates/account/signup.html index 328c8fd2..eb37155b 100644 --- a/src/main/resources/templates/account/signup.html +++ b/src/main/resources/templates/account/signup.html @@ -6,6 +6,7 @@
+
+
+
+ +
+