Java

[Spring Boot] Spring Security 권한 설정 및 사용 방법

cob 2023. 7. 6. 13:20

 

 

Spring Security

 

 

 

 Spring Security는 Java 기반의 애플리케이션에서 보안인증을 처리하기 위한 프레임워크로 REST API 및 서비스를 보호하기 위한 다양한 보안 기능을 제공하며 주요 기능으로 인증(Authentication)과 인가(Authorization)가 있다. Spring 프레임워크와 통합되어 사용하기 쉽고 유연한 설정 방식을 제공한다. XML 또는 Java 구성을 통해 보안 설정을 정의할 수 있으며, 어노테이션과 Spring Boot의 자동 설정 기능을 활용하여 간편하게 보안을 구성할 수 있다.

 

* 인증(Authentication)
 인증은 사용자의 신원을 확인하는 과정으로, 사용자가 제공한 자격 증명(예: 아이디와 비밀번호)을 검증하여 사용자를 식별한다. Spring Security는 다양한 인증 방식을 지원하며, 사용자 정보를 데이터베이스, LDAP 서버, 메모리 등 다양한 소스에서 가져올 수 있다.
* 인가(Authorization)
 인가는 인증된 사용자에게 특정 리소스 또는 기능에 대한 액세스 권한을 부여하는 과정이다. Spring Security는 세분화된 권한 관리를 지원하여, 역할 기반의 접근 제어를 구현할 수 있다. 이를 통해 애플리케이션의 리소스에 대한 접근 제한을 설정할 수 있다.
* 이 외 기능
 Spring Security는 또한 다양한 보안 기능을 제공한다. 세션 관리, 크로스 사이트 스크립팅(XSS) 및 크로스 사이트 요청 위조(CSRF) 공격 방어, 보안 헤더 설정 등을 처리할 수 있다.

 

 

 


* 전체 소스 코드
https://github.com/kangilbin/Spring-Boot/tree/master/Spring%20Security

 

 

1.  Security 설정 파일 생성

WebSecurityConfigurerAdapter 클래스는 보안 구성을 설정하는 클래스로 configure 메서드를 재정의 하여 보안 요구 사항에 맞게 설정한다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *  1-1) 보안 구성을 설정
     */ 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/hello").permitAll()  // "/", "/hello"는 모두 볼 수 있음
                .anyRequest().authenticated()            // 나머지 모든 요청은  인증이 필요
                .and()
            .formLogin()                                 // form 로그인 사용
                .and()
            .httpBasic();                                // http basic 사용
    }

    /**
     * 1-2) 패스워드 인코딩
     * Spring Security에서 권장하는 패스워드 인코딩 방법
     * 인코딩을 하지 않으면 오류 발생
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

 

1-1) configure() 메서드 주요 기능

설정 설명
http.authorizeRequests() URL 경로에 대한 인가 규칙을 설정합니다.
.antMatchers().permitAll() 특정 URL 경로를 인증 없이 허용합니다.
.anyRequest().authenticated() 모든 요청에 대해 인증을 요구합니다.
.hasRole() 특정 역할을 가진 사용자만 접근을 허용합니다.
.formLogin() 폼 기반 로그인을 활성화합니다.
.loginPage() 로그인 페이지의 경로를 지정합니다.
.defaultSuccessUrl() 로그인 성공 후 이동할 기본 URL을 설정합니다.
.logout() 로그아웃을 처리하는 설정을 추가합니다.
.logoutUrl() 로그아웃 URL을 지정합니다.
.logoutSuccessUrl() 로그아웃 성공 후 이동할 URL을 설정합니다.
.csrf() CSRF(Cross-Site Request Forgery) 공격 방어 설정을 활성화합니다.
.sessionManagement() 세션 관리를 설정합니다.
.sessionCreationPolicy() 세션 생성 정책을 설정합니다.

 

 

1-2) 패스워드 인코딩

Spring Security에서는 보안을 위해 패스워드를 인코딩하여 저장해야 한다. 패스워드 인코딩은 일반적으로 단방향 해시 함수를 사용하며 원래 값으로 복원할 수 없다. Security 권장하는 암호화 알고리즘 방법은 BCrypt로 암호화 메서드를 Bean에 등록하여 사용한다.

 

 

 


2. 사용자 Service 클래스 생성

사용자 정보를 관리하는 Service에 UserDetailsService를 구현하거나 새로운 클래스를 만들어 UserDetailsService구현해야 한다. 중요한 건 UserDetailsService을 Bean에 등록 한다는 것이다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;


@Service
public class AccountService implements UserDetailsService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    // 새로운 acount 저장
    public AccountEntity createAccountEntity(String username, String password) {
        AccountEntity accountEntity = new AccountEntity();
        accountEntity.setUsername(username);
        accountEntity.setPassword(passwordEncoder.encode(password)); // 패스워드 인코딩

        return accountRepository.save(accountEntity);
    }

    // 2-1) 로그인 처리 시에 자동으로 호출된다.
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        /*
            로그인할 때 username, password를 입력하는데 입력받은 username을 가지고와 해당 username에
            해당하는 user 정보에서 password를 가져와 입력받은 password와 비교해 같으면 로그인 처리한다.
            다를 경우 예외를 던진다.
         */
        Optional<AccountEntity> byUsername = accountRepository.findByUsername(username);
        AccountEntity accountEntity = byUsername.orElseThrow(() -> new UsernameNotFoundException(username)); // 해당하는 데이터가 없으면 예외 처리

        /*
         * UserDetails을 return 해줘야 하지만 UserDetails은 Servcie의 User 정보
         * 즉, 현재 프로젝트에서는 AccountEntity의 인터페이스이다. 이 인터페이스의
         * 기본 정보를 Spring Security에서 User라는 클래스로 지원해준다.
         * new User(id, pw, authorities)
         */
        return new User(accountEntity.getUsername(), accountEntity.getPassword(), authorities());
    }

    // 2-2)
    private Collection<? extends GrantedAuthority> authorities() {
        return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); // 권한 세팅
    }

 

 

