Spring Security & OAuth 2.0

iseon_u·2023년 3월 22일
0

Book

목록 보기
9/16
post-thumbnail

CH05 - Spring Security & OAuth 2.0


Spring Security

  • 인증과 권한 부여 기능을 가진 프레임워크
  • 인터셉터, 필터 기반으로 보안 기능을 구현하는 것보다 권장

Google 서비스 등록

  1. 새 프로젝트 생성

  2. API 및 서비스에서 OAuth 클라이언트 ID로 사용자 인증 정보 만들기

  3. 동의 화면 구성

    • 애플리케이션 이름
      • 사용자에게 노출되는 이름
    • 지원 이메일
      • 주로 서비스의 help 이메일 사용
    • Google API 의 범위
      • 기본 값으로 email/profile/openid
  4. 승인된 리디렉션 URI

    {도메인}/login/oauth2/code/{소셜서비스코드}

    • 스프링 부트 시큐리티에서는 기본 지원 리다이렉트 URL
    • 사용자가 별도로 Controller 를 만들 필요 없다
  5. 클라이언트 ID와 클라이언트 보안 비밀번호

    • 제공받은 코드들을 프로젝트에서 사용

    application-oauth.properties

    spring.security.oauth2.client.registration.google.client-id=클라이언트 ID
    spring.security.oauth2.client.registration.google.client-secret=클라이언트 보안 비밀
    spring.security.oauth2.client.registration.google.scope=profile,email
    • scope=profile, email
      • 기본값이 openid, profile, email 이기 때문에 보통 scope 를 별도로 등록하지 않는다
      • openid scope 가 있으면 Open Id Provider 로 인식하기 때문에 다른 서비스 등록시 OAuth2Service 따로 구현
      • OAuth2Service 하나로 구현하기 위해 설정
    • application-xxx.properties
      • xxx 라는 profile 이 생성되어 profile=xxx 로 설정을 가져올 수 있다

    Google 로그인 연동

    User.java

    @Getter
    @NoArgsConstructor
    @Entity
    public class User extends BaseTimeEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @Column(nullable = false)
        private String name;
    
        @Column(nullable = false)
        private String email;
    
        @Column
        private String picture;
    
        @Enumerated(EnumType.STRING)
        @Column(nullable = false)
        private Role role;
    
        @Builder
        public User(String name, String email, String picture, Role role) {
            this.name = name;
            this.email = email;
            this.picture = picture;
            this.role = role;
        }
    
        public User update(String name, String picture) {
            this.name = name;
            this.picture = picture;
            return this;
        }
    
        public String getRoleKey() {
            return this.role.getKey();
        }
    }
    • @Enumerated(EnumType.STRING)
      • JPA 로 데이터베이스로 저장할 때 기본값은 int
      • 문자열로 저장해야 DB로 확인했을 때 무슨 코드를 의미하는지 알 수 있다

    Role.java

    @Getter
    @RequiredArgsConstructor
    public enum Role {
    
        GUEST("ROLE_GUEST", "손님"),
        USER("ROLE_USER", "일반_사용자");
    
        private final String key;
        private final String title;
    
    }
    • 시큐리티 권한 코드 앞에는 ROLE_ 이 붙는다

    config/auth/SecurityConfig.java

    @RequiredArgsConstructor
    @EnableWebSecurity // 1
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        private final CustomOAuth2UserService customOAuth2UserService;
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .csrf().disable()
                    .headers().frameOptions().disable() // 2
                    .and()
                        .authorizeRequests() // 3
                        .antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile").permitAll()
                        .antMatchers("/api/v1/**").hasRole(Role.USER.name()) // 4
                        .anyRequest().authenticated() // 5
                    .and()
                        .logout()
                            .logoutSuccessUrl("/") // 6
                    .and()
                        .oauth2Login() // 7
                            .userInfoEndpoint() // 8
                                .userService(customOAuth2UserService); // 9
        }
    }
    1. @EnableWebSecurity
      • Spring Security 설정 활성화
    2. .csrf().disable().headers().frameOptions().disable()
      • h2-console 화면을 사용하기 위해 옵션 비활성화
    3. authorizeRequests
      • URL 별 권환 관리를 설정하는 옵션의 시작점
    4. antMatchers
      • 권한 관리 대상을 지정하는 옵션
      • URL, HTTP 메소드별로 관리 가능
        • “/” 등 지정된 URL 은 permitAll() 옵션을 통해 전체 열람 권한
        • “/api/v1/**” 주소를 가진 API는 USER 권한을 가진 사람만 가능
    5. anyRequest
      • 설정된 값들 이외 나머지 URL
      • authenticated() 옵션을 추가하여 인증된 사용자 (로그인 사용자) 에게만 허용
    6. logout().logoutSuccessUrl(”/”)
      • 로그아웃 기능에 대한 여러 설정의 진입점
      • 로그아웃 성공시 설정 주소로 이동
    7. oauth2Login
      • OAuth 2 로그인 기능에 대한 여러 설정의 진입점
    8. userInfoEndpoint
      • OAuth 2 로그인 성공 이후 사용자 정보를 가져오는 설정 담당
    9. userService
      • 소셜 로그인 성공시 후속 조치를 진행할 UserService 인터페이스의 구현체를 등록
      • 리소스 서버에서 사용자 정보를 가져온 상태에서 추가로 진행하고자 하는 기능을 명시
@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
    private final UserRepository userRepository;
    private final HttpSession httpSession;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        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));

        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey());
    }

    private User saveOrUpdate(OAuthAttributes attributes) {
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPicture()))
                .orElse(attributes.toEntity());

        return userRepository.save(user);
    }
}
profile
🧑🏻‍💻 Hello World!

0개의 댓글