<개요>

- WebSecurityConfigurerAdapter 사용

- Annotation기반의 설정

 

<내용>

 - 기본적인 세팅 방법 및 제공메소드를 알아본다.

 - 특정 IP 의 호출만 가능하도록 해본다. (white list)

 - hasIpAddress 활용을 위한 API, SpEL을 사용해본다.

 - IP subnet , IPv4/v6 차이점 확인

 

1.  WebSecurityConfigurerAdapter

 - 우리가 일반적으로 가장 많이 사용하는 방법이다.

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
    public void configure(HttpSecurity http) throws Exception
    {
        http
                .httpBasic().disable()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()

                .authorizeRequests()

                // swagger-ui 관련 접속 url - permitAll 추가
                .antMatchers("/v2/api-docs/**").permitAll()
                .antMatchers("/swagger.json").permitAll()
                .antMatchers("/swagger-ui.html").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/webjars/**").permitAll()

- Session은 STATELESS를 기본으로 하며 각 url에 permitAll() 권한을 부여하는 것으로 시작하였다. 

- 상세한 정책은 antMatchers() 를 사용하며 permitAll(), anonymous(), denyAll(), authenticated(), hasAuthority()등 다양한 방법을 통해서 정의가 가능하다.

  • hasRole() or hasAnyRole()
    특정 역할을 가지는 사용자
  • hasAuthority() or hasAnyAuthority()
    특정 권한을 가지는 사용자
  • hasIpAddress()
    특정 아이피 주소를 가지는 사용자
  • permitAll() or denyAll()
    접근을 전부 허용하거나 제한
  • rememberMe()
    리멤버 기능을 통해 로그인한 사용자
  • anonymous()
    인증되지 않은 사용자
  • authenticated()
    인증된 사용자

- 내부 구현방식을 알아보기 위해서 Spring 내부소스를 보면 다음과 같다.

public final class ExpressionUrlAuthorizationConfigurer<H extends HttpSecurityBuilder<H>>
		extends
		AbstractInterceptUrlConfigurer<ExpressionUrlAuthorizationConfigurer<H>, H> {
	static final String permitAll = "permitAll";
	private static final String denyAll = "denyAll";
	private static final String anonymous = "anonymous";
	private static final String authenticated = "authenticated";
	private static final String fullyAuthenticated = "fullyAuthenticated";
	private static final String rememberMe = "rememberMe";

public ExpressionInterceptUrlRegistry permitAll() {
			return access(permitAll);
		}

		public ExpressionInterceptUrlRegistry anonymous() {
			return access(anonymous);
		}

		public ExpressionInterceptUrlRegistry rememberMe() {
			return access(rememberMe);
		}

		public ExpressionInterceptUrlRegistry denyAll() {
			return access(denyAll);
		}

 - 내부적으로 SpEL을 활용하고 access메소드를 통해서 처리하는 것을 볼 수 있다.

 

2. Annotation을 통한 관리

 - @PreAuthorize, @PostAuthorize, @Secured 를 사용하여 Controller레벨에서 메소드별 관리도 가능하다.

@RestController
public class FilterController {

	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/api/a", method = RequestMethod.POST)
    public @ResponseBody
    List<String> testMethod()throws Exception {
		...
    }

 - SpEL을 사용하는 등 내부적인 처리는 1번방식과 동일하다.

 - 다음과 같은 전역설정을 필요로 하며 되도록 @Configuration 과 같은 성격들은 한곳에 모아두는 것을 추천한다.

  @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true

 

3. hasIpAddress 예제

- SecurityConfig에 다음과 같이 추가를 하게되면 로컬에서만 /login 으로 접근하는 것이 가능하다.

.authorizeRequests()
.antMatchers("/login").hasIpAddress("127.0.0.1")

- 이 때 IP, Subnet등의 다양한 방법이 가능하다.

 e.g) 192.168.1.0/24, 172.16.0.0/16

- 여러 개의 ip를 list로 처리하고 싶다면? hasIpAddress()로는 처리가 어렵다. 힌트는 1번에서 살펴보았던 access()메소드이다.

  hasIpAddress()도 내부적으로 access()를 통해서 처리하고 있다.

    public ExpressionInterceptUrlRegistry hasIpAddress(String ipaddressExpression) {
			return access(ExpressionUrlAuthorizationConfigurer
					.hasIpAddress(ipaddressExpression));
    }

- 즉, hasIpAdress("ip #1") or hasIpAdress("ip #2") 의 형태로 SpEL을 작성하여 access()메소드를 호출하면 될 것 같다.

 

4. 구현소스

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


	@Override
    public void configure(HttpSecurity http) throws Exception
    {
        http
                .httpBasic().disable()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()

                .authorizeRequests()
                .antMatchers("/login").access( buildSpEL(ssoProperties.getAllowedIps()) )
                
                ...
                
                
    private String buildSpEL(List<String> ipLists){

        if(ipLists.isEmpty()){
            return "hasIpAddress('0.0.0.0/0')";
        }

        return ipLists.stream()
                .map(s -> String.format("hasIpAddress('%s')",s) )
                .collect(Collectors.joining(" or "));

    }

- 특정 ip 목록이 추가될 경우 SpEL처리를 하며, 빈 값이 넘어올 경우 전체 ip에 대해서 허용해주도록 한다.

@ConfigurationProperties(prefix = "1.2.3")
@Getter
@Setter
@ToString
public class Properties {
	private SsoProperties ssoProperties;
}


public class SsoProperties {
    private List<String> allowedIps = new ArrayList<>();
}

- ip목록의 경우 수시로 바뀔수 있기 때문에 별도 properties를 활용하도록 한다.

 

<수행결과>

- FilterSecurityInterceptor에 의해서 처리되었으며 처리 결과는 다음과 같다.

localhost주소 127.0.0.1

<주의사항>

- IPv6를 사용하고 있을 경우 ip형식이 다음과 같이 처리가 되기 때문에 허용주소를 "127.0.0.1"로 한 경우 403오류가 발생한다.

IPv6

- IPv6로 주소를 사용하거나 JVM 기동시 -Djava.net.preferIPv4Stack=true 옵션을 사용하면 된다.

 

<참조>

https://namu.wiki/w/%EC%84%9C%EB%B8%8C%EB%84%B7%20%EB%A7%88%EC%8A%A4%ED%81%AC

https://ko.wikipedia.org/wiki/IPv6

 

IPv6 - 위키백과, 우리 모두의 백과사전

IPv6(Internet Protocol version 6)는 인터넷 프로토콜 스택 중 네트워크 계층의 프로토콜로서 버전 6 인터넷 프로토콜(version 6 Internet Protocol)로 제정된 차세대 인터넷 프로토콜을 말한다. 인터넷(Internet)은

ko.wikipedia.org

 

+ Recent posts