스프링 부트와 AWS로 혼자 구현하는 웹 서비스
https://github.com/HYEBPARK/springboot-webservice
- application-oauth.properties 파일 생성
spring.security.oatuh2.client.registration.google.client-id = cliendId sprint.security.oauth2.client.registration.google.client-secret = clientSecret spring.security.oauth2.client.registration.google.scope = profile,email
// scope의 기본값은 openid, profile, email 인데 profile과 email로 등록한 이유는
// openid라는 scope는 Open Id Provider 라고 인식을 하게 되는데
// OpenId Provider인 구글과는 다르게 naver, kakao는 그렇지 않아 각각 OAuth2Service를 만들어야 하기 때문
- 사용자 정보를 담당할 도메인 -> User class
@Enumerated(EnumType.STRING) @Column(nullable = false) private Role role;
- @Enumerated(EnumType.STRING)
- JPA로 저장할때 Enum값을 어떤 형태로 저장할지 결정
- 기본적으로 int형으로 저장됨-> db에서 무슨 코드를 의미하는지 알 수 없어서 string으로 선언
- Role은 Enum 클래스
- 각 사용자의 권한을 관리하는 Enum 클래스 Role
GUSET("ROLE_GUEST", "손님"), USER("ROLE_USER" , "일반사용자");
- 권한 코드에 항상 ROLE_이 앞에 있어야 한다.
- User의 CRUD를 책임지는 UserRpository
Optional<User> findByEmail(String email);
- findByEmail : 소셜 로그인으로 반환되는 값 중 email을 통해 이미 생성된 사용자인지 판단하기 위한 메소드
- build.gradle에 의존성 추가
implementation('org.springframework.boot:spring-boot-starter-oauth2-client') // 소셜 로그인 등 클라이언트 입장에서 소셜 기능 구현시 필요한 의존성
- spring-security-oauth2-client와 spring-security-oauth2-jose를 기본으로 관리해준다.
- jose(Javascript Object Signing and Encryption)
=> JWT(JSON Web Tokens)의 권한을 안전하게 전송하는 프레임워크
=> JWT의 리소스 접근 권한 정보의 암호화/복호화 및 일정 기능을 제공한다.
- SecurityConfig 클래스
@RequiredArgsConstructor @EnableWebSecurity // Spring Security 설정 활성화 public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CustomOAuth2UserService customOAuth2UserService; @Override protected void configure(HttpSecurity http) throws Exception{ http // h2-console 화면을 사용하기 위해서 disable .csrf().disable() .headers().frameOptions().disable() .and() .authorizeRequests() // URL별 권한 관리를 설정하는 옵션의 시작점 .antMatchers("/","/css/**","/images/**", "/js/**","/h2-console/**").permitAll() // 전체 열람 권한 .antMatchers("/api/v1/**").hasRole(Role.USER.name()) // USER 권한만 가능 .anyRequest().authenticated() // 로그인한 사용자들만 허용 .and() .logout() .logoutSuccessUrl("/") // 로그아웃 성공 시 / 주소로 이동 .and() .oauth2Login() // oauth2 로그인 기능에 대한 여러설정의 진입점 .userInfoEndpoint() // 성공 이후 사용자 정보를 가져올때 설정 담당 .userService(customOAuth2UserService); } }
- antMatchers
- 권한 관리 대상을 지정하는 옵션
- URL, HTTP 메소드별로 관리 가능
- anyRequest
- 설정된 값들 이외 나머지 URL
- authenticated()
- 모두 인증된(로그인한) 사용자들에게만 허용
- .userService(customOAuth2UserService)
- 소셜 로그인 성공시 후속 조치를 할 UserService인터페이스의 구현체 등록
- 소셜서비스들에서 사용자 정보를 가져온 상태에서 추가로 진행하고자 하는 기능 명시 가능
- CustomOAuth2UserService 클래스 생성 - 로그인 후 가져온 사용자의 정보를 기반으로 기능 지원
String registrationId = userRequest.getClientRegistration().getRegistrationId(); String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() .getUserInfoEndpoint().getUserNameAttributeName(); OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes()); User user = saveOrUpdate(attributes); httpSession.setAttribute("user", new SessionUser(user));
- registrationId
- 현재 로그인 진행 중인 서비스를 구분 ex) google, naver ...
- userNameAttributeName
- OAuth2 로그인 진행 시 키가 되는 필드값, Primarty Key와 같은 의미
- 구글은 기본적인 코드를 지원"sub" , naver, kakao등은 기본지원 x
- OAuthAttributes
- OAuth2UserService를 통해 가져온 OAuth2User의 attribute를 담은 클래스
- SessionUser
- 세션에 사용자 정보를 저장하기 위한 Dto 클래스
- Dto(Data Transfer Object) : 클라이언트와 서버의 서비스 계층 사이에서 교환되는 데이터를 담는 그릇
- OAuthAttributes 클래스 생성 - Dto
public User toEntity(){ return User.builder() .name(name) .email(email) .picture(picture) .role(Role.GUEST) .build(); }
- toEntity()
- User 엔티티를 생성
- OAuthAttributes에서 엔티티를 처음 생성하는 시점은 처음 가입할 때
- 가입할때의 기본권한은 GUEST
SessionUser 클래스 생성
@Getter public class SessionUser implements Serializable { private String name; private String email; private String picture; public SessionUser(User user){ this.name = user.getName(); this.email = user.getEmail(); this.picture = user.getPicture(); } }
- Serializable (직렬화) : 객체는 바이트형이 아니라서 스트림을 통해 파일에 저장하거나 네트워크로 전송할 수 없어서 객체를 스트림을 통해 입출력하기위해서 바이트 배열로 변환하는 것
- 인증된 사용자 정보만 필요하기 때문에 name, email ,picture만 필드로 선언
- 화면에 로그인 버튼 추가
{{#userName}} Logged in as : <span id="user">{{userName}}</span> <a href="/logout" class="btn btn-info active" role = "button">Logout</a> {{/userName}} {{^userName}} <a href="/oauth2/authorization/google" class="btn btn-success active" role="button">Google Login</a> {{/userName}}
- {{#userName}}
- 머스테치는 if문 제공 x , true/false 여부만 판단 => 최종값을 넘겨줘야한다.
- userName이 있다면 userName을 노출시키도록 구성
- a href = "/logout"
- spring security에서 기본적으로 제공하는 logout URL => Controller를 따로 만들어줄 필요없음
- {{^userName}}
- userName이 없다면 로그인 버튼을 노출시키도록 구성
- 머스테치는 해당 값이 존재하지 않는 경우 ^ 사용
- a href = "/oauth2/authorization/google"
- spring security에서 기본적으로 제공하는 login URL => Controller를 따로 만들어줄 필요없음
- IndexController에서 userName을 model에 저장 : index.mustache에서 userName을 사용
@GetMapping("/") public String index(Model model){ model.addAttribute("posts",postsService.findAllDesc()); SessionUser user = (SessionUser) httpSession.getAttribute("user"); if(user != null){ model.addAttribute("userName", user.getName()); } return "index"; }
- (SessionUser) httpSession.getAttribute("user")
- CustomeOAuth2UserService에서 로그인 성공시 세션에 SessionUser를 저장하도록 구성
=> 로그인 성공시 httpSession.getAttribute("user")에서 값을 가져올 수 있다.- if(user!=null)
- 세션에 저장된 값이 있을 때만 model에 userName으로 등록
- 세션에 저장된 값이 없으면 로그인 버튼만 보이게된다.