Spring Security: Form Login & OAuth2 Login (1)

Sora Yoo·2023년 1월 27일

spring

목록 보기
2/2
post-thumbnail

Spring 웹 Form Login과 OAuth2 Login을 위한 Spring Security 구현하기

  • Form login: ID, password를 통한 로그인
  • OAuth2 login: 구글, 네이버, 카카오톡 등의 계정을 통한 소셜 로그인

본 포스팅에서는 Form Login과 OAuth2 Login을 위한 Spring Security의 개념을 설명하고 Form Login Spring Security에 대한 설정을 한다.
OAuth2 로그인 설정은 2편(https://velog.io/@sorayoo/Spring-Security-Form-Login-OAuth2-Login-2)에서 계속된다.


개요

아래는 Spring Security가 작동하는 플로우이다.

  1. 로그인 시도
  2. Security Config에 등록한 로그인 함수 동작
    • Form Login: formLogin()
    • OAuth2 Login: oauth2Login() 동작
  3. 구현한 service에서 존재하는 유저인지 확인하는 함수가 자동으로 실행
    • Form Login: UserDetailService를 구현한 PrincipalDetailServiceloadUserByUsername() 동작
    • OAuth2 Login: DefaultOAuth2UserService를 상속한 PrincipalOauth2UserServiceloadUser() 동작 (가입하지 않는 회원은 자동으로 회원가입 후 로그인)
  4. 3번의 각각의 함수가 멤버 정보와 Authentication을 담은 PrincipalDetails를 리턴함
    • PrincipalDetailsUserDetailsOAuth2User를 implementation한 오브젝트
    • Form Login, OAuth2 Login에서 리턴되는 멤버 정보를 PrincipalDetails 바구니에 담아 동일한 오브젝트 타입으로 후처리 할 수 있다.
  5. 요청 받은 controller에서 @AuthenticationPrincipal 어노테이션이 활성화 되어 멤버 정보에 접근할 수 있다.

Spring Security 설정

SecurityConfig.java

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public BCryptPasswordEncoder encodePassword() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.authorizeRequests()
            .antMatchers("/user/**").authenticated()
            .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
            .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
            .anyRequest().permitAll()
    }
    
}

@EnableWebSecurity

  • 이 어노테이션을 추가하면 filter chain에 security filter가 추가됨

BcryptPasswordEncoder

  • password를 암호화해준다.
  • Bean으로 등록하여 사용한다.
  • Spring Security를 사용하는데 비밀번호 암호화를 하지 않으면 저장되지 않는다.

http.antMatchers("/api/**").authenticated()

  • antMatchers에 authentication이 필요한 url 규칙을 명시해준다.

http.antMatchers("/api/**").access("")

  • 유저에 권한이 있는 경우 url 접근에 대한 권한 설정을 할 수 있다.
    (본 예제에서는 권한을 사용하지 않는다.)

http.anyRequest().permitAll()

  • 위에서 설정되지 않은 이외의 url에 대해서는 모든 유저의 접근을 허용한다.


Form Login 설정

스프링 웹의 form login을 사용하기 위한 설정을 아래와 같이 추가해준다.
SecurityConfig.java

@Configuration
@EnableWebSecurity // filter chain에 security filter가 추가됨
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    ...
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.
        	...
            
            .and()
            .formLogin()
            .loginPage("/loginForm")
            .loginProcessingUrl("/login")
            .defaultSuccessUrl("/");
    }
    
}

.formLogin()

  • 스프링 웹의 form login 기능을 활성화
  • 설정 후 http://localhost:8080/login 으로 가면 스프링 웹 자체 로그인 폼이 출력됨

