Table of Contents
Google/Naver Login
개발환경
- Spring Boot 2.1.x
- Gradle 4.10.2
build.gradle 수정
build.gradle
......
dependencies {
// ......
compile('org.springframework.boot:spring-boot-starter-oauth2-client')
// ......
}
......
파일 수정 및 추가
src/main/java/kr/co/episode/example/config/oauth2/dto/OAuthAttributes.java
@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) {
//System.out.println(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)
.role(Role.USER)
.build();
}
}
src/main/java/kr/co/episode/example/config/oauth2/CustomOAuth2UserService.java
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final HttpSession httpSession;
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2UserService oAuth2UserService = new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(oAuth2UserRequest);
String registrationId = oAuth2UserRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
OAuthAttributes attributes = OAuthAttributes.
of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
User user = saveOrUpdate(attributes);
httpSession.setAttribute("user", new SessionUserDto(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);
}
}
src/main/java/kr/co/episode/example/config/SecurityConfig.java
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customOAuth2UserService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.headers().frameOptions().disable()
.and()
.authorizeRequests()
.antMatchers("/", "/css/**", "/images/**", "/js/**", "/oauth_login", "/profile").permitAll()
.antMatchers("/api/v1/**", "/posts/save").hasRole(Role.USER.name())
.anyRequest().authenticated()
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.oauth2Login()
.loginPage("/oauth_login")
.userInfoEndpoint()
.userService(customOAuth2UserService);
}
}
src/main/resources/application.properties
spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=
spring.security.oauth2.client.registration.google.scope=profile,email
# registration
spring.security.oauth2.client.registration.naver.client-id=
spring.security.oauth2.client.registration.naver.client-secret=
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.client-name=Naver
# provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response
src/main/resources/templates/oauth_login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>게시판</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<style>
.container p { display: inline }
</style>
</head>
<body class="container">
<h3>로그인:</h3>
<div class="col-md-12">
<div class="row">
<div class="col-md-6">
<a th:each="url : ${urls}" th:href="${url.link}" class="btn btn-success active" role="button" th:text="${url.client}"></a>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script src="/js/app/index.js"></script>
</body>
</html>
src/main/java/kr/co/episode/example/web/dto/LoginUrlDto.java
@Getter
public class LoginUrlDto {
private String client;
private String url;
public String getLink() {
return "/" + url;
}
public LoginUrlDto(String client, String url) {
this.client = client;
this.url = url;
}
}
src/main/resources/templates/index.html
<h1>스프링 부트 게시판</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>
<div th:if="${userName != null}" th:inline="text">
[[${userName}]] 님, 안녕하세요.
<a href="/logout" class="btn btn-info active" role="button">로그아웃</a>
</div>
<div th:if="${userName == null}">
<a href="/oauth2/authorization/google" class="btn btn-success active" role="button">구글 로그인</a>
<a href="/oauth2/authorization/naver" class="btn btn-success active" role="button">네이버 로그인</a>
</div>
</div>
</div>
</div>
<div style="height: 80px;">
</div>
src/main/java/kr/co/episode/example/web/LoginController.java
@Controller
public class LoginController {
private static String authorizationRequestBaseUri = "oauth2/authorization";
List<LoginUrlDto> oauth2AuthenticationUrls;
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@GetMapping("/oauth_login")
public String getLoginPage(Model model) {
if (oauth2AuthenticationUrls == null) {
oauth2AuthenticationUrls = new ArrayList();
Iterable<ClientRegistration> clientRegistrations = null;
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
clientRegistrations.forEach(registration ->
oauth2AuthenticationUrls.add(new LoginUrlDto(registration.getClientName(), authorizationRequestBaseUri + "/" + registration.getRegistrationId()))
);
}
}
model.addAttribute("urls", oauth2AuthenticationUrls);
return "oauth_login";
}
}