Skip to content

Commit

Permalink
Oauth2 소셜 로그인 기능 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
hanbonghun committed Mar 23, 2023
1 parent 37c3e59 commit a1ddb8f
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 12 deletions.
1 change: 1 addition & 0 deletions build.gradle
Expand Up @@ -32,6 +32,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'
implementation 'org.commonmark:commonmark:0.21.0'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client:2.6.2'



Expand Down
3 changes: 2 additions & 1 deletion src/main/java/com/mysite/sbb/SbbApplication.java
Expand Up @@ -2,12 +2,13 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.core.userdetails.UserDetails;

@SpringBootApplication
public class SbbApplication {

public static void main(String[] args) {
SpringApplication.run(SbbApplication.class, args);
}

}
}
12 changes: 12 additions & 0 deletions src/main/java/com/mysite/sbb/SecurityConfig.java
@@ -1,5 +1,7 @@
package com.mysite.sbb;

import com.mysite.sbb.user.Oauth2UserSecurityService;
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 @@ -17,6 +19,8 @@
@EnableMethodSecurity(prePostEnabled = true)
//EnableWebSecurity 애너테이션을 사용하면 내부적으로 SpringSecurityFilterChain이 동작하여 URL 필터가 적용된다., spring security 활성화
public class SecurityConfig {
@Autowired
private Oauth2UserSecurityService oauth2UserService;
@Bean
//인증되지 않은 모든 요청 허용
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
Expand All @@ -27,10 +31,18 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.loginPage("/user/login")
.defaultSuccessUrl("/")
.and()
.oauth2Login()
.loginPage("/user/login")
.userInfoEndpoint()
.userService(oauth2UserService)
.and()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/")
.invalidateHttpSession(true);

return http.build();
}