.loginPage("/loginForm")

  • 스프링 웹 자체 로그인 폼을 사용하면 생략한다.
  • 로그인이 필요할 때 보낼 주소를 명시
  • 자체 로그인 폼을 사용할 때 설정에 추가한다.
    * src/main/resources/templates/loginForm.html에 로그인 폼을 작성
    • controller에서 GetMapping 추가
        @GetMapping("/loginForm")
        public String loginForm() {
            return "loginForm";
        }
      	```      
    • 설정 후 http://localhost:8080/loginForm 으로 가면 custom login form을 볼 수 있다.

.loginProcessingUrl("/login")

  • 로그인을 실행할 url을 명시
    * loginForm.html
    	<form action="/login" method="POST">
    	...
    	</form>
  • 로그인 폼이 /login으로 가면 Spring Security가 낚아채서 대신 로그인을 진행
  • controller에서 따로 mapping 하지 않아도 된다.

.defaultSucessUrl("/")

  • 로그인 성공 이후 redirect할 주소를 명시


PrincipalDetails.class 생성

로그인이 완료되면 SecurityContextHolder라는 네임으로 로그인 세션이 저장된다.

저장되는 SecuritySession의 오브젝트 타입은 Authentication이고, 그 안에 들어있는 유저 정보는 UserDetails(Form Login) 혹은 OAuth2User(OAuth2 Login) 타입 객체이다.

우선, UserDetails interface의 구현체인 PrincipalDetail 클래스를 만든다.

PrincipalDetails.java

public class PrincipalDetails implements UserDetails {
    
    private Member member;

    public PrincipalDetails(Member member) {
        this.member = member;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return member.getUsername();
            }
        });
        return collect;
    }

    @Override
    public String getPassword() {
        return member.getPassword();
    }

    @Override
    public String getUsername() {
        return member.getUsername();
    }
    
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }

}
  • 인터페이스의 메소드를 모두 override 해준다.
  • getAuthorities() 에서 세션에 담고싶은 유저 정보를 리턴한다.


PrincipalDetailsService 생성

SecurityConfig.class에서 설정(.loginProcessingUrl("/login"))한 주소로 로그인 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어있는 loadUserByUsername() 함수가 실행된다.

아래와 같이 UserDetailsService를 implements하는 PrincipalDetailsService를 작성해준다.

PrincipalDetailsService.java

@Service
public class PrincipalDetailsService implements UserDetailsService {

    @Autowired
    private MemberRepository memberRepository;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Member memberEntity = memberRepository.findByUsername(username);
        if (memberEntity != null) {
            return new PrincipalDetails(memberEntity);
        }
        return null;
    }
}
  • form login을 한 경우, form에서 넘어오는 username을 통해 유저를 찾고 PrincipalDetails의 모양으로 유저 정보를 리턴해준다.
  • 리턴된 PrincipalDetailsAuthentication에 담겨 Securiy Session으로 저장된다.


Test

Spring 웹에서 Form Login시 session이 Authentication 객체로 저장되고, 그 안에 PrincipalDetails 객체가 멤버 정보를 담고 있다.
controller에서 Authentication 혹은 @AuthenticationPrincipal을 이용하여 세션에 있는 유저 정보에 접근할 수 있다.

Authentication을 요구하는 페이지를 만들어 로그인 유저의 세션이 저장되었는지 테스트 해볼 수 있다.

IndexController.java

@Controller
public class IndexController {
	...
	@GetMapping("/test/login")
    public @ResponseBody String testLogin(
        Authentication authentication,
        @AuthenticationPrincipal PrincipalDetails principalDetails
        ) {
        if (authentication != null) {
	        System.out.println("authentication: "+authentication);
            PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal();
            System.out.println("principalDetails: "+principal.getMember().getUsername());
            System.out.println("userdetails: "+principalDetails.getMember());
        } else {
            System.out.println("no auth");
        }
        return "authentication";
    }
    ...
}

result:

authentication: UsernamePasswordAuthenticationToken [...]
principalDetails: ***
userdetails: com.example.***.Domain.Member@2ab7202c

OAuth2 로그인은 다음 포스팅에 연결하여 진행한다.
https://velog.io/@sorayoo/Spring-Security-Form-Login-OAuth2-Login-2

0개의 댓글