
Spring 웹 Form Login과 OAuth2 Login을 위한 Spring Security 구현하기
본 포스팅에서는 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가 작동하는 플로우이다.

formLogin()oauth2Login() 동작UserDetailService를 구현한 PrincipalDetailService의 loadUserByUsername() 동작DefaultOAuth2UserService를 상속한 PrincipalOauth2UserService의 loadUser() 동작 (가입하지 않는 회원은 자동으로 회원가입 후 로그인)Authentication을 담은 PrincipalDetails를 리턴함PrincipalDetails는 UserDetails와 OAuth2User를 implementation한 오브젝트PrincipalDetails 바구니에 담아 동일한 오브젝트 타입으로 후처리 할 수 있다.@AuthenticationPrincipal 어노테이션이 활성화 되어 멤버 정보에 접근할 수 있다.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
BcryptPasswordEncoder
http.antMatchers("/api/**").authenticated()
http.antMatchers("/api/**").access("")
http.anyRequest().permitAll()
스프링 웹의 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()
.loginPage("/loginForm")
src/main/resources/templates/loginForm.html에 로그인 폼을 작성 @GetMapping("/loginForm")
public String loginForm() {
return "loginForm";
}
``` .loginProcessingUrl("/login")
<form action="/login" method="POST">
...
</form>/login으로 가면 Spring Security가 낚아채서 대신 로그인을 진행.defaultSucessUrl("/")
로그인이 완료되면 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;
}
}
getAuthorities() 에서 세션에 담고싶은 유저 정보를 리턴한다.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;
}
}
PrincipalDetails의 모양으로 유저 정보를 리턴해준다.PrincipalDetails가 Authentication에 담겨 Securiy Session으로 저장된다.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