Expand Down
34 changes: 28 additions & 6 deletions src/main/java/com/mysite/sbb/question/QuestionController.java
Expand Up @@ -9,6 +9,8 @@
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
Expand All @@ -26,6 +28,19 @@ public class QuestionController {
private final UserService userService;
private final AnswerService answerService;

private SiteUser getSiteUser(Principal principal) {
SiteUser siteUser = null;
if (principal instanceof OAuth2AuthenticationToken) {
OAuth2User oAuth2User = ((OAuth2AuthenticationToken) principal).getPrincipal();
String email = oAuth2User.getAttribute("email");
siteUser = this.userService.findByEmail(email);
siteUser.setGoogleId(email);
} else {
siteUser = this.userService.getUser(principal.getName());
}
return siteUser;
}

@GetMapping("/list")
public String list(Model model, @RequestParam(value="page", defaultValue="1") int page, @RequestParam(value = "kw", defaultValue = "") String kw) {
Page<Question> paging = this.questionService.getList(page,kw);
Expand All @@ -35,9 +50,11 @@ public String list(Model model, @RequestParam(value="page", defaultValue="1") in
}

@GetMapping(value = "/detail/{id}")
public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm) {
public String detail(Model model, @PathVariable("id") Integer id, AnswerForm answerForm,Principal principal) {
Question question = this.questionService.getQuestion(id);
model.addAttribute("question", question);
SiteUser siteUser = getSiteUser(principal);
model.addAttribute("siteUser",siteUser);
return "question_detail";
}

Expand All @@ -53,7 +70,7 @@ public String questionCreate(@Valid QuestionForm questionForm, BindingResult bin
if (bindingResult.hasErrors()) {
return "question_form";
}
SiteUser siteUser = this.userService.getUser(principal.getName());
SiteUser siteUser = getSiteUser(principal);
Question q = this.questionService.create(questionForm.getSubject(), questionForm.getContent(),siteUser);
return "redirect:/question/detail/"+ q.getId();
}
Expand All @@ -62,7 +79,8 @@ public String questionCreate(@Valid QuestionForm questionForm, BindingResult bin
@GetMapping("/modify/{id}")
public String questionModify(QuestionForm questionForm, @PathVariable("id") Integer id, Principal principal) {
Question question = this.questionService.getQuestion(id);
if(!question.getAuthor().getUsername().equals(principal.getName())) {
SiteUser siteUser = getSiteUser(principal);
if(!question.getAuthor().getUsername().equals(siteUser.getUsername())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
questionForm.setSubject(question.getSubject());
Expand All @@ -78,7 +96,9 @@ public String questionModify(@Valid QuestionForm questionForm, BindingResult bin
return "question_form";
}
Question question = this.questionService.getQuestion(id);
if (!question.getAuthor().getUsername().equals(principal.getName())) {
SiteUser siteUser = getSiteUser(principal);

if (!question.getAuthor().getUsername().equals(siteUser.getUsername())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "수정권한이 없습니다.");
}
this.questionService.modify(question, questionForm.getSubject(), questionForm.getContent());
Expand All @@ -89,7 +109,9 @@ public String questionModify(@Valid QuestionForm questionForm, BindingResult bin
@GetMapping("/delete/{id}")
public String questionDelete(Principal principal, @PathVariable("id") Integer id) {
Question question = this.questionService.getQuestion(id);
if (!question.getAuthor().getUsername().equals(principal.getName())) {
SiteUser siteUser = getSiteUser(principal);

if (!question.getAuthor().getUsername().equals(siteUser.getUsername())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "삭제권한이 없습니다.");
}
this.questionService.delete(question);
Expand All @@ -100,7 +122,7 @@ public String questionDelete(Principal principal, @PathVariable("id") Integer id
@GetMapping("/vote/{id}")
public String questionVote(Principal principal, @PathVariable("id") Integer id) {
Question question = this.questionService.getQuestion(id);
SiteUser siteUser = this.userService.getUser(principal.getName());
SiteUser siteUser = getSiteUser(principal);
this.questionService.vote(question, siteUser);
return String.format("redirect:/question/detail/%s", id);
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/mysite/sbb/question/QuestionService.java
Expand Up @@ -27,6 +27,8 @@
public class QuestionService {
private final QuestionRepository questionRepository;



public Page<Question> getList(int page,String kw) {
Pageable pageable = PageRequest.of(page-1,10,Sort.by("createDate").descending());
Specification<Question> spec = search(kw);
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/mysite/sbb/user/Oauth2UserSecurityService.java
@@ -0,0 +1,28 @@
package com.mysite.sbb.user;

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 Oauth2UserSecurityService extends DefaultOAuth2UserService {
private UserRepository userRepository;
private UserService userService;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String provider = userRequest.getClientRegistration().getRegistrationId(); //google
String providerId = oAuth2User.getAttribute("sub");
String username = provider+"_"+providerId; // 사용자가 입력한 적은 없지만 만들어준다
String email = oAuth2User.getAttribute("email");

System.out.println("provider : "+ provider);
System.out.println("providerId : "+ providerId);
System.out.println("username : "+ username);
System.out.println("email = " + email);
return super.loadUser(userRequest);
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/mysite/sbb/user/SiteUser.java
Expand Up @@ -19,4 +19,7 @@ public class SiteUser {

@Column(unique = true)
private String email;

@Column(unique = true)
private String googleId;
}
2 changes: 2 additions & 0 deletions src/main/java/com/mysite/sbb/user/UserController.java
Expand Up @@ -34,6 +34,8 @@ public String signup(@Valid UserCreateForm userCreateForm, BindingResult binding
return "signup_form";
}



userService.create(userCreateForm.getUsername(),
userCreateForm.getEmail(), userCreateForm.getPassword1());

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/mysite/sbb/user/UserRepository.java
@@ -1,9 +1,11 @@
package com.mysite.sbb.user;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

public interface UserRepository extends JpaRepository<SiteUser,Long> {
Optional<SiteUser> findByusername(String username);
Optional<SiteUser> findByEmail(@Param("email") String email);
}
1 change: 1 addition & 0 deletions src/main/java/com/mysite/sbb/user/UserSecurityService.java
Expand Up @@ -25,6 +25,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx
if (_siteUser.isEmpty()) {
throw new UsernameNotFoundException("사용자를 찾을수 없습니다.");
}

SiteUser siteUser = _siteUser.get();
List<GrantedAuthority> authorities = new ArrayList<>();
if ("admin".equals(username)) {
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/mysite/sbb/user/UserService.java
Expand Up @@ -32,4 +32,15 @@ public SiteUser getUser(String username) {
throw new DataNotFoundException("siteuser not found");
}
}

public SiteUser findByEmail(String email){
Optional<SiteUser> siteUser = this.userRepository.findByEmail(email);
if(siteUser.isPresent()){
SiteUser user = siteUser.get();
if(user.getGoogleId()==null) user.setGoogleId(email);
return user;
}else{
throw new DataNotFoundException("siteuser not found");
}
}
}
6 changes: 5 additions & 1 deletion src/main/resources/application.properties
Expand Up @@ -4,5 +4,9 @@ spring.datasource.url=jdbc:mariadb://localhost:3306/ssb
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=
# ???? ???? ???? ???? ??? ????. update : ??? ???? db? ?? ??? ??
spring.jpa.hibernate.ddl-auto=update
server.port = 8080
# google
spring.security.oauth2.client.registration.google.client-id = 4333200682-0qe2195o66fefsin35rsdinlrvm6923f.apps.googleusercontent.com
spring.security.oauth2.client.registration.google.client-secret = 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
spring.security.oauth2.client.registration.google.scope = profile, email
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/main/resources/templates/login_form.html
Expand Up @@ -16,5 +16,8 @@
</div>
<button type="submit" class="btn btn-primary">로그인</button>
</form>
<a href="/oauth2/authorization/google">
<img src="/img/google-login-icon.png" alt="Google 로그인">
</a>
</div>
</html>
8 changes: 4 additions & 4 deletions src/main/resources/templates/question_detail.html
Expand Up @@ -25,11 +25,11 @@ <h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
</a>
<a th:href="@{|/question/modify/${question.id}|}" class="btn btn-sm btn-outline-secondary"
sec:authorize="isAuthenticated()"
th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
th:if="${question.author != null and siteUser.username == question.author.username}"
th:text="수정"></a>
<a href="javascript:void(0);" th:data-uri="@{|/question/delete/${question.id}|}"
class="delete btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
th:if="${question.author != null and #authentication.getPrincipal().getUsername() == question.author.username}"
th:if="${question.author != null and siteUser.username == question.author.username}"
th:text="삭제"></a>
</div>
</div>
Expand Down Expand Up @@ -63,12 +63,12 @@ <h2 class="border-bottom py-2" th:text="${question.subject}"></h2>
</a>
<a th:href="@{|/answer/modify/${answer.id}|}" class="btn btn-sm btn-outline-secondary"
sec:authorize="isAuthenticated()"
th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
th:if="${answer.author != null and siteUser.username == answer.author.username}"
th:text="수정">
</a>
<a href="javascript:void(0);" th:data-uri="@{|/answer/delete/${answer.id}|}"
class="delete btn btn-sm btn-outline-secondary" sec:authorize="isAuthenticated()"
th:if="${answer.author != null and #authentication.getPrincipal().getUsername() == answer.author.username}"
th:if="${answer.author != null and siteUser.username == answer.author.username}"
th:text="삭제"></a>
</div>
</div>
Expand Down

0 comments on commit a1ddb8f

Please sign in to comment.