[Spring Boot] Spring-Security

이맑음·2021년 10월 5일
0

Spring Boot

목록 보기
12/21
post-thumbnail

fastcampus 웹 개발 마스터 초격차 패키지를 수강하며 정리한 내용들입니다.

Spring-Security

Servlet Container

  • 톰캣과 같은 웹 애플리케이션들을 서블릿 컨테이너라고 하는데, 이는 기본적으로 필터와 서블릿으로 구성되어 있다.
  • filter1,2,3-->--서블릿1, DispatchServlet-->--controller1,2-->--method1
  1. request가 threadlocal로 실행되어 들어온다.
  2. 필터를 차례대로 거친다.
  3. url에 따라 서블릿이 분기되고
  4. 실행될 메소드를 찾아 request, response를 넘긴다
  • 이때, 필터가 체인처럼 연결되어 있어 필터 체인이라고 불리며 모든 request는 이 필터 체인을 반드시 거쳐야만 서블릿 서비스에 도착할 수 있다.

Spring-Security

  • 필터를 반드시 거쳐야 하므로 스프링 시큐리티는 DelegatingFilterProxy라는 자체 필터를 만들어 메인 필터 체인에 끼워넣고, 그 하위에 다시 SecurityFilterChain 그룹을 등록한다.
  • FilterChain을 여러 개 만들 수 있지만 들어오는 request의 순서에 유의해서 작성해야 한다. -> @Order() 어노테이션을 사용해 순서를 지정할 수 있다.

예제

  • 스프링 시큐리티를 사용한 패지키는 처음 루트 페이지로 들어가면 스프링 시큐리티에 의한 로그인 페이지가 나타난다. 서버 콘솔에 보면 비밀번호가 작성되어 있지만 매번 작성하기 번거롭기 때문에, resource/application.yml 파일에 아래와 같이 작성해서 사용한다.
spring:
  security:
    user:
      name: 로그인시 사용할 이름
      password: 로그인시 사용할 비밀번호
      roles: USER
  • controller/HomeController
@RestController
public class HomeController {

    @RequestMapping("/")
    public String index(){
        return "홈페이지";
    }
    
    // url에 접근할 때, 사용자가 어떤 권한을 가지고 접근하는지 Authentication을 사용해 확인해 볼 수 있다.
    @RequestMapping("/auth")
    public Authentication auth(){
        return SecurityContextHolder.getContext()
                .getAuthentication();
    }
    
    // 접근한 사용자에 대한 권한과 어떤 페이지에 접근 했는지에 대해 확인하는 메서드
    // @PreAuthorize = 해당 페이지에 접근할 수 있는 사용자에 대한 권한을 지정한다.
    @PreAuthorize("hasAnyAuthority('ROLE_USER')")
    @RequestMapping("/user")
    public SecurityMessage user(){
        return SecurityMessage.builder()
                .auth(SecurityContextHolder.getContext().getAuthentication())
                .message("User 정보")
                .build();
    }

    @PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
    @RequestMapping("/admin")
    public SecurityMessage admin(){
        return SecurityMessage.builder()
                .auth(SecurityContextHolder.getContext().getAuthentication())
                .message("관리자 정보")
                .build();
    }
}
  • controller/SecurityMessage
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SecurityMessage {

    private Authentication auth;
    private String message;

}
  • application.yml에는 한 개의 계정만 추가할 수 있으므로, 더 많은 계정을 추가하고 싶다면 WebSecurityConfigurerAdapter를 사용하여 추가할 수 있다.
  • 원래 application.yml에 작성한 user1 계정으로 진행해야 하지만, 이미 SecurityConfig파일에 withUser()로 user2와 admin 계정을 생성하여 user1 계정은 인식이 되지 않는다.
  • user2의 계정으로 /auth 페이지에 접근 했을 경우, 아래와 같이 접근한 사용자에 대한 권한과 인증된 사용자 인지 등에 대한 정보를 보여준다.
{
  "authorities": [
    {
      "authority": "ROLE_USER"
    }
  ],
  "details": {
    "remoteAddress": "",
    "sessionId": ""
  },
  "authenticated": true,
  "principal": {
    "password": null,
    "username": "user2",
    "authorities": [
      {
        "authority": "ROLE_USER"
      }
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true
  },
  "credentials": null,
  "name": "user2"
}
  • user2의 계정으로 /user 페이지에 접근 했을 경우
{
  // 위와 동일하게 보여주며, 추가로 작성한 message를 보여준다.
  "message": "User 정보"
}
  • user2 계정으로 /admin 페이제 접근 했을 경우, 당연히 접근 할 수 없다.
  • 이처럼 페이지에 접근하는 사용자에 대한 개인정보를 보호하기 위해 스프링 시큐리티를 사용한다.

  • config/SecurityConfig

@EnableWebSecurity(debug = true) // request가 올 때마다 어떤 filter chain을 거치고 있는지 콘솔창에 보여준다.
@EnableGlobalMethodSecurity(prePostEnabled = true) // 권한을 확인하는 모듈을 사용할 수 있다.
public class SecurityConfig extends WebSecurityConfigurerAdapter { 
// WebSecurityConfigurerAdapter 필터 체인을 구성하는 configuration class

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser(User.builder()
                        .username("user2")
                        .password(passwordEncoder().encode("2222"))
                        .roles("USER")
                ).withUser(User.builder()
                .username("admin")
                .password(passwordEncoder().encode("3333"))
                .roles("ADMIN"));

    }
    
    // 위에서 계정을 생성할 때 비밀번호를 따로 인코딩하지 않으면 오류가 발생하므로 
    // BCryptPasswordEncoder를 사용한다.
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
    // security filter 설정
    @Override
    protected void configure(HttpSecurity http) throws Exception { 

//        http.antMatcher("/**"); // 모든 request에 대해서 filter를 거치게 설정
//        http.antMatcher("/api/**"); // 특정 api 밑의 request들만 filter를 거치게 설정

// 스프링 시큐리티는 기본적으로 모든 페이지에 대한 접근을 막기 때문에
// "/" 즉, 홈페이지에 대해서는 모든 접근을 허용하도록 변경한다.
        http.authorizeRequests((requests) ->
                requests.antMatchers("/").permitAll()
                        .anyRequest().authenticated());
        http.formLogin();
        http.httpBasic();
    }
}
  • @EnableWebSecurity(debug = true)를 사용하면 서버 콘솔창에서 스프링 시큐리티가 거친 필터 체인을 아래와 같이 보여준다.
  • SecurityConfig에 아래 코드를 추가하고 다시 서버 콘솔창을 보면 header, csrf, logout, requestCache 필터 체인을 거치지 않은 것을 확인할 수 있다.
http.headers().disable()
                .csrf().disable()
                .logout().disable()
                .requestCache().disable();

0개의 댓글