Skip to content

Commit

Permalink
[Hexlet#159] add oauth2 with github
Browse files Browse the repository at this point in the history
  • Loading branch information
d1z3d committed Jun 27, 2024
1 parent a69c94a commit b376b45
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 10 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ dependencies {
implementation("org.mapstruct:mapstruct:1.5.3.Final")
// Annotation processors
annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final")

implementation("org.springframework.boot:spring-boot-starter-oauth2-client")
// Testing
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
Expand Down
27 changes: 24 additions & 3 deletions src/main/java/io/hexlet/typoreporter/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.hexlet.typoreporter.config;

import io.hexlet.typoreporter.handler.OAuth2SuccessHandler;
import io.hexlet.typoreporter.handler.exception.ForbiddenDomainException;
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;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
Expand All @@ -20,6 +23,7 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
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 @@ -37,6 +41,8 @@
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Autowired
private CustomOAuth2UserService oAuth2UserService;

@Bean
public PasswordEncoder bCryptPasswordEncoder() {
Expand Down Expand Up @@ -77,15 +83,17 @@ public SecurityContextRepository securityContextRepository() {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http,
SecurityContextRepository securityContextRepository,
DynamicCorsConfigurationSource dynamicCorsConfigurationSource) throws Exception {
SecurityContextRepository securityContextRepository,
DynamicCorsConfigurationSource dynamicCorsConfigurationSource)
throws Exception {
http.httpBasic();
http.cors();
http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());

http.authorizeHttpRequests(authz -> authz
.requestMatchers(GET, "/webjars/**", "/widget/**", "/fragments/**", "/img/**").permitAll()
.requestMatchers("/", "/login", "/signup", "/error", "/about").permitAll()
.requestMatchers("/oauth/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(login -> login
Expand All @@ -94,6 +102,14 @@ public SecurityFilterChain filterChain(HttpSecurity http,
.defaultSuccessUrl("/workspaces")
.permitAll()
)
.oauth2Login(config -> config
.loginPage("/login")
.userInfoEndpoint()
.userService(oAuth2UserService)
.and()
.successHandler(getOAuth2SuccessHandler())
.defaultSuccessUrl("/workspaces")
)
.csrf(csrf -> csrf
.ignoringRequestMatchers(
new AntPathRequestMatcher("/api/**", POST.name()),
Expand All @@ -108,6 +124,11 @@ public SecurityFilterChain filterChain(HttpSecurity http,
return http.build();
}

@Bean
public AuthenticationSuccessHandler getOAuth2SuccessHandler() {
return new OAuth2SuccessHandler();
}

@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
Expand Down Expand Up @@ -136,7 +157,7 @@ protected void doFilterInternal(HttpServletRequest request,
super.doFilterInternal(request, response, filterChain);
} catch (ForbiddenDomainException e) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
} catch (WorkspaceNotFoundException e) {
} catch (WorkspaceNotFoundException e) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import io.hexlet.typoreporter.service.WorkspaceService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import java.security.Principal;

@Controller
@RequestMapping
@RequiredArgsConstructor
Expand All @@ -17,7 +16,7 @@ public class IndexController {
private final WorkspaceService workspaceService;

@GetMapping("/workspaces")
public String index(final Model model, Principal principal) {
public String index(final Model model, Authentication principal) {
if (principal != null) {
final var email = principal.getName();
final var wksInfoList = workspaceService.getAllWorkspacesInfoByEmail(email);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.security.Principal;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -93,7 +92,7 @@ public String getCreateWorkspacePage(final Model model) {

@PostMapping
public String postCreateWorkspacePage(final Model model,
Principal principal,
Authentication principal,
@Valid @ModelAttribute CreateWorkspace createWorkspace,
BindingResult bindingResult) {
String email = principal.getName();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.hexlet.typoreporter.domain.account;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

import java.util.Collection;
import java.util.Map;
import java.util.Objects;

public class CustomOAuth2User implements OAuth2User {
private final OAuth2User oAuth2User;

public CustomOAuth2User(OAuth2User oAuth2User) {
this.oAuth2User = oAuth2User;
}

@Override
public Map<String, Object> getAttributes() {
return oAuth2User.getAttributes();
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return oAuth2User.getAuthorities();
}

@Override
public String getName() {
return oAuth2User.getAttribute("email");
}

public String getEmail() {
return oAuth2User.getAttribute("email");
}
public String getLogin() {
return oAuth2User.getAttribute("login");
}
public String getFirstName() {
String[] fullName = Objects.requireNonNull(oAuth2User.<String>getAttribute("name")).split(" ");
return fullName[1];
}
public String getLastName() {
String[] fullName = Objects.requireNonNull(oAuth2User.<String>getAttribute("name")).split(" ");
return fullName[0];
}
public String getPassword() {
Integer password = oAuth2User.getAttribute("id");
return Objects.requireNonNull(password).toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private AccountService accountService;

@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
accountService.processOAuthPostLogin(oAuth2User);
response.sendRedirect("/workspaces");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.hexlet.typoreporter.security.service;

import io.hexlet.typoreporter.domain.account.CustomOAuth2User;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(userRequest);
return new CustomOAuth2User(user);
}
}
17 changes: 17 additions & 0 deletions src/main/java/io/hexlet/typoreporter/service/AccountService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.hexlet.typoreporter.domain.account.Account;
import io.hexlet.typoreporter.domain.account.AuthProvider;
import io.hexlet.typoreporter.domain.account.CustomOAuth2User;
import io.hexlet.typoreporter.repository.AccountRepository;
import io.hexlet.typoreporter.repository.WorkspaceRoleRepository;
import io.hexlet.typoreporter.service.account.EmailAlreadyExistException;
Expand Down Expand Up @@ -138,4 +139,20 @@ public Account updatePassword(final UpdatePassword updatePassword, final String
accountRepository.save(sourceAccount);
return sourceAccount;
}
@Transactional
public void processOAuthPostLogin(CustomOAuth2User user) {
//TODO: убрать после тестирования
//accountRepository.deleteAll();
var existUser = accountRepository.existsByEmail(user.getEmail());
if (!existUser) {
Account account = new Account();
account.setEmail(user.getEmail());
account.setAuthProvider(AuthProvider.GITHUB);
account.setUsername(user.getLogin());
account.setPassword(user.getPassword());
account.setFirstName(user.getFirstName());
account.setLastName(user.getFirstName());
accountRepository.save(account);
}
}
}
11 changes: 11 additions & 0 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@ spring:
enabled: true
problemdetails:
enabled: true

security:
oauth2:
client:
registration:
github:
clientId: ${GITHUB_CLIENT_ID}
clientSecret: ${GITHUB_CLIENT_SECRET}
scope:
- email
- profile
3 changes: 3 additions & 0 deletions src/main/resources/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
<button class="btn btn-primary" type="submit" th:text="#{btn.login}"></button>
</form>
</div>
<div class="container unauthenticated">
With GitHub: <a href="/oauth2/authorization/github">click here</a>
</div>
</div>
<div class="row">
<div class="mt-4">
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/io/hexlet/typoreporter/web/LoginIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static void datasourceProperties(DynamicPropertyRegistry registry) {
private static final String EMAIL_UPPER_CASE = "[email protected]";
private static final String EMAIL_LOWER_CASE = EMAIL_UPPER_CASE.toLowerCase();

private SignupAccountModel model = new SignupAccountModel(
private final SignupAccountModel model = new SignupAccountModel(
"model_upper_case",
EMAIL_UPPER_CASE,
"password", "password",
Expand Down

0 comments on commit b376b45

Please sign in to comment.