목차
이전글
Spring Security는 각 유명한 사이트들(구글, 깃허브, 페이스북, okta)의 OAuth2를 미리 설정해두었다.
public enum CommonOAuth2Provider {
GOOGLE {
@Override
public Builder getBuilder(String registrationId) {
ClientRegistration.Builder builder = getBuilder(registrationId,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);
builder.scope("openid", "profile", "email");
builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");
builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");
builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");
builder.issuerUri("https://accounts.google.com");
builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");
builder.userNameAttributeName(IdTokenClaimNames.SUB);
builder.clientName("Google");
return builder;
}
},
....
}
그래서 구글 로그인을 연동할때 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
하지만 네이버의 경우는 기본 Provider가 아니기때문에 application.properties에 부가적인 내용을 등록해주어야한다!
OAuth2 설정 파트에서 확인하자.
Spring Security - OAuth2 네이버 로그인하기
네이버 설정
1. 네이버 개발자 센터 접속
2. Application > 애플리케이션 등록
3. 약관동의, 계정정보등록 후 애플리케이션 등록
> 애플리케이션 이름 작성 > 사용 API : 네이버 로그인 클릭 > 회원이름, 이메일주소 클릭 > 서비스환경 PC웹 클릭
4. 서비스 URL : http://localhost:8080 입력
콜백 URL : http://localhost:8080/login/oauth2/code/naver 입력 후 등록
5. 아이디와 비밀번호 기억해두자
OAuth2 설정
build.gradle : 이전과 동일함
application.properties
# naver
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
spring.security.oauth2.client.registration.naver.client-name = Naver
spring.security.oauth2.client.registration.naver.authorization-grant-type = authorization_code
spring.security.oauth2.client.registration.naver.redirect-uri = http://localhost:8080/login/oauth2/code/naver
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
registration.naver.client-name | 자동 생성되는 로그인 페이지에서 노출하는 등에 사용한다(꼭 있어야하는지는 모르겠다..) |
registration.naver.authorization-grant-type | OAuth2는 4가지 Authorization Grant유형이 있다 네이버는 가장 많이 사용되는 authorization_code 방식을 이용한다 (참고) |
registration.naver.redirect-uri | 네이버가 사용자 확인 후 정보를 내 프로젝트로 보내주는 주소 구글처럼 패턴을 가지고 있다. "{baseUrl}/login/oauth2/code/{registrationId}" |
provider.naver.authorization-uri | 인증을 요청하는 url을 작성한다 (#3.4.2) |
provider.naver.token-uri | 토근을 요청하는 url을 작성한다 (#3.4.4) |
provider.naver.user-info-uri | 회원 정보를 가져오는 url을 작성한다 (#3.4.5) |
provider.naver.user-name-attribute | { "resultCode":~, "message":~, "response": { "email":~, "name":~, ... } } 네이버는 사용자정보를 이런식으로 반환해주는데, spring security에서는 하위 필드를 명시할 수 없고 최상위 필드만 user_name으로 지정할 수 있다. 네이버의 최상위필드는 resultCode, message, response이기때문에 3개 중에 골라야하는데, 하위필드가 존재하는 response를 user_name으로 지정한다. (#3.4.5) |
OAuth2UserInfo 인터페이스와 GoogleUserInfo 클래스, NaverUserInfo 클래스
기존 구글 로그인에서는 providerId값을 "sub"로 받았지만 네이버의 경우는 "id"로 받아야한다.
다른 OAuth2 로그인을 추가하게된다면 경우에 따라서 providerId값이 아닌 email, name 값을 다르게 설정해야할수도있다. 추후 유지보수를 위해 인터페이스를 생성하자!
//구글의 경우
String providerId = oAuth2User.getAttribute("sub");
//네이버의 경우
String providerId = oAuth2User.getAttributes().get("response").get("id").toString();
{ "resultCode":~, "message":~, "response": { "id":~, "email":~, "name":~, ... } }
패키지 > auth 폴더 > userinfo 폴더 > OAuth2UserInfo.java
public interface OAuth2UserInfo {
Map<String, Object> getAttributes();
String getProviderId();
String getProvider();
String getEmail();
String getName();
}
패키지 > auth 폴더 > userinfo 폴더 > GoogleUserInfo.java
public class GoogleUserInfo implements OAuth2UserInfo{
private Map<String, Object> attributes;
public GoogleUserInfo(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getProviderId() {
return attributes.get("sub").toString();
}
@Override
public String getProvider() {
return "google";
}
@Override
public String getEmail() {
return attributes.get("email").toString();
}
@Override
public String getName() {
return attributes.get("name").toString();
}
}
패키지 > auth 폴더 > userinfo 폴더 > NaverUserInfo.java
public class NaverUserInfo implements OAuth2UserInfo{
private Map<String, Object> attributes; //OAuth2User.getAttributes();
private Map<String, Object> attributesResponse;
public NaverUserInfo(Map<String, Object> attributes) {
this.attributes = (Map<String, Object>) attributes.get("response");
this.attributesResponse = (Map<String, Object>) attributes.get("response");
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getProviderId() {
return attributesResponse.get("id").toString();
}
@Override
public String getProvider() {
return "naver";
}
@Override
public String getEmail() {
return attributesResponse.get("email").toString();
}
@Override
public String getName() {
return attributesResponse.get("name").toString();
}
}
OAuth2Login loadUserByUsername
패키지 > auth 폴더 > PrincipalOauth2UserService.java
package com.example.demo.oauth;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
@Autowired private UserRepository userRepository;
@Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
OAuth2UserInfo oAuth2UserInfo = null; //추가
String provider = userRequest.getClientRegistration().getRegistrationId();
//추가
if(provider.equals("google")){
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
}
else if(provider.equals("naver")){
oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
}
String providerId = oAuth2UserInfo.getProviderId(); //수정
String username = provider+"_"+providerId;
String uuid = UUID.randomUUID().toString().substring(0, 6);
String password = bCryptPasswordEncoder.encode("패스워드"+uuid);
String email = oAuth2UserInfo.getEmail(); //수정
Role role = Role.ROLE_USER;
User byUsername = userRepository.findByUsername(username);
//DB에 없는 사용자라면 회원가입처리
if(byUsername == null){
byUsername = User.oauth2Register()
.username(username).password(password).email(email).role(role)
.provider(provider).providerId(providerId)
.build();
userRepository.save(byUsername);
}
return new PrincipalDetails(byUsername, oAuth2UserInfo); //수정
}
}
OAuth2Login Authentication OAuth2User
패키지 > auth 폴더 > PrincipalDetails.java
package com.example.demo.auth;
@Getter
@ToString
public class PrincipalDetails implements UserDetails, OAuth2User {
private User user;
//private Map<String, Object> attributes;
private OAuth2UserInfo oAuth2UserInfo;
//UserDetails : Form 로그인 시 사용
public PrincipalDetails(User user) {
this.user = user;
}
//OAuth2User : OAuth2 로그인 시 사용
//public PrincipalDetails(User user, Map<String, Object> attributes) {
// //PrincipalOauth2UserService 참고
// this.user = user;
// this.attributes = attributes;
//}
public PrincipalDetails(User user, OAuth2UserInfo oAuth2UserInfo) {
this.user = user;
this.oAuth2UserInfo = oAuth2UserInfo;
}
/**
* UserDetails 구현
* 해당 유저의 권한목록 리턴
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collect = new ArrayList<>();
collect.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole().toString();
}
});
return collect;
}
/**
* UserDetails 구현
* 비밀번호를 리턴
*/
@Override
public String getPassword() {
return user.getPassword();
}
/**
* UserDetails 구현
* PK값을 반환해준다
*/
@Override
public String getUsername() {
return user.getUsername();
}
/**
* UserDetails 구현
* 계정 만료 여부
* true : 만료안됨
* false : 만료됨
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* UserDetails 구현
* 계정 잠김 여부
* true : 잠기지 않음
* false : 잠김
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* UserDetails 구현
* 계정 비밀번호 만료 여부
* true : 만료 안됨
* false : 만료됨
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* UserDetails 구현
* 계정 활성화 여부
* true : 활성화됨
* false : 활성화 안됨
*/
@Override
public boolean isEnabled() {
return true;
}
/**
* OAuth2User 구현
* @return
*/
@Override
public Map<String, Object> getAttributes() {
//return attributes;
return oAuth2UserInfo.getAttributes();
}
/**
* OAuth2User 구현
* @return
*/
@Override
public String getName() {
//String sub = attributes.get("sub").toString();
//return sub;
return oAuth2UserInfo.getProviderId();
}
}
OAuth2Login Domain구현
패키지 > domain 폴더 > Role.java : 이전과 같음
패키지 > domain 폴더 > User.java : 이전과 같음
OAuth2Login Controller 구현
패키지 > controller 폴더 > UserController.java : 이전과 같음
OAuth2Login View구현
resources > templates > join.html : 이전과 같음
resources > templates > login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta charset="UTF-8">
<title>로그인 페이지</title>
</head>
<body>
<h1>로그인 페이지</h1>
<hr/>
<h2>로그인 유저 : </h2>
<p sec:authentication="principal"></p>
<div sec:authorize="isAnonymous()" style="background-color:pink; padding:1em;">
<form action="/login" method="post" >
<input type="text" name="username" />
<input type="password" name="password" />
<button>로그인</button>
</form>
<a href="/oauth2/authorization/google">구글 로그인</a>
<a href="/oauth2/authorization/naver">네이버 로그인</a>
<!-- /oauth2/authorization/{registrationId}에 요청이 들어오면,
스프링 시큐리티가 provider의 authorization-uri로 요청을 전달한다-->
<br><br>
<a href="/joinForm">회원가입하기</a><br>
</div>
<div sec:authorize="isAuthenticated()" style="background-color:pink; padding:1em;">
<a href="/logout">로그아웃</a>
</div>
<br><br>
<a href="/user">유저</a><br>
<a href="/manager" sec:authorize="hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')">매니저</a><br>
<a href="/admin" sec:authorize="hasRole('ROLE_ADMIN')">어드민</a><br>
<br><br>
<a href="/form/loginInfo">Form 로그인 정보</a>
<a href="/oauth/loginInfo">OAuth2 로그인 정보</a>
<a href="/loginInfo">로그인 정보</a>
</body>
</html>
'Backend' 카테고리의 다른 글
[JPA] 연관관계 매핑 (0) | 2022.02.20 |
---|---|
[OAuth2] 동작과정 (0) | 2022.02.05 |
[Spring Security] OAuth 구글 로그인하기 (0) | 2022.02.02 |
[Spring Security] 동작방법 및 Form, OAuth 로그인하기 (Feat.Thymeleaf 타임리프) (8) | 2022.02.01 |
[JPA] Java Persistence API 등장배경, 사용방법 (0) | 2022.01.28 |