Home > Today I Learned > 2026_05_21~2026_05_22 > 2026-05-21 일지

2026-05-21 일지
spring security

Spring Security 구현


인증된 유저를 보관하는 UserDetails

@Getter
@RequiredArgsConstructor
public class MemberDetails implements UserDetails {

    private final String username;
    private final String password;
    private final String role;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //GrantedAuthority의 하위 타입인 경우에만 올 수가 있다.
        return List.of(new SimpleGrantedAuthority(this.role));
    }

}

폼기반 로그인을 위한 MemberDetails는 폼 로그인 인증 과정에서 현재 로그인 한 사용자의 정보를 담는 객체의 역할을 수행한다. SpringSecurity는 내부적으로 인증이 완료되면 UserDetails 타입 객체를 통해 사용자 정보를 관련하는데 내가 만들어준 Member Entity를 Spring Security에서 사용하려면 UserDetails를 구현한 클래스를 만들어야 한다.

getAuthorities에 관한 설명


이 메서드는 현재 사용자의 권한목록을 반환하게 된다.
Spring Security는 사용자 인증 성공시

  1. userDetails 조회
  2. getAuthorities() 호출
  3. 권한 확인
  4. URL 접근 허용 여부 결정

이렇게 과정을 거치게 되는데 여기서 GrantAuthority는 권한을 표현하는 인터페이스로
ROLE_USER, ROLE_ADMIN 이런 식으로 되어있다. 권한이 여러 개일 수도 있기 때문에 Collection 형태로 반환을 받아야 한다.

UserDetailSerive 구현

@Service
@RequiredArgsConstructor
public class MemberService implements UserDetailsService {

    private final MemberRepository repository;
    private final PasswordEncoder passwordEncoder;

    @Transactional
    public void save(SignUpRequest request) {

        Member member = Member.builder()
                .username(request.getUsername())
                .password(passwordEncoder.encode(request.getPassword()))
                .email(request.getEmail())
                .build();

        repository.save(member);

    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Optional<Member> memberOptional = repository.findByUsername(username);

        Member member = memberOptional.orElseThrow(
                () -> new UsernameNotFoundException("해당 회원을 찾을 수 없습니다.")
        );

        return new MemberDetails(
                member.getUsername(),
                member.getPassword(),
                member.getEmail()
        );

    }

}

UserDetail에 들어갈 사용자 정보를 담기 위해 사용자 정보를 조회하는 역할을 UserDetailsService에서 시행한다. 이 UserDetailService는 로그인시 loadUserByUsername이라는 메서드로 호출을 하게 되는데 이를 구현하기 위한 재정의가 되어있어야 한다.

여기서

.password(passwordEncoder.encode(request.getPassword()))

암호화를 한 상태로 패스워드를 보관을 하게 된다. 각종 보안문제에 대한 기본적인 정책으로 시행한다. 그리고 이 passwordEncoder에서 시행하는 것은 BCrypt 방식의 단방향 암호화를 제공한다.

Spring Security 보안 규칙 설정


로그인, 로그아웃, URL별 접근 권한을 설정해야 한다. SecurityFilterChain 클래스를 통해 관리를 할 수가 있다.


