본문 바로가기
Projects/SNS프로젝트

Spring Security의 로그인 절차

by 젊은오리 2022. 1. 16.
728x90

 

스프링 시큐리티는 커스터마이징이 가능한 인증 및 접근제어 프레임워크이다. 보안과 관련해서 체계적으로 많은 옵션을 제공하기 때문에 개발자 입장에서 일일이 보안에 관련된 로직을 작성하지 않아도 된다는 장점이 있다.

 

시큐리티가 form login을 어떻게 처리하는지에 대해서 프로젝트 내용과 함께 설명한다.

 

밑의 코드는 WebSecurityConfigurerAdapter를 상속받아 작성한 SecurityConfig 클래스(설정파일)이다. 

configure메서드에서는 csrf를 비활성화하고, antMatchers에 명시된 경로에 권한을 부여해서 해당 경로로 들어가게 되는 경우 로그인페이지로 이동하도록 했다. 시큐리티 설정파일에 .loginProcessingUrl("/auth/signin")라고 명시하면 /auth/signin/으로 POST요청이 들어올 경우에 UserDetailsService인터페이스의 구현체 PrincipalDetailService에서 로그인 프로세스를 처리한다.

  • UserDetailsService 인터페이스는 일반적으로 DB에서 유저 정보를 가져오는 역할을 한다.
  • UserDetailsService의 구현체를 따로 만들 경우에 패스워드를 알아서 시큐리티가 체크해 주기 때문에 신경 쓸 필요 X

 

@RequiredArgsConstructor
@EnableWebSecurity // 해당파일로 시큐리티를 활성화, 자동으로 csrf 보호기능 활성화
@Configuration //IoC
public class SecurityConfig extends WebSecurityConfigurerAdapter{
   
   private final OAuth2DetailsService oAuth2DetailsService;
   
   @Bean
   public BCryptPasswordEncoder encode() {
      return new BCryptPasswordEncoder();
   }
   
   @Override
   protected void configure(HttpSecurity http) throws Exception {
      
      //super삭제 - 기존 시큐리티가 가지고 있는 기능이 다 비활성화됨.
      http.csrf().disable();
      http.authorizeRequests()
         .antMatchers("/user/**","/image/**","/subscribe/**","/comment/**","/api/**").authenticated()
         .anyRequest().permitAll()
         .and()
      .formLogin()
         .loginPage("/auth/signin") //GET
         .loginProcessingUrl("/auth/signin") //POST -> 시큐리티가 로그인 프로세스 진행



UserDetailsService 인터페이스의 구현체 PrincipalDetailService를 보자.UserDetailsService의 loadUserByUsername메서드를 override하게 되는데, 여기서 UserRepository에서 해당 username을 가진 user가 있는지 체크 후에 user를 리턴을 해야하는데,

 

가만보니 리턴타입이 UserDetails이다.

 

따라서 이를 맞춰주기 위해서 UserDetails 인터페이스를 구현한 PrincipalDetail를 만들어서 return new PrincipalDetail(userEntity) 형태로 감싸주었다. 

  • UserDetails 인터페이스는 사용자의 정보를 담고있다.

 

@RequiredArgsConstructor
@Service
public class PrincipalDetailService implements UserDetailsService{
   private final UserRepository userRepository;
   
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      User userEntity = userRepository.findByUsername(username);
      if(userEntity==null)
         return null;
      else
         return new PrincipalDetail(userEntity);
   }
}



 

아래는 UserDetails 인터페이스를 구현한 PrincipalDetail이다.

user에 대한 세션을 만들어주는 클래스라서 여러가지 detail적인 것을 override하고, 그 중에서 Collection부분은 권한에 관련된 건데 권한이 꼭 1개만이 아니기 때문에 직접 Collection<GrantedAuthority>형 객체를 만들어서 하나만 추가한 다음 리턴하도록 했다.

  • isAccountNonExpired()
  • isCredentialsNonExpired()
  • isEnabled()
  • isAccountNonLocked()

위 4개의 메서드는 지금 프로젝트에 크게 중요치 않으므로 true로 리턴했고, 생성자에서 user를 주입받아 PrincipalDetail객체에 User정보를 담을 수 있게 했다. UserDetails를 상속받은 principalDetails는 세션에 저장된다.

 

@Data
public class PrincipalDetail implements UserDetails,OAuth2User{

   private User user;
   private Map<String,Object> attributes;
   
   public PrincipalDetail(User user) {
      this.user = user;
   }

   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
      Collection<GrantedAuthority> collectors = new ArrayList<>();
      collectors.add(()->{return user.getRole();});
      return collectors;
   }

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

   @Override
   public String getUsername() {
      return user.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
   public String getName() {
      return null;
   }
}

 

 

Views에서 user session을 사용하려면  아래와 같이 @AuthenticationPrincipal를 사용해서 PrincipalDetail객체를 생성하여 Authentication에 접근하면 편리하게 사용이 가능하다!!

@GetMapping("/user/{id}/update")
public String update(@PathVariable int id, @AuthenticationPrincipal PrincipalDetail principalDetail) {
   log.info("=======================");
   log.info(principalDetail.getUser().toString());
   return "user/update";
}

 

 

참고

Controller상에서 model에 PrincipalDetail객체를 담아서 넘기지 않고,, view에서 인증된 사용자에 접근을 하려면 아래와 같이 taglib과 tag를 작성해서 사용하면 principal이라는 변수만으로 사용자를 조회할 수 있다. 

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>

<sec:authorize access="isAuthenticated()">
   <sec:authentication property="principal" var="principal"/>
</sec:authorize>

 

 

 

728x90

댓글