Skip to content

Commit

Permalink
[Hexlet#159] add oauth2handlers and login via github on sign up page
Browse files Browse the repository at this point in the history
  • Loading branch information
d1z3d committed Aug 26, 2024
1 parent 964714f commit 73d86c4
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 63 deletions.
51 changes: 35 additions & 16 deletions src/main/java/io/hexlet/typoreporter/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -48,7 +53,7 @@
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private OAuth2ConfigurationProperties oAuth2ConfigurationProperties;
private GithubConfigurationProperties githubConfigurationProperties;

@Bean
public PasswordEncoder bCryptPasswordEncoder() {
Expand All @@ -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(
Expand All @@ -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()
)
Expand All @@ -111,6 +124,8 @@ public SecurityFilterChain filterChain(HttpSecurity http,
)
.oauth2Login(config -> config
.loginPage("/login")
.successHandler(getOAuth2LoginSuccessHandler())
.failureHandler(getOAuth2LoginFailureHandler())
)
.csrf(csrf -> csrf
.ignoringRequestMatchers(
Expand All @@ -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();
}

Expand Down Expand Up @@ -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");
}
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> scope;
@Value("redirect-uri")
private String redirectUri;
}

Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -52,4 +48,9 @@ public ResponseEntity<String> handleTypoNotFoundException(TypeNotPresentExceptio
public ResponseEntity<String> handleWorkSpaceAlreadyExistException(WorkspaceAlreadyExistException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}

@ExceptionHandler(OAuth2Exception.class)
public ResponseEntity<String> handleOAuth2Exception(OAuth2Exception ex) {
return ResponseEntity.status(ex.getStatusCode()).body(ex.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
//}
}
}
6 changes: 6 additions & 0 deletions src/main/resources/templates/account/signup.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<main class="container">
<div class="row">
<div class="col">
<div class="alert alert-danger" role="alert" th:if="${isOAuth2Fail}" th:text="#{alert.oauth2.exception}"></div>
<form method="post" th:action="@{/signup}" th:object="${signupAccount}">
<div class="form-floating mb-3">
<input id="inputUsername" placeholder="p" type="text" th:field="*{username}"
Expand Down Expand Up @@ -69,6 +70,11 @@
</div>
</div>
<button class="btn btn-primary" type="submit" th:text="#{btn.create-account}"></button>
<div class="row">
<div class="mt-4">
<a class="link-primary" href="/oauth2/authorization/github" th:text="#{link.sign-in-with-github}"></a>
</div>
</div>
</form>
</div>
</div>
Expand Down

0 comments on commit 73d86c4

Please sign in to comment.