이 포스팅은 스프링 부트와 AWS로 혼자 구현하는 웹 서비스
를 보며 공부한 내용을 정리하는 포스팅입니다.
먼저 build.gradle에 스프링 시큐리티 관련 의존성 하나를 추가한다.
spring-boot-starter-oauth2-client
-> 소셜 로그인 등 클라이언트 입장에서 소셜 기능 구현 시 필요한 의존성입니다.
-> spring-security-oauth2-client와 spring-security-oauth2-jose를 기본으로 관리해줌
시큐리티 관련 클래스를 모두 담아줄 config.auth 패키지를 만들어준다.
먼저 SecurityConfig 클래스를 만들어 준다
@EnableWebSecurity
-> Spring Security 설정들을 활성화시켜 준다.@csrf().disable().headers().frameOptions().disable()
-> h2-console 화면을 사용하기 위해 해당 옵션들을 disable 합니다.authorizeRequest
-> URL별 권한 관리를 설정하는 옵션의 시작점이다.
-> authorizeRequest가 선언되야만 andMatchers 옵션을 사용할 수 있다.andMatchers
-> 권환 관리 대상을 지정하는 옵션이다
-> URL, HTTP 메소드별로 관리가 가능하다.
-> "/"등 지정된 URL들은 permitAll()옵션을 통해 전체 열람 권한을 줬다.
-> "/api/v1/** "주소를 가진 API는 USER 권한을 가진 사람만 가능하도록 했다.anyRequset
-> 설정된 값을 이외 나머지 URL들을 나타냄.
-> 여기서는 authenticated()을 추가하여 나머지 URL들은 모두 인증된 사용자들에게만 허용하게 해줌.
-> 인증된 사용자 즉, 로그인한 사용자들을 의미함.logout().logoutSuccessUrl("/")
-> 로그아웃 기능에 대한 여러 설정의 진입점이다.
-> 로그아웃 성공시 / 주소로 이동함oauth2Login
-> Oauth2 로그인 기능에 대한 여러 설정의 진입점이다.userInfoEndpoint
-> OAuth 2 로그인 성공 이후 사용자 정보를 가져올 때의 설정들을 담당함.userService
-> 소셜 로그인 성공 시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록함.
-> 리소스 서버에서 사용자 정보를 가져온 상태에서 추가로 진행하고자 하는 기능을 명시 가능.
설정 코드 작성이 끝났으면 CustomOAuth2UserService 클래스를 생성한다
이 클래스는 구글 로그인 이후 가져온 사용자의 정보(email, name, picture)들을 기반으로
가입 및 정보수정, 세션 저장 등의 기능을 지원한다.
registrationId
-> 현재 로그인 진행 중인 서비스를 구분하는 코드
-> 지금은 구글만 사용하는 불필요한 값이지만, 이후 네이버 로그인 연동 시에 네이버 로그인인지, 구글 로그인인지 구분하기 위해 사용.userNameAttributeName
-> OAuth2 로그인 진행 시 키가 되는 필드값을 이야기함. Primary Key와 같은 의미
-> 구글의 경우 기본적으로 코드를 지원하지만, 네이버, 카카오 등은 기본 지원하지않음
구글의 기본 코드는 "sub"
-> 이후 네이버 로그인과 구글 로그인을 동시 지원할 때 사용.OAuthAttributes
-> OAuth2UserService를 통해 가져온 OAuth2 User의 attribute를 담을 클래스.
-> 이후 네이버 등 다른 소셜 로그인도 이 클래스를 사용함SessionUser
-> 세션에 사용자 정보를 저장하기 위한 Dto 클래스
-> 왜 User 클래스를 쓰지 않고 새로 만들어 쓰는지는 뒤이어서 설명함
구글 사용자 정보가 업데이트 되었을 때를 대비하여 update 기능도 같이 구현함
사람의 이름이나 프로필 사진이 변경되면 User 엔티티에도 반영됨.
CustomOAuth2UserService 클래스까지 생성되었다면 OAuthAttributes 클래스를 생성
OAuthAttribtes는 Dto로 보기 때문에 config.auth.dto 패키지를 생성해줌
@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 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();
}
public User toEntity() {
return User.builder()
.name(name)
.email(email)
.picture(picture)
.role(Role.GUEST)
.build();
}
}
of()
-> OAuth2User에서 반환하는 사용자 정보는 Map이기 때문에 값 하나하나를 변환해만 함.toEntity()
-> User 엔티티를 생성함
-> OAuthAttributes에서 엔티티를 생성하는 시점은 처음 가입할 때이다.
-> 가입할 때의 기본 권한을 GUEST로 주기 위해서 role 빌더값에는 Role.GUEST를 사용
-> OAuthAttributes 클래스 생성이 끝났으면 같은 패키지에 SessionUser클래스를 생성
예상치 못한 곳에서 오류가 떳다..
아무래도 여기가 문제인듯하다 고쳐보자
확인해 보니 Role이 내가 만든게아니라 이상한게 import되어있었다.
해결!
SessionUser도 만들어 줬다
SessionUser에는 인증된 사용자 정보만 필요하다
그 외에 필요한 정보들은 없으니 name, email, picture만 필드로 선언한다
왜 User 클래스를 사용하면 안 되나요?
세션에 저장하기 위해 User 클래스를 세션에 저장하려고 하니 User 클래스에
직렬화를 구현하지 않았다라는 의미를 가진 에러가 나온다
User 클래스에 직렬화 코드를 넣으면 될까? 그것에 대해선 생각해 볼 것이 많다
이유는 User 클래스가 엔티티이기 때문이다. 엔티티 클래스에는 언제 다른 엔티티와
관계가 형성될지 모른다
예를 들어 @OneToMany, @ManyToMany 등 자식 엔티티를 갖고 있다면 직렬화 대상에
자식들까지 포함되니 성능 이슈, 부수 효과가 발생할 활률이 높다
그래서 직렬화 기능을 가진 세션 Dto를 하나 추가로 만드는게 이후 운영 및 유지보수에
도움이 된다.
스프링 시큐리티가 잘 적용되었는지 확인해보기 위해 로그인 버튼을 추가해주가
{{#userName}}
-> 머스테치는 다른 언어와 같은 if문을 제공하지 않는다
-> true/false 여부만 판단한다
-> 그래서 머스테치에서는 항상 최종값을 넘겨줘야 한다
-> 여기서도 역시 userName이 있다면 userName을 노출시키도록 구성했다.a href="/logout"
-> 스프링 시큐리티에서 기본적으로 제공하는 로그아웃 URL이다
-> 즉, 개발자가 별도로 저 URL에 해당하는 컨트롤러를 만들 필요가 없다.
-> SecurityConfig 클래스에서 URL을 변경할 순 있지만 기본 URL을 사용해도 충분하다.{{^userName}}
-> 머스테치에서 해당 값이 존재하지 않는 경우에는 ^를 사용한다.
-> 여기서는 userName이 없다면 로그인 버튼을 노출시키도록 구성함a href="/oauth2/authorization/google
-> 스프링 시큐리티에서 기본적으로 제공하는 로그인 URL이다
-> 로그아웃 URL과 마찬가지로 개발자가 별도의 컨트롤러를 생성할 필요가 없다
index.mustach에서 userName을 사용할 수 있게 IndexController에서 userName을 model에
저장하는 코드를 추가한다.
(SessionUser) httpSession.getAttribute("user")
-> 앞서 작성된 CustomOAuth2UserService에서 로그인 성공 시 세션에 SessionUser를 저장하도록 구성함
-> 즉, 로그인 성공 시 httpSession.getAttribute("user")에서 값을 가져올 수 있음.if(user != null)
-> 세션에 저장된 값이 있을 때만 model에 userName으로 등록한다
-> 세션에 저장된 값이 없으면 model엔 아무런 값이 없는 상태이니 로그인 버튼이 보이게 됨.
성공했다.
데이터베이스에도 값이 잘 들어왔다.
현재 로그인된 사용자의 권한은 GUEST이다. 이 상태에서는 posts기능을 사용할 수 없다.
권한을 h2-console로 가서 role을 USER로 변경해준다