@Configuration
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final PasswordEncoder passwordEncoder;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http){
        return http
                .cors(cors -> cors.disable()) // CORS 종료
                .csrf(csrf -> csrf.disable()) // CSRF 종료
                //폼 로그인 관련 설정
                .formLogin(Customizer.withDefaults()
//
//                        form -> form.usernameParameter("loginId")
//                                .passwordParameter("loginPwd")
//                                //특정페이지를 로그인 페이지로 사용하기 위해서 사용
//                                .loginPage("/login-page")
//                                .loginProcessingUrl("/login")
//                                //로그인을 성공했을 때 다음 url로 이동하고 싶을때 영구적으로 이동하고 싶으면 default
//                                //페이지를 이동시킨다.
//                                //로그인이 끝나고 나서 비동기 처리를 하려 할때는 페이지만 다시 보여주고 싶을때 successForwardUrl
//                                //화면만 바뀌게 된다.
//                                .successForwardUr l("/")
//                                .defaultSuccessUrl("/")
//                                .failureUrl("/login-page?error=true")
//                                .failureForwardUrl("/login-page?error=true")
                )
                .logout( Customizer.withDefaults()
//                        logoutConfig ->logoutConfig
//                        .logoutUrl("/logout")
//                        .invalidateHttpSession(true)
//                        .deleteCookies("JSESSIONID")
//                        .clearAuthentication(true)
                )
                .authorizeHttpRequests(
                        auth -> auth.requestMatchers("/login-page")
                                // 인증이 안되어있는 사람만 접근 가능, 로그인페이지에 열어둔다
                                .anonymous()
                                .requestMatchers("/sign-up")
                                // 인증만 되어있으면 접근을 허용해주겠다는 것, 무조건 승인하겠다는 것
                                    .permitAll()
                                .requestMatchers("/user/**")
                                    .hasAnyAuthority("USER","MANAGER","ADMIN")
                                .requestMatchers("/manager/**")
//                                    .hasAuthority("MANAGER")
                                        .hasAnyAuthority("MANAGER","ADMIN")
                                .requestMatchers("/admin/**")
                                    .hasAuthority("ADMIN")
                                .requestMatchers("/**")
                                    .authenticated()
                                .anyRequest()
                                    .authenticated()
                                .anyRequest()
                                    .denyAll()
//                                .hasAnyRole()
//                                //가변 인자
//                                .hasAnyAuthority()
//                                //ROLE_을 붙일 필요가 없다. 내부적으로 붙이게 된다. DB에서 관리하고 운영하기 편하도록
//                                .hasRole()
//                                //ROLE_을 붙일 필요가 없다. 내부적으로도 붙이지 않는다.
//                                .hasAuthority()
                )
                .build();
    }

  • formLogin
    로그인을 시도했을 때 성공한 경우, 실패한 경우때 redirection을 위한 설정

    formConfig.loginPage(“/login-page”) : 특정 페이지를 로그인 페이지로 쓰고 싶을때

    .loginProcessingUrl(“/login”) : 필터로 보내서 자동으로 로그인을 시켜줄 URL 매핑 가능. 로그인이 수행되는 비밀번호를 맞춰볼 URL 지정.

    .successForwardUrl(“/”) : 로그인 성공 후, 다른 페이지로 임시 이동시킬때, 로그인 후 비동기 처리를 해야 할 때, 로그인 URL은 유지되고 페이지만 다시 보여주고 싶을 때

    .defaultSuccessUrl(“/”) : 로그인이 성공했을 때 영구적으로 이동시키고 싶을 때 사용

    .failureUrl(“/login-page?error”) : 로그인 실패시, 로그인 페이지로 돌려주고, 동사에 쿼리 파라미터를 전달해서 표시하고 싶을 때 사용.

    .failureForwardUrl(“/login-page?error”) : 주소창은 내버려두고, 보여줄 화면을 어떻게 보여줄지에 대한 주소를 남길때

  • logout
    로그아웃을 했을 때 성공한 경우, 실패한 경우에서 redirection을 위한 설정

    .logoutUrl(“/logout”) : 로그아웃 URL

    .invalidateHttpSession(true) : http세션을 쓰고 있을 때, 로그아웃을 하게 되면 세션을 무효화 시킬지에 대한 여부.

    .deleteCookies(“JSESSIONID”) : 로그아웃을 하게 되면, 이 쿠키를 지우겠다.

    .clearAuthentication(true) : 쓰레드 로컬안에 들어 있는, SpringSecurityContextHolder안에 있는 인증 객체인 Authentication 비워줄 것이냐 = 진짜 인증정보를 날릴 것이냐

  • authorizeHttpRequest
    URL별 접근하는 사람의 Role에 따라 authorize 설정

    • .anonymous : 아직 로그인하지 않은 사용자만 접근 가능
    • .permitAll : 누구나 접근가능하도록 모두 접근 허용
    • hasAnyAuthority: 여러 개의 권한에 관해 접근하는 것이 권한을 설정

    401 : 로그인 되지 않았음
    403 : 로그인 되었지만, 접근 권한 없음
    404 : 해당 페이지 없음

OAuth2.0

gcp에서 OAuth2.0 가져오는법

웹 서버 애플리케이션용 OAuth 2.0 사용 Google for Developers

OAuth 2.0 액세스 토큰 가져오기 파트의 시나리오를 가져오면 좋을 것 같다.

kakao에서 OAuth2.0 가져오는 법

Kakao Developers 문서

구글 로그인은 스프링에서 공식적으로 지원하지만, 카카오, 네이버등 기타 로그인은 스프링에서 공식적으로 지원하지 않으므로, OAuth2.0 정보를 추가로 구현 하여야 한다.

구글은 registration에 client-id, client-secret, scope만 설정파일에 넣으면 되지만, 기타 로그인은 authorization-grant-type, client-authentication-method, redirect-uri을 추가로 넣어야 하고, 별도로 provider를 만들고 authorization-uri, user-name-attribute, token-uri, user-info-uri을 넣어 주어야 한다