민팽로그

spring security 본문

🍃spring boot/spring security

spring security

민팽 2021. 9. 13. 01:20

spring security? 

스프링 서버에 필요한 인증 및 인가를 위한 다양한 기능을 제공하는 프레임워크로 로그인 기능을 구현할 수 있음

 

- gradle 사용 시 다음을 추가하여 사용할 수 있음

 implementation 'org.springframework.boot:spring-boot-starter-security'

 

- 아래 예시 코드와 같은 형식으로 스프링 시큐리티를 활성화 할 수 있음

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@EnableGlobalMethodSecurity(securedEnabled = true) //권한 부여가 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    //password 암호화를 위한 bean 등록
    @Bean
    public BCryptPasswordEncoder encodePassword() {
        return new BCryptPasswordEncoder();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.headers().frameOptions().disable();

        http.authorizeRequests()
                // image 폴더를 login 없이 허용
                .antMatchers("/images/**").permitAll()
                // css 폴더를 login 없이 허용
                .antMatchers("/css/**").permitAll()
                .antMatchers("/user/**", "/h2-console/**").permitAll()
                // 그 외 모든 요청은 인증과정 필요
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //로그인 페이지 설정(뷰 템플릿 엔진은 timeleaf 사용했음)
                //    /user/login 컨트롤러가 login html문서를 내려줌
                .loginPage("/user/login")
                .loginProcessingUrl("/user/login")
                .defaultSuccessUrl("/")
                .permitAll()
                .and()
                .logout()
                //로그아웃 컨트롤러는 따로 구현하지 않아도 됨(이미 구현되어 있음)
                .logoutUrl("/user/logout")
                .permitAll()
                .and()
                .exceptionHandling()
                .accessDeniedPage("/user/forbidden"); //접근 거부 페이지 설정
    }
    
    //AuthenticationManager 빈 등록
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

-> default 로그인 시 username은 user이고, password는 서버 시작 시 spring 로그를 통해 알 수 있음.

 

user 정보 저장 시 password를 DB에 저장하여 관리할 때는 '정보통신망법, 개인정보보호법'에 의해 반드시 암호화를 해야 함.

사진 출처: https://seed.kisa.or.kr/kisa/bbs/faq.do

 

KISA 암호이용활성화 - 알림마당 - FAQ

알림마당 정보보호의 기반 암호기술 및 정책에 대한 다양한 정보전달 HOME 알림마당 소개 국산 암호기술 암호모듈검증 암호 역기능 대응 자료실 알림마당 FAQ 공지사항 FAQ 1:1문의

seed.kisa.or.kr

 

스프링 시큐리티에서 제공 및 권고하는 일방향 암호화 알고리즘(암호화는 가능하지만 복호화는 불가능) 중 하나인 BCrypt 해시함수를 사용해 password를 암호화 할 수 있음.

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

-> 빈 등록 후 DI받아 사용하면 됨!

예시 코드)

@Autowired
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
    this.userRepository = userRepository;
    this.passwordEncoder = passwordEncoder;
}

...

public String encodingPassword(String pw) {
    // 패스워드 인코딩
    return passwordEncoder.encode(pw);
}

 

 

 

 

 

 

 

스프링 시큐리티를 이용한 로그인 기능

 

1. 스프링 시큐리티를 통한 전체적인 동작 흐름

스프링 시큐리티 사용 시

 

2. 스프링 시큐리티의 로그인 처리 과정

로그인 처리 과정

- 인증/인가 성공 시에만 controller에 회원 정보(UserDetails) 전달

- 인증을 관리하는 역할을 하는 AuthenticationManager가 username을 UserDetails Service에 전달 > UserDetailsService가 username으로 DB를 조회하여 조회된 user 정보를 소유한 UserDetails 생성(조회되지 않는다면 바로 에러 발생) > AuthenticationManager가 ID와 PW를 비교하여 일치한다면 인증 성공 후 인가

- UserDetailsService 인터페이스와 UserDetails 인터페이스를 사용하여 클래스를 구현해 주어야 함(UserDetailsServiceImpl 클래스, UserDetailsImpl 클래스)

 

UserDetailsServiceImpl 클래스

@Service
public class UserDetailsServiceImpl implements UserDetailsService{

    @Autowired
    private UserRepository userRepository;

    //Authentication Manager로부터 받은 id가 DB에 있는지 확인하고 있다면 유저정보 넘겨줌(패스워드 포함)
    //Authentication Manager는 아이디와 비밀번호를 비교하여 일치하면 인증 및 인가함
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("Can't find " + username));

        return new UserDetailsImpl(user);
    }
}

 

UserDetailsImpl 클래스

public class UserDetailsImpl implements UserDetails {

    private final User user;

    public UserDetailsImpl(User user) {
        this.user = user;
    }

    public User getUser() {
        return user;
    }

    @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;
    }

    //스프링 시큐리티는 ROLE_권한 의 형태로 권한을 구분할 수 있음
    private static final String ROLE_PREFIX = "ROLE_";

    //인가 정보(권한) 부여
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        UserRole userRole = user.getRole();

        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(ROLE_PREFIX + userRole.toString());
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(authority);

        return authorities;
    }
}

 

 

이후 controller에서 로그인된 사용자 정보를 @AuthenticationPrincipal 어노테이션을 통해 사용할 수 있음. @AuthenticationPrincipal 어노테이션을 사용하면 spring security가 로그인된 사용자의 정보를 가져와 넘겨줌.

@GetMapping("/")
public String home(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    List<Folder> folders = folderService.getFolders(userDetails.getUser());
    model.addAttribute("folders", folders);
    model.addAttribute("username", userDetails.getUsername());
    return "index";
}

 

인가(접근 권한 설정)가 필요한 controller에는 @Secured("{ROLE_이름}")을 추가 + WebSecurityConfigurerAdapter를 상속한 클래스에 @EnableGlobalMethodSecurity(securedEnabled = true) 추가

예시 코드)

@Secured("ROLE_ADMIN")
@GetMapping("/admin")
public String admin(Model model, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    model.addAttribute("username", userDetails.getUsername());
    model.addAttribute("admin", true);
    return "index";
}

 

마치며

후에 기존 웹 애플리케이션의 로그인 기능에 카카오 소셜 로그인을 추가하게 된다면 세션을 만들어주어 로그인을 하는 강제 로그인 처리가 필요할 수 있다. 이 때 WebSecurityConfig 클래스에서 볼 수 있듯 AuthenticationManager를 사용하게 된다. 

// 스프링 시큐리티 통해  인증된 사용자로 등록해주기(세션 생성)

// password를 알 때
Authentication kakaoUsernamePassword = new UsernamePasswordAuthenticationToken(username, password); //id, pw에 대한 인증 토큰 생성
Authentication authentication = authenticationManager.authenticate(kakaoUsernamePassword); //authenticationManager가 토큰을 통해 id와 pw가 일치여부 확인
SecurityContextHolder.getContext().setAuthentication(authentication); //세션 생성 -> 로그인처리 완료

// password를 모를 때
UserDetailsImpl userDetails = new UserDetailsImpl(kakaoUser); //userDetails 정보 가져오기
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); //pw를 모르므로 userDetails 정보 사용
SecurityContextHolder.getContext().setAuthentication(authentication); //세션 생성 -> 로그인 처리 완료

 

'🍃spring boot > spring security' 카테고리의 다른 글

spring security  (0) 2021.09.05
Comments