2-1)  loadUserByUsername 메서드 

인증 과정 중에 자동으로 호출되는 메서드로 사용자가 로그인할 때, 입력한 사용자명(username)을 기반으로 Spring Security는 loadUserByUsername() 메서드를 호출하여 사용자 정보를 조회한다. 이 메서드는 사용자명을 입력받아 해당 사용자에 대한 정보를 반환하는 역할을 수행한다.

 

 

2-2) 사용자 권한 설정 

 사용자의 권한 정보를 나타내는 GrantedAuthority 객체들의 컬렉션으로,  사용자가 가지고 있는 권한들을 전달한다.
  • GrantedAuthority : 인증된 사용자의 권한을 나타내는 인터페이스로 일반적으로는 SimpleGrantedAuthority 클래스를 사용하여 권한을 생성하고 컬렉션에 추가한다.
  • ROLE_USER : Spring Security에서 사전 정의된 기본적인 권한 이름이다. 기본적으로 이러한 권한 이름을 사용하여 권한 기반의 접근 제어를 수행할 수 있도록 지원한다.


* Controller에서 권한 체크 방법 두 가지

@Controller
public class MyController {

    @GetMapping("/admin")
    public String adminPage() {
        // ROLE_ADMIN 권한을 가진 사용자만 해당 페이지에 접근할 수 있도록 체크
        SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
                .filter(a -> a.getAuthority().equals("ROLE_ADMIN"))
                .findFirst()
                .orElseThrow(() -> new AccessDeniedException("Access denied"));

        // 권한 체크를 통과한 경우에만 해당 페이지로 이동
        return "admin";
    }
}
@Controller
public class MyController {

    /**
     * @PreAuthorize을 사용해서 권한을 요구하는 설정을 추가
     */
    @GetMapping("/admin")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminPage() {
        // ...
    }
}

 

 

* Security 설정 파일에서 체크

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin").hasRole("ADMIN")   // "/admin"는 ROLE_ADMIN 권한만 가능
                .antMatchers("/user").hasRole("USER")     // "/user"는 ROLE_USER 권한만 가능
                .antMatchers("/", "/hello").permitAll()   // "/", "/hello"는 모드 볼 수 있음
                .anyRequest().authenticated()                       
                .and()
            .formLogin()                                           
                .and()
            .httpBasic();                                           
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

 

 

반응형