스프링 시큐리티 의존성 추가
- 스프링 시큐리티 의존성만 추가해도 서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다.
- 별도의 설정이나 구현을 하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동한다.
- 모든 요청은 인증 되어야 자원에 접근이 가능하다.
- 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다.
- 기본 로그인 페이지를 제공한다.
- 기본 계정을 한개 제공한다.
WebSecurityConfigurerAdapter
- 스프링 시큐리티의 웹 보안 기능을 초기화 및 설정하는 역할을 한다.
- HttpSecurity 를 생성한다. -> HttpSecurity 는 세부적인 보안 기능을 설정할 수 있는 API 를 제공한다.
- HttpSecurity는 위와 같이 인증 API, 인가 API 를 설정할 수 있다.
인가 API
- authenticated() : 인증된 사용자의 접근을 허용
- fullyAuthenticated() : 인증된 사용자의 접근을 허용, rememberMe 인증 제외
- permitAll() : 무조건 접근을 허용
- denyAll() : 무조건 접근을 허용하지 않음
- anonymous() : 익명사용자의 접근을 허용
- rememberMe() : 기억하기를 통해 인증된 사용자의 접근을 허용
- access(String) : 주어진 SpEL 표현식의 평가 결과가 true이면 접근을 허용
- hasRole(String) : 사용자가 주어진 역할이 있다면 접근을 허용
- hasAuthority(String) : 사용자가 주어진 권한이 있다면
- hasAnyRole(String...) : 사용자가 주어진 권한이 있다면 접근을 허용
- hasAnyAuthority(String...) : 사용자가 주어진 권한 중 어떤 것이라도 있다면 접근을 허용
- hasIpAddress(String) : 주어진 IP로부터 요청이 왔다면 접근을 허용
Security Config
- 사용자 정의 보안 설정 클래스이다.
- 아무 설정을 하지 않아도 아래와 같이 기본적으로 세팅이 되어있다.
// WebSecurityConfigurerAdapter.java
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure......")
http
.authorizeRequests()
.anyRequest().authenticated() // 모든 요청을 검증하겠다.
.and()
.formLogin().and()
.httpBasic();
}
- 하지만 보안 설정을 커스텀하기 위해서는 WebSecurityConfigurerAdapter 를 상속받아서 구현해야 한다. -> 정확히는 위의 configure 메소드를 Override 한다고 생각하면 된다.
필터 On/Off (Security Config)
- Spring Security 의 특정 필터를 disable하여 동작하지 않게 한다.
- 사용하지 않을 필터를 명시적으로 disable하는 것도 좋은 방법이다.
- 코드예시
// basic authentication
http.httpBasic().disable(); // basic authentication filter 비활성화
// csrf
http.csrf();
// remember-me
http.rememberMe();
로그인 & 로그아웃 페이지 관련 기능 (Security Config)
- 폼 로그인의 로그인 페이지를 지정하고 로그인에 성공했을때 이동하는 URL을 지정한다.
- 로그아웃 URL을 지정하고 로그아웃에 성공했을때 이동하는 URL을 지정한다.
- 코드예시
// login
http.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll(); // 모두 허용
// logout
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/");
Url Matchers 관련 기능 (Security Config)
- antMatchers
- http.authorizeRequests().antMatchers("/signup").permitAll()
- “/signup” 요청을 모두에게 허용한다.
- mvcMatchers
- http.authorizeRequests().mvcMatchers("/signup").permitAll()
- “/signup”, “/signup/“, “/signup.html” 와 같은 유사 signup 요청을 모두에게 허용한다.
- regexMatchers
- 정규표현식으로 매칭한다.
- requestMatchers
- antMatchers, mvcMatchers, regexMatchers는 결국에 requestMatchers로 이루어져있다.
- 명확하게 요청 대상을 지정하는 경우에는 requestMatchers를 사용한다.
인가 관련 설정 (Security Config)
- authorizeRequests()
- http.authorizeRequests()
- 인가를 설정한다.
- permitAll()
- http.authorizeRequests().antMatchers(“/home").permitAll()
- “/home” 요청을 모두에게 허용한다.
- hasRole()
- http.authorizeRequests().antMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN")
- 권한을 검증한다.
- authenticated()
- http.authorizeRequests().anyRequest().authenticated()
- 인증이 되었는지를 검증한다.
- 코드예시
// authorization
http.authorizeRequests()
// /와 /home은 모두에게 허용
.antMatchers("/", "/home", "/signup").permitAll()
// hello 페이지는 USER 롤을 가진 유저에게만 허용
.antMatchers("/note").hasRole("USER")
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/notice").hasRole("ADMIN")
.anyRequest().authenticated();
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new LoggingFilter(), WebAsyncManagerIntegrationFilter.class);
http.authorizeRequests()
.mvcMatchers("/", "/info", "/account/**", "/signup").permitAll()
.mvcMatchers("/admin").hasRole("ADMIN")
.mvcMatchers("/user").hasRole("USER")
.anyRequest().authenticated()
.expressionHandler(expressionHandler());
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("junho").password("{noop}123").roles("USER").and()
.withUser("admin").password("{noop}!@#").roles("ADMIN");
}
}
- WebSecurityConfigurerAdapter 를 상속받으면 Security 설정을 할 수 있다.
http.authorizeRequests()
는 요청을 어떻게 인가할지에 대한 설정이고, chaining 형태로 설정할 수 있다.anyRequest().authenticated()
는 그 밖에 기타 등등은 인증만 되면 접근이 가능하다는 뜻이다.- 위와 같이 auth를 매개변수로 받는 configure를 오버라이딩 하면 인메모리 유저를 생성할 수 있다.
Ignoring (Security Config)
- 모든 요청은 시큐리티 필터들을 거치면서 권한을 검사한다.
- 하지만 검사하고 싶지 않은 리소스까지 전부 검사하면 자원낭비다. -> 예를들어 favicon.io
- 아래와 같이 ignoring() 을 사용하면 위의 문제를 해결할 수 있다.
@Override
public void configure(WebSecurity web) throw Exception {
web.ignoring().mvcMatchers("/favicon.io");
}
- 만약 favicon 과 같은 정적리소스에 모두 적용하고 싶다면 아래와 같이 구현하면 된다.
@Override
public void configure(WebSecurity web) throw Exception {
web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
- 실제로 FilterChainFoxy 에 break point 를 걸어보면 시큐리티 필터를 하나도 거치지 않는 것을 확인할 수 있다.
- 그렇다면 아래와 같이 설정한 것과는 어떤 차이가 있을까?
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll();
}
- 결과는 똑같다. -> 하지만 과정이다르다.
- 이 경우 필터 15개를 모두 거치게 된다. -> 다만 가장 마지막 필터인 FilterSecurityinterceptor에서 권한을 인정해줄 뿐이다.
- 위의 경우도 필터를 거치므로 자원낭비가 발생한다. 그러므로 web.ignoring() 방식이 더 좋다고 할 수 있다.
- 하지만 "/", "/info", "/account/**", "/signup" 와 같은 동적인 리소스를 관리하는 요청의 경우 위와 같이 처리하는 것이 바람직하다.
인메모리 사용자 추가
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("{noop}1111").roles("USER");
auth.inMemoryAuthentication().withUser("manager").password("{noop}1111").roles("MANAGER");
auth.inMemoryAuthentication().withUser("admin").password("{noop}1111").roles("ADMIN");
}
AuthenticationManager 빈으로 등록
- Test 코드를 짤때, @Autowired 로 AuthenticationManager 를 가져다가 쓸 일이 있을 것이다.
- 하지만 AuthenticationManager 는 기본적으로 빈으로 노출이 되어있지 않기 때문에 아래와 같이 빈으로 등록시켜야 사용할 수 있다.
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throw Exception {
return super.authenticationManagerBean();
}
PasswordEncoder 빈으로 등록
- 비밀번호를 암호화 하기 위해서는 아래와 같이 PasswordEncoder 을 빈으로 등록해야한다.
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
- 아래와 같이 비밀번호를 암호화해서 저장해 줄 수 있다.
private final PasswordEncoder passwordEncoder;
//...
account.setPassword(passwordEncoder.encode(account.getPassword()));
REFERENCES
- 백기선님의 스프링 시큐리티
- 정수원님의 스프링 시큐리티
- 안성훈님의 스프링 시큐리티
'Spring Security' 카테고리의 다른 글
CustomAuthenticationProvider (0) | 2022.02.28 |
---|---|
AuthenticationManager 와 AuthenticationProvider (0) | 2022.02.28 |
SecurityContextHolder 와 FilterChainProxy (0) | 2022.02.28 |
SecurityContextHolder, AuthenticationManager, ThreadLocal (0) | 2022.02.28 |
스프링 시큐리티 아키텍처 (0) | 2022.02.28 |