스프링부트 여행 웹사이트 만들기(2)

노건우·2023년 11월 2일
0

스프링 프로젝트

목록 보기
2/6

스프링 시큐리티(Spring Security)

스프링 시큐리티는 스프링 프레임워크에서 제공하는 보안 프레임워크.

📜WebSecurityConfig.java

스프링 시큐리트 설정 클래스

/* 
@Configuration
스프링의 설정 정보를 포함하고 있음을 나타냄
@Bean
빈 객체를 생성, 빈 객체는 스프링 컨테이너에 등록되어 다른 빈 객체에서 참조할 수 있음.

@EnableWebSecurity
스프링 시큐리티의 설정 정보를 포함하는 클래스임을 나타냄.
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {

   // 소셜(OAuth2) 로그인 성공 후, 처리 되는 로직을 수행하는 서비스단
   private final CustomOAuthUserService oAuthUserService;

   // 회원의 비밀번호를 DB에 저장하기 전, 해쉬 알고리즘으로 암호화를 시킬 때, 사용되는 객체
   @Bean
   public PasswordEncoder passwordEncoder(){
      return new BCryptPasswordEncoder();
   }

   /* SecurityFilterChain는 스프링 시큐리티에서 사용되는 인터페이스로,
      HttpServletRequest와 매칭되는 필터 체인을 정의한다.
      이 필터 체인은 FilterChainProxy를 구성하는 데 사용된다.
   */
   @Bean
   public SecurityFilterChain configure(HttpSecurity http) throws Exception{
      // HttpSecurity에 빌더 패턴으로 보안 옵션들을 설정한다.
      /* authorizeHttpRequests 유저의 권한에 따라 요청(request)에 대한 접근 가능 여부를 판별.
         .hasAuthority("USER") USER 권한을 가진 회원만 접근 허용
         .anonymous() 비회원인 경우만 접근 허용
         .permitAll() 회원 여부에 상관 없이 접근 허용
      */
      http.authorizeHttpRequests((req) -> req
            .requestMatchers("/member/mypage**", "planner/**").hasAuthority("USER")
            .requestMatchers("/member/join**", "/member/login").anonymous()
            .anyRequest().permitAll())
         // formLogin 로그인 시
         .formLogin(formLogin -> formLogin
            .loginPage("/member/login")
            .defaultSuccessUrl("/"))
         // logout 로그 아웃 시
         .logout((logout) -> logout
            .logoutRequestMatcher(new AntPathRequestMatcher("/member/logout"))
            .logoutSuccessUrl("/")
            .invalidateHttpSession(true))
         // oauth2Login 소셜 로그인 시
         .oauth2Login((oauth) -> oauth
            .loginPage("/member/login")
            .userInfoEndpoint((endpoint)->endpoint
               .userService(oAuthUserService)));
      return http.build();
   }
}

로그인

interface UserDetailsService - 로그인

📜MemberService.java

스프링 Security에 formLogin를 설정할 경우, Controller단에서 로그인 인증을 위한 RequestMapping을 따로 지정하지 않아도, loadUserByUsername(String username) 메소드로 유저 이름을 입력받아 해당 유저의 인증 정보(UserDetails)를 반환합니다.

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {
   @Override
   public UserDetails loadUserByUsername(String username) 
		   throws UsernameNotFoundException {
      Member member = memberRepository.findByUsername(username);
      List<GrantedAuthority> authorities = new ArrayList<>();
      authorities.add(new SimpleGrantedAuthority("USER"));
      return new MemberDetails(member, authorities);
   }
}

📜MemberDetails.java

UserDetails 클래스는 유저의 세부 정보와 권한 정보를 포함하는 유저 세부 정보 클래스.

public class MemberDetails implements UserDetails, OAuth2User {
   // 회원 객체
   private Member member;
   // 권한
   private final Set<GrantedAuthority> authorities;
   // OAuth의 속성
   private Map<String, Object> attributes;

   public MemberDetails(Member member, Collection<? extends GrantedAuthority> authorities){
      this.member = member;
      this.authorities = Set.copyOf(authorities);
   }

   public MemberDetails(Member member, Collection<? extends GrantedAuthority> authorities, Map<String, Object> attributes){
      this.member = member;
      this.authorities = Set.copyOf(authorities);
      this.attributes = attributes;
   }
}

카카오 로그인

소셜 로그인을 위한 설정

📜application.properties

# KaKao OAuth2 설정
spring.security.oauth2.client.registration.kakao.client-id={카카오 REST API 키}
spring.security.oauth2.client.registration.kakao.client-secret={카카오 로그인 Client Secret 키}
spring.security.oauth2.client.registration.kakao.redirect-uri={카카오 로그인 Redirect URI}
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post
spring.security.oauth2.client.registration.kakao.client-name=Kakao
spring.security.oauth2.client.registration.kakao.scope=profile_nickname,profile_image,account_email

# Kakao Provider 등록
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id

interface OAuth2UserService

📜CustomOAuthUserService.java

카카오 로그인 시 카카오

@Service
@RequiredArgsConstructor
public class CustomOAuthUserService extends DefaultOAuth2UserService{

   private final MemberRepository memberRepository;

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

      Map<String, Object> attributes = oAuth2User.getAttributes();
      String userId = String.valueOf(attributes.get("id"));

      Member member = memberRepository.findByUsername(userId);
      if(member == null){
         Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
         Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile");

         String nickname = String.valueOf(profile.get("nickname"));
         String email = String.valueOf(kakaoAccount.get("email"));

         member = new Member();
         member.setCreateDate(LocalDateTime.now());
         member.setMemberType(MemberType.Kakao);
         member.setUsername(userId);
         member.setRealname(nickname);
         member.setEmail(email);
         memberRepository.save(member);
      }

      List<GrantedAuthority> authorities = new ArrayList<>();
      authorities.add(new SimpleGrantedAuthority("USER"));

      return new MemberDetails(member, authorities, attributes);
   }
   
}

📜oAuth2User.getAttributes() 예시

{
   id=1000000000, 
   connected_at=2023-10-30T02:00:00Z, 
   properties={
      nickname=이름, 
      profile_image=http://k.kakaocdn.net/dn/WTAaw/btssfTs84YC/.../img_640x640.jpg, 
      thumbnail_image=http://k.kakaocdn.net/dn/WTAaw/btssfTs84YC/.../img_110x110.jpg
      }, 
   kakao_account={
      profile_nickname_needs_agreement=false, 
      profile_image_needs_agreement=false, 
      profile={
         nickname=이름, 
         thumbnail_image_url=http://k.kakaocdn.net/dn/WTAaw/btssfTs84YC/.../img_110x110.jpg, 
         profile_image_url=http://k.kakaocdn.net/dn/WTAaw/btssfTs84YC/.../img_640x640.jpg, 
         is_default_image=false
         }, 
      has_email=true, 
      email_needs_agreement=false, 
      is_email_valid=true, 
      is_email_verified=true, 
      email=user@email.com
   }
}

회원가입 페이지다.

로그인 화면. 카카오 로그인도 구현했기에 아래에 뜨게 해놓았다.

profile
초보 개발자 이야기

0개의 댓글