1. 개요

actuator를 설정하고 보니 정보 유출이 걱정돼서 접근제어를 구현하려고 한다. 일전에 IP를 통한 접근제어를 시도했는데,

PC의 IP를 코드에 넣는 것은 위험하다고 생각되어 사용자를 생성하고, 사용자들에게 권한을 부여하여 접근제어를 구현해보기로 했다 .

 

2. 구현

2-1) 페이지로 이동하기 위한 Controller 설정

-. management Controller
@Controller
@RequestMapping("management")
public class ManagementController {
    @GetMapping("index")
    public String index(){
        return "management/index";
    }
}

-. admin Controller
@Controller
@RequestMapping("admin")
public class AdminController {
    @GetMapping("index")
    public String index(){
        return "admin/index";
    }
}

2-2) Security 설정

@Component
public class Security extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
         .antMatchers("/admin/**").hasRole("ADMIN")
         .antMatchers("/management/**").hasAnyRole("ADMIN","SUPERADMIN")
         .antMatchers("/actuator/**").hasRole("SUPERADMIN")
         .and()
         .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder().encode("admin123")).roles("ADMIN","SUPERADMIN")
                .and()
                .withUser("user").password(passwordEncoder().encode("user123")).roles("USER")
                .and()
                .withUser("manager").password(passwordEncoder().encode("manager123")).roles("MANAGER","ADMIN");
    }
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

-. .authorizeRequests().antMatchers("/management/**").hasAnyRole("ADMIN","SUPERADMIN")

        .antMatchers("/actuator/**").hasRole("SUPERADMIN")

        .and()

        .httpBasic()

URL 패턴이 일치하는 경우 권한이 일치하면 화면을 보여주도록 하는 설정이다. 

authorizeRequests() 는 특정한 경로에 특정 권한을 가진 사용자만 접근이 가능하도록 사용하는 메소드이다.

httpBasic은 사용자가 HTTP기반으로 인증 할 수 있도록 해주는 설정이다.

* 주의 *

1. 권한을 검사할 때는 제일 위에서 부터 하나씩 차례로 검사를 진행한다. 따라서 anyRequest().permitAll()을 먼저 추가해주는 경우에는 아래에 아무리 많은 접근제어를 구현해도 소용이 없다.

2. anyRequest().authenticated() 는 로그인한 모든 사용자는 모든 resource에 접근가능하도록 하는 설정으로, 권한을 부여해서 접근제어를 구현하려는 의도와 맞지 않다. 실제로 이 구문을 추가하면 에러가 나면서 기동이 되지 않는다.

 

-. inMemoryAuthentication().withUser("admin").password(passwordEncoder().encode("admin123")).roles("ADMIN","SUPERADMIN")

따로 DB를 사용하고 있지 않음으로 inMemoryAuthentication() 메소드를 사용해 인메모리 사용자 저장소를 사용한다. 이하 문구는 user 생성 부분으로 직관적임으로 생략.

* 주의 *

application.properties 에서 spring.main.user.name= / spring.main.user.password= / spring.main.user.roles= 를 사용하여 user를 생성할 수도 있는데, 위에서 처럼 코드로 생성을 하는 경우 application.properties에서 설정한 옵션은 적용되지 않았다.

 

-. BCryptPasswordEncoder()

회원 가입 기능을 구현했을때 유저의 암호를 그대로 DB에 저장하면 보안에 취약하게 된다. 이때 많이 쓰이는게 BCrypt 이다.

Bcrypt는 입력을 1회 해시하는게 아니라 랜덤의 소트(salt)를 부여하여 여러번 해시를 적용하고, 원래 암호를 추측하기 어렵도록 도와준다.

BCryptPasswordEncoder는 그러한 BCrypt 해시 함수를 이용하여 비밀번호를 저장하는 방법을 제공해 준다. 

 

3. 테스트

-. 권한확인이 필요한 페이지에 접근한 경우 아래와 같이 접속하라는 pop-up이 뜬다.

-. manager로 접속하여 admin page와 actuator page 접근.

admin page는 ADMIN role이 있기 때문에 정상적으로 보여지지만,

 

 actuator page는 SUPERADMIN role 이 없어서 볼 수 없다.

 

-. admin으로 접속하여 actuator page 접근

정상적으로 확인이 가능하다.

 

tip. 초기에 별다른 설정 없이, security설정을 하고 페이지를 호출하면 계정을 입력하라는 pop-up 혹은 디폴트 로그인 페이지가 뜬다.

콘솔 기동로그를 살펴보면 디폴트로 [ 계정명 : user ] 의 password를 출력해 주는데, 해당 암호를 복사하고 user로 접속하면 정상적으로 페이지를 호출해 볼 수 있다.