Chapter 05
스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기
구글 서비스 등록
- 여기서 발급된 인증정보를 통해서 로그인 기능과 소셜 서비스 기능을 사용할 수 있음
- 구글 클라우드 플랫폼 주소(https://console.cloud.google.com) 이동
- 프로젝트 선택 -> 새 프로젝트 : freelec-springboot2-webservice 생성
- 왼쪽 메뉴 탭을 클릭해 API 및 서비스 -> 사용자 인증 정보 -> OAuth 클라이언트 ID 클릭
-> 동의 화면 구성
- 애플리케이션 이름을 아까와 같이 작성
- 다시 OAuth 클라이언트 ID 만들기 화면으로 이동
어플리케이션 유형 : 웹 어플리케이션
승인된 리디렉션 URI : http://localhost:8080/login/oauth2/code/google로 설정한 후 저장
클라이언트 ID와 클라이언트 보안비밀 코드 설정
- application-oauth 등록
src/main/resources/ 디렉토리에 application-oauth.properties 파일 생성
application.properties에 코드 추가
- .gitignore 등록
깃허브에 올라갈 수 있는 보안을 위해 아래 코드를 추가
application-oauth.properties
구글 로그인 연동하기
- domain 아래 user 패키지 -> 사용자 정보를 담당할 User 클래스 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
package com.qwon.springboot.domain.user;
import com.qwon.springboot.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Getter
@NoArgsConstructor
@Entity
public class User extends BaseTimeEntity {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
@Column(nullable=false)
private String name;
@Column(nullable = false)
private String email;
@Column
private String picture;
@Enumerated(EnumType.STRING)
@Column(nullable=false)
private Role role;
@Builder
public User(String name,String email,String picture,Role role){
this.name=name;
this.email=email;
this.picture=picture;
this.role=role;
}
public User update(String name,String picture){
this.name=name;
this.picture=picture;
return this;
}
public String getRoleKey(){
return this.role.getKey();
}
}
|
- 각 사용자의 권한을 관리할 Enum 클래스 Role 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package com.qwon.springboot.domain.user;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum Role {
GUEST("ROLE_GUEST","손님"),
USER("ROLE_USER","일반 사용자");
private final String key;
private final String title;
}
|
- User의 CRUD를 책임질 UserRepository 생성
1
2
3
4
5
6
7
8
9
10
11
12
|
package com.qwon.springboot.domain.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User,Long> {
Optional<User> findByEmail(String email);
}
|
스프링 시큐리티 설정
- build.gradle에 의존성 추가
compile('org.springframework.boot:spring-boot-starter-oauth2-client')
- com.qwon.springboot 아래 config.auth 패키지 생성 -> SecurityConfig 클래스 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
package com.qwon.springboot.config.auth;
import com.qwon.springboot.domain.user.Role;
import lombok.RequiredArgsConstructor;
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.WebSecurityConfigurerAdapter;
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/", "/css/**", "/images/**", "/js/**",
"/h2-console/**", "/profile").permitAll()
.antMatchers("/api/v1/**").hasRole(Role.USER.name())
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
}
|
cs |
- CustomOAuth2UserSerivce 클래스 생성 (구글 로그인 이후 가져온 사용자의 정보들을 기반으로 가입 및 정보수정, 세션 저장 등의 기능을 담당)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
package com.qwon.springboot.config.auth;
import com.qwon.springboot.config.auth.dto.OAuthAttributes;
import com.qwon.springboot.config.auth.dto.SessionUser;
import com.qwon.springboot.domain.user.User;
import com.qwon.springboot.domain.user.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.util.Collections;
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
OAuthAttributes attributes = OAuthAttributes.of(registrationId,
userNameAttributeName, oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
httpSession.setAttribute("user", new SessionUser(user));
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
attributes.getAttributes(),
attributes.getNameAttributeKey());
}
private User saveOrUpdate(OAuthAttributes attributes) {
User user = userRepository.findByEmail(attributes.getEmail())
.map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
.orElse(attributes.toEntity());
return userRepository.save(user);
}
}
|
- config/auth 아래 dto 패키지 생성 -> OAuthAttributes 클래스 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
package com.qwon.springboot.config.auth.dto;
import com.qwon.springboot.domain.user.Role;
import com.qwon.springboot.domain.user.User;
import lombok.Builder;
import lombok.Getter;
import java.util.Map;
@Getter
public class OAuthAttributes {
private Map<String, Object> attributes;
private String nameAttributeKey;
private String name;
private String email;
private String picture;
@Builder
public OAuthAttributes(Map<String, Object> attributes, String nameAttributeKey,
String name, String email, String picture) {
this.attributes = attributes;
this.nameAttributeKey = nameAttributeKey;
this.name = name;
this.email = email;
this.picture = picture;
}
public static OAuthAttributes of(String registrationId, String userNameAttributeName,
Map<String, Object> attributes) {
if("naver".equals(registrationId)) {
return ofNaver("id", attributes);
}
return ofGoogle(userNameAttributeName, attributes);
}
private static OAuthAttributes ofGoogle(String userNameAttributeName,
Map<String, Object> attributes) {
return OAuthAttributes.builder()
.name((String) attributes.get("name"))
.email((String) attributes.get("email"))
.picture((String) attributes.get("picture"))
.attributes(attributes)
.nameAttributeKey(userNameAttributeName)
.build();
}
private static OAuthAttributes ofNaver(String userNameAttributeName,
Map<String, Object> attributes) {
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
return OAuthAttributes.builder()
.name((String) response.get("name"))
.email((String) response.get("email"))
.picture((String) response.get("profile_image"))
.attributes(response)
.nameAttributeKey(userNameAttributeName)
.build();
}
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.GUEST)
.build();
}
}
|
- SessionUser 클래스 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package com.qwon.springboot.config.auth.dto;
import com.qwon.springboot.domain.user.User;
import java.io.Serializable;
@Getter public class SessionUser implements Serializable {
private String name;
private String email;
private String picture;
public SessionUser(User user){
this.name=user.getName();
this.email=user.getEmail();
this.picture=user.getPicture();
}
}
|
로그인 테스트
- index.mustache에 로그인 버튼 / 성공 시 사용자 이름이 나오도록 코드 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
....
<h1>스프링부트로 시작하는 웹 서비스 Ver.2</h1>
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<a href="/posts/save" role="button" class="btn btn-primary">글 등록</a>
{{#userName}}
Logged in as: <span id="user">{{userName}}</span>
<a href="/logout" class="btn btn-info active" role="button">Logout</a>
{{/userName}}
{{^userName}}
<a href="/oauth2/authorization/google" class="btn btn-success active"
role="button">Google Login</a> {{/userName}}
</div>
</div>
<br>
<!-- 목록 출력 영역 -->
<table class="table table-horizontal table-bordered">
....
|
- IndexController에 userName을 model에 저장하는 코드 추가
- 프로젝트 실행해보기
여기서 Googole Login을 눌렀는데 에러가 났다.
이 에러는 추후 확인해 봐야겠다.
- 이 원인을 이틀이 되어서야 해결했다.
정말 멍청한 짓을 하고 있었다.
application-oauth.properties 에서 위와 같이 클라이언트 ID, 보안비밀은 내 개인정보에 관한 내용을 담는 것이었다.
정보를 입력한 후, 로그인을 하면 아래와 같이 뜬다.
'언어 > SpringBoot' 카테고리의 다른 글
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 7장 (0) | 2020.10.21 |
---|---|
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 6장 (0) | 2020.10.19 |
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 4장 (0) | 2020.10.14 |
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 3장 (0) | 2020.10.12 |
스프링 부트와 AWS로 혼자 구현하는 웹 서비스 2장 (0) | 2020.10.09 |