거의 2~3달 가까이 퇴근하고 틈틈이 만들지만 아직까지 Spring Bean Handling에 미숙함이 많아 시간이 많이 걸리지만 Spring Lifecycle에 대해 좀 더 심도 있게 파고드는 부분은 정말 도움이 많이된다.
또한, Spring 대부분이 상속(Extend) 또는 구현(Implement)를 통해 커스텀(Custom) 되는 부분으로 인해 소스를 Custom 전 파악 후 하는 부분에 있어서 나도 개발이 자연스러워지는 점에선 큰 도움이 되고있다.
1. 개발 요지
1) Spring F/W 자체로는 구성이 쉬움. But Security 와 OAuth2를 적용하기엔 난이도가 급상승하여
개발을 막 시작한 사용자들이 개발하기엔 난이도가 존재한다.
→실제로 프로젝트에도 Spring Security를 적용하지 않는 경우가 허다하고 대체로 AspectJ AOP 형태(Intercept)로 개발하는 경우가 많다.
→ 운영 입장에서도 Spring Security의 Lifecycle을 모른다면 쉽지 않음.
→ Spring Security 는 Role으로 시작해서 Role로 끝나는 FW으로 봐도 무방하다.
2) OAuth2는 정규스펙이 아닌 비정규 스펙으로 정의하여 만들어둔 기업이 몇몇 존재한다.
ex) /authorize => /token 시 Response 된 access_token으로 사용자(Member) 정보를 가져오는 경우가 일반적인데
Payco 처럼 /token 발행시 access_token_secret , access_token 이중으로 발행하여, 초기에 authorize, token시 발급받은 access_token_secret과 API가 일치하지 않아 별도로 로직 개발이 필요한 경우 매우 난감하다.
// 비정규 스펙으로 인해 별도로 userInfo 부분만 Custom 하였다.
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
httpHeaders.add("client_id", oAuth2UserRequest.getClientRegistration().getClientId());
httpHeaders.add("access_token", oAuth2UserRequest.getAccessToken().getTokenValue());
URI uri = UriComponentsBuilder.fromUriString(oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri()).build().toUri();
RequestEntity requestEntity = new RequestEntity(httpHeaders, HttpMethod.POST, uri);
ResponseEntity<Map> response = restTemplate.exchange(requestEntity, Map.class);
Map<String, Object> userAttributes = (Map) response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = oAuth2UserRequest.getAccessToken();
token.getScopes().forEach(scope -> authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)));
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
2. 개발 방향
1) 별도 envConfig.yml(가칭) 에서 active 목록에 ex) Google, Payco 등을 넣고 @Component로 설정 Bean을 생성하면 자동으로 로그인이 사용 될 수 있는 구조가 되도록 작성하려한다.
→ spring-security-config-5.5.0.jar > CommonOAuth2Provider.class
※ 종류가 적다 : Google, Github, Facebook, Okta
→ 다이나믹(Dynamic) 형태로 국내 지원을 포함한 FW Library를 만들어 보고싶다.
→ 개발 완료
- 국내 : Kakao, Naver, Payco
- 해외 : Google, Facebook, Github, Gitlab
→ 개발 중
- 국내 : Searching....
- 해외 : Twitch, Okta
3. 개발 구조
1) 설정 파일
2) 소스 구성
a. SpringSecurityUserAttributeFaceBook.java
b. OAuthForFacebook.java (@Configuration) / implement OAuthAPI.java
→ Q : @Value를 이용하여 설정을 사용한 이유
A : OAuth 종류별로 Configuation 파일 생성됨에 따라 일회성 클래스인데 불구하고 로드가 계속 필요하기 때문에
자원 및 구조 낭비 방지를 위해 Class in values 이용.
→ Q : Apache-commons BeanUtils 대신 Jackson ObjectMapper 이용 이유
A : Apche-commons BeanUtils의 경우 describe , Populate시 Property가 완전 일치되어야만 정상 처리 됨에 따라 핸들링이 나쁜 점이 존재하여 Jackson ObjectMapper를 이용하면 @JsonIgnore 와 같은 Jackson 내장 Annotation을 이용하여 처리함.
@Configuration(value = "oAuthFacebook")
@Getter
@Setter
@ToString
@Slf4j
public class OAuthForFacebook implements OAuthAPI {
private static String svrName = "Facebook";
@Autowired
private JsonMapper jsonMapper;
@Value(value = "${OAuth.Facebook.api}")
private String oAuthKey;
@Value(value = "${OAuth.Facebook.secret}")
private String oAuthSecret;
@Value(value = "${OAuth.Facebook.callback}")
private String oAuthcallback;
@Value(value = "${OAuth.Facebook.request.profile}")
private String requestProfile;
@Value(value = "${OAuth.Facebook.request.authorize}")
private String requestAuthorize;
@Value(value = "${OAuth.Facebook.request.token}")
private String requestToken;
@Value(value = "${OAuth.Facebook.request.user-name-attribute}")
private String requestUserNameAttribute;
@Value(value = "${OAuth.Facebook.response.redirectUri}")
private String responseRedirectUri;
@Value(value = "${OAuth.Facebook.response.redirectUriStr}")
private String responseRedirectUriStr;
@Value(value = "${OAuth.Facebook.scope}")
private String[] scope;
@Override
public String getOAuthApiKey() {
return this.getOAuthApiKey();
}
@Override
public LinkedMultiValueMap getParameter() {
return null;
}
@Override
public SessionUser<OAuthForFacebook> getOAuthForUserInfo() {
return null;
}
@Override
public SessionUser<OAuthForFacebook> getOAuthForProfile() {
return null;
}
@Override
public ClientRegistration clientRegistration() {
return ClientRegistration.withRegistrationId(this.svrName)
.clientId(this.oAuthKey)
.clientSecret(this.oAuthSecret)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope(this.scope)
.userNameAttributeName(this.requestUserNameAttribute)
.authorizationUri(this.requestAuthorize)
.tokenUri(this.requestToken)
.userInfoUri(this.requestProfile)
.clientName(this.svrName)
.build();
}
@Override
public String getPopupUrl() throws Exception {
return new URIBuilder("/oauth2/authorization/" + this.svrName).toString();
}
@Override
public SpringSecurityUser<OAuthForFacebook> of(String userNameAttributeName, Map<String, Object> attributes) {
Map<String, Object> pictureMap = (Map<String, Object>) attributes.get("picture");
Map<String, Object> pictureDataMap = (Map<String, Object>) pictureMap.get("data");
SpringSecurityUserAttributeFaceBook springSecurityUserAttributeFaceBook = jsonMapper.convertValue(attributes, SpringSecurityUserAttributeFaceBook.class);
springSecurityUserAttributeFaceBook.setPicture_width((Integer) pictureDataMap.get("width"));
springSecurityUserAttributeFaceBook.setPicture_height((Integer) pictureDataMap.get("height"));
springSecurityUserAttributeFaceBook.setPicture_is_silhouette((Boolean) pictureDataMap.get("is_silhouette"));
springSecurityUserAttributeFaceBook.setPicture_url((String) pictureDataMap.get("url"));
return SpringSecurityUser.create()
.socialData(springSecurityUserAttributeFaceBook)
.name(springSecurityUserAttributeFaceBook.getName())
.picture(springSecurityUserAttributeFaceBook.getPicture_url())
.email(springSecurityUserAttributeFaceBook.getEmail())
.socialKey(springSecurityUserAttributeFaceBook.getId())
.addAuthority(Role.GUEST)
.addAuthority(Role.FACEBOOK)
;
}
@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws Exception {
return null;
}
@Override
public boolean isDefaultLogin() throws Exception {
return true;
}
}
#스프링시큐리티스프링시큐리티,#스프링 OAuth2스프링 OAuth2,#Naver OAuth2Naver OAuth2,#Kakao OAuth2Kakao OAuth2,#Payco OAuth2Payco OAuth2,#Google OAuth2Google OAuth2,#Facebook OAuth2Facebook OAuth2,#Github OAuth2Github OAuth2,#Gitlab OAuth2Gitlab OAuth2,#스프링 시큐리티 OAuth2스프링 시큐리티 OAuth2,
'Programming > 스프링 F.W' 카테고리의 다른 글
[SPRING][JUNIT] Junit Spring F/W XML 구동시 필수 확인사항 (0) | 2022.08.05 |
---|---|
[SPRING][SPRING BOOT] JUnit4~5 기본 구성 (0) | 2022.08.02 |
[Springboot][스프링부트]스프링 멀티모듈 프로젝트 Working Directory 이슈 (0) | 2021.12.05 |