티스토리 뷰
프로젝트를 진행하다보니 쇼핑몰이나 다른 사이트에서 많이 보던 sns 로그인을 연동하여 사용하기로 했다
기존에 security로 로그인이 구현되어 있는 상태이다.
1. POM.XML oauth2 client add
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
2. clientId와 clientSecret setting, application.yml (해당 사이트에서 발급) - 따로 포스팅 되어 있지 않으니 발급 절차는 검색하세요
spring:
security:
oauth2:
client:
registration:
kakao:
client-id: 발급받은 client-id key
client-secret: 발급받은 client-secret key
google:
client-id: 발급받은 client-id key
client-secret: 발급받은 client-secret key
naver:
client-id: 발급받은 client-id key
client-secret: 발급받은 client-secret key
facebook:
client-id: 발급받은 client-id key
client-secret: 발급받은 client-secret key
3. WebSecurityConfigurerAdapter를 상속받은 class에서 ouath2Login 추가 (기존 security configure class에 추가)
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers().frameOptions().disable()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/member/update").authenticated()
.anyRequest().permitAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint("/" + loginPage))
.and()
.formLogin()
.loginPage("/" + loginPage)
.permitAll()
.usernameParameter("j_username")
.passwordParameter("j_password")
.loginProcessingUrl("/j_spring_security_check")
.defaultSuccessUrl("/", false)
.failureUrl("/" + loginPage + "?login=fail")
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOauth2UserService)
.and()
.defaultSuccessUrl("/",false)
.and()
.logout().permitAll()
.logoutUrl("/j_spring_security_logout")
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER)
.sessionFixation().migrateSession()
.maximumSessions(3)
;
}
4. CustomOAuth2Provider.class 추가 (기본적으로 구글, 페이스북은 CommonOAuth2Provider를 지원,
지원하지 않는 네이버, 카카오를 클래스에 추가해준다)
※ 로그인 인증 후에는 기본적으로 CustomOauth2UserService.java에 오버라이드 되어 있는 loadUser 메소드로 들어오게 되어 있는데 구글은 기본적으로 지원되는 CommonOAuth2Provider scope에 openid이 포함되어 있어 인증 후에는 OidcUserService를 통해 인증이 진행된다.
따라서, CustomOauth2UserService.java loadUser에서 후 처리를 해주려면 openid를 제외해주면 된다.
public enum CustomOAuth2Provider {
KAKAO {
public ClientRegistration.Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId, ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
builder.scope("profile");
builder.authorizationUri("https://kauth.kakao.com/oauth/authorize");
builder.tokenUri("https://kauth.kakao.com/oauth/token");
builder.userInfoUri("https://kapi.kakao.com/v2/user/me");
builder.userNameAttributeName("id");
builder.clientName("Kakao");
return builder;
}
},
NAVER {
public ClientRegistration.Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId, ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
builder.scope("profile");
builder.authorizationUri("https://nid.naver.com/oauth2.0/authorize");
builder.tokenUri("https://nid.naver.com/oauth2.0/token");
builder.userInfoUri("https://openapi.naver.com/v1/nid/me");
builder.userNameAttributeName("response");//네이버는 response로 지정
builder.clientName("Naver");
return builder;
}
},
GOOGLE {
public ClientRegistration.Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId, ClientAuthenticationMethod.POST, DEFAULT_LOGIN_REDIRECT_URL);
builder.scope(new String[]{"profile", "email"});
builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
builder.userNameAttributeName("sub");
builder.clientName("Google");
return builder;
}
};
private static final String DEFAULT_LOGIN_REDIRECT_URL = "{baseUrl}/login/oauth2/code/{registrationId}";
protected final ClientRegistration.Builder getBuilder( String registrationId, ClientAuthenticationMethod method, String redirectUri) {
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
builder.clientAuthenticationMethod(method);
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
builder.redirectUriTemplate(redirectUri);
return builder;
}
public abstract ClientRegistration.Builder getBuilder(String registrationId);
}
5. SecurityConfig.java 파일에 ClientRegistrationRepository 설정
(기본적으로 spring.security.oauth2.client.registration 안에 있는 값들을 프로젝트 빌드 시 모두 주입 시킨다)
@Bean
public ClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties oAuth2ClientProperties){
List<ClientRegistration> registrations = oAuth2ClientProperties.getRegistration().keySet().stream()
.map(client -> getRegistration(oAuth2ClientProperties, client))
.filter(Objects::nonNull) .collect(Collectors.toList());
return new InMemoryClientRegistrationRepository(registrations);
}
public ClientRegistration getRegistration(OAuth2ClientProperties oAuth2ClientProperties, String client) {
OAuth2ClientProperties.Registration registration = oAuth2ClientProperties.getRegistration().get(client);
String clientId = registration.getClientId();
String clientSecret = registration.getClientSecret();
if (clientId == null) {
return null;
}
switch (client){//구글, 페이스북은 제공, 네이버 카카오는 따로 Provider 선언해줘야함
case "google":
return customOAuth2Provider.GOOGLE.getBuilder(client)
.clientId(clientId).clientSecret(clientSecret).build();
case "facebook":
return CommonOAuth2Provider.FACEBOOK.getBuilder(client)
.clientId(clientId).clientSecret(clientSecret).build();
case "kakao":
return customOAuth2Provider.KAKAO.getBuilder(client)
.clientId(clientId)
.clientSecret(clientSecret).build();
case "naver":
return customOAuth2Provider.NAVER.getBuilder(client)
.clientId(clientId)
.clientSecret(clientSecret).build();
}
return null;
}
6. 인증이 완료되면 security에서 설정해준 userService인 CustomOauth2UserService로 리턴되게 되어있다
( override받은 loadUser로 떨어짐)
@Service
public class CustomOauth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
@Autowired
SecurityMapper securityMapper;
@Autowired
ServiceDocument serviceDocument;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2UserService delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);
Map<String, Object> userMap = new HashMap<>();
String registrationId = userRequest.getClientRegistration().getRegistrationId();
switch (registrationId){
case "kakao" :
Map<String, Object> profile = oAuth2User.getAttribute("kakao_account");
Map<String, Object> temp = (Map<String, Object>) profile.get("profile");
userMap.put("memberId", oAuth2User.getAttribute("id").toString());
userMap.put("email", profile.get("email"));
userMap.put("nickName", temp.get("nickname"));
userMap.put("accessToken",userRequest.getAccessToken().getTokenValue());
}
userMap.put("loginType",registrationId);
serviceDocument.oauth2UserInfo(userMap);
return oAuth2User;
}
}
인증 링크는 기본적으로 따로 설정하지 않으면 (http://localhost:8080/oauth2/authorization/{registrationId})를 탄다 CustomOAuth2Provider.class에 설정된 곳으로 인증을 태움
클라이언트 페이지에서 하단 예시와 같이 링크를 태우면 바로 로그인 창으로 떨어진다.
<a href="http://localhost:8080/oauth2/authorization/kakao">
<input type="button" value="카카오로그인" style="background-color: yellow;">
</a>
기본적인 redirect Uri = localhost:8080/login/oauth2/code/{registrationId}
security에서 authentication 리턴 타입이 두 가지(OAuth2User type, UserDetails type)가 있다
만약 두 가지를 다 사용해야 한다면 UserDetails와 OAuth2User는 상속받은 class들이 너무 달라 cast 하는데 문제가 있으니 사용하는 곳에서 따로 타입 체크 후 개발하는 것을 추천하다.
'Development > JAVA' 카테고리의 다른 글
[JAVA] Bearer token Authorization (0) | 2021.08.22 |
---|---|
[JAVA] URL 한글 인코딩 (0) | 2021.08.20 |
[JAVA] 페이징 pageable example (without jpa) (0) | 2021.08.20 |
[JAVA] ElasticSearch 키워드 검색 (0) | 2021.08.11 |
[JAVA] XSS Filter Config (0) | 2021.08.03 |
- Total
- Today
- Yesterday