본문 바로가기

스터디 기록/SpringBoot 심화 스터디

SpringSecurity 세팅

 UserDetails UserDetailsSerive

시큐리티 세팅법을 간단하게 알아보자!

먼저 UserDetails와 UserDetailsSerive 인터페이스를 각각 구현해야한다.

@Getter
@RequiredArgsConstructor
public class LoginUser implements UserDetails {

    private final User user;

    @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 Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(() -> "ROLE_" + user.getRole());
        return authorities;
    }

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

    @Override
    public String getUsername() {
        return user.getUsername();
    }
}
@Service
@AllArgsConstructor
public class LoginService implements UserDetailsService {

    private UserRepository userRepository;

    // 시큐리티로 로그인이 될 때, 시큐리티가 loadUserByUsername() 실행해서 username 을 체크할 수 있음.
    // 없으면 오류고
    // 있으면 정상적으로 시큐리티 컨텍스트 내부 세션에 로그인된 세션이 만들어진다.
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userPS = userRepository.findByUsername(username).orElseThrow(
                () -> new InternalAuthenticationServiceException("인증 실패")); // 나중에 테스트할 때 설명
        return new LoginUser(userPS); // 찾았으면, 이 객체가 세션에 만들어짐.
    }
}

 

 

SecurityConfig를 조작해보자

개발자가 원하는대로 SecurityConfig를 설정해줄 수 있다. 그 중 몇가지 설정을을 소개하겠다.

나중에 Jwt를 사용하기 위해 Jwt 필터도 등록해줬는데, 이건 다음 강의 정리때 Jwt개념과 함께 정리하겠다.

 

- BCryptPasswordEncoder

유저의 비밀번호를 암호화해 저장할 때 사용할 객체이다.

@Bean // IoC 컨테이너에 BCryptPasswordEncoder() 객체가 등록됨
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

 

- 기본 HTML로그인 페이지, 브라우저 팝업창 사용 안함

보통 프론트엔드와 함께 협업할 때 Security의 기본 로그인페이지는 사용하지 않는다. 마찬가지로 httpBasic도 사용하지 않으니 둘 다 비허용 처리를 해준다.

이때 SecurityFilterChain안에 넣어줘야한다.

http.formLogin(f->f.disable());

http.httpBasic(hb->hb.disable());

 

- 인증 실패, 인가 실패 Exception 가로채기

인증 또는 인가에 실패하면 예외가 터진다. 이 예외를 예쁘게 던지기 위해 미리 만들어준 커스텀 예외로 잡아준다. 바로 이전 글에 커스텀 예외에 대한 포스팅 글이 있다.

// Exception 가로채기
// 인증 실패
http.exceptionHandling(e -> e.authenticationEntryPoint((request, response, authException) -> {
    CustomResponseUtil.fail(response, "로그인을 진행해 주세요.", HttpStatus.UNAUTHORIZED);
}));

// 권한 실패
http.exceptionHandling(e -> e.accessDeniedHandler((request, response, accessDeniedException) -> {
    CustomResponseUtil.fail(response, "권한이 없습니다.", HttpStatus.FORBIDDEN);
}));

 

- 권한이 있어야 접근할 수 있는 페이지나 api 설정하기

특정 페이지나 api에 접근할 땐 인증이나 인가를 완료해야 접근할 수 있도록 만들고 싶을 수 있다.

이것도 SecurityConfig에서 설정해줄 수 있다.

http.authorizeHttpRequests(c -> 
        c.requestMatchers("/api/s/**").authenticated()
                    .requestMatchers("/api/admin/**").hasRole(""+ UserEnum.ADMIN) 
                    .anyRequest().permitAll()
);

 

이렇게 설정해준 경우 /api/s/로 시작하는 곳에 접근하기 위해 인증, 즉 로그인을 한 상태여야하고, /api/admin/으로 시작하는 곳엔 ADMIN인가, 즉 특정 권한을 가진 ADMIN만 접근할 수 있다.

나머지엔 permitAll()로 모두가 접근할 수 있게 해준다.