• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

hasIpAddress(), getRemoteAddr() 질문입니다.

23.09.15 22:24 작성 23.09.19 22:56 수정 조회수 285

0

이전에 이런 질문들((https://www.inflearn.com/questions/423204))이 있던 것을 확인하였으나 저는 해결이 되지 않아 질문을 남깁니다.

postman으로 회원가입하고 로그인하려고 하면 401 Unauthorized가 나며 많이 보셨을 이런 에러가 나옵니다.

java.lang.UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getRemoteAddr() is not supported

....

이와 같은 에러의 질문과 답변을 보고 hasIpAddress()에서 .access("hasIpAddress('" + IP + "')")로 바꿔서 해보았는데도 또는 스프링 부트 버전을 바꾸어 시도해(2.6.2, 2.4.2 등)보았으나 해결이 되지 않아 질문을 드립니다.

처음 설정한 spring boot 버전은 2.7.15입니다.

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.15'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '11'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

ext {
    set('springCloudVersion', "2021.0.8")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.h2database:h2:1.3.176'

    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.modelmapper:modelmapper:3.1.1'

    implementation 'org.springframework.boot:spring-boot-starter-security'
    testImplementation 'org.springframework.security:spring-security-test'


    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

tasks.named('test') {
    useJUnitPlatform()
}

WebSecurity은

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private final Environment environment;
    private final UserService userService;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;

     private String IP = "192.168.1.2";

    @Autowired
    public WebSecurity(Environment environment, UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.environment = environment;
        this.userService = userService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();

        http.authorizeRequests()
                .antMatchers("/**")
//                .hasIpAddress(IP)
                .access("hasIpAddress('" + IP + "')")
                .and()
                .addFilter(getAuthenticationFilter());

        http.headers().frameOptions().disable();
    }

    private AuthenticationFilter getAuthenticationFilter() throws Exception {
        AuthenticationFilter authenticationFilter =
                new AuthenticationFilter(authenticationManager(), userService, environment);
        return authenticationFilter;
    }

    // db password와 input password 비교
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
    }

}

2.7 이후 버전에서는 https://www.inflearn.com/chats/789887와 같이 WebSecurityConfigurerAdapter 를 상속하지 않고 configure함수 오버라이드 대신 SecurityFilterChain을 사용해보았으나 이렇게 해도 되지 않았습니다.

AuthenticationFilter에 unsuccessfulAuthentication를 오버라이드하여 확인해본 결과

org.springframework.security.authentication.BadCredentialsException: 자격 증명에 실패하였습니다.
	at org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:80)
	at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:147)
	at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:182)
	at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:201)
	at com.example.userservice.filter.AuthenticationFilter.attemptAuthentication(AuthenticationFilter.java:46)

라고 오류가 나오는 것을 확인했습니다.

AuthenticationFilter.java:46 부분은 return getAuthenticationManager().authenticate( 부분 입니다.

AuthenticationFilter 코드는 다음과 같습니다.

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final UserService userService;
    private final Environment environment;

    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                UserService userService,
                                Environment environment) {
        super.setAuthenticationManager(authenticationManager);
        this.userService = userService;
        this.environment = environment;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        try {
            RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);

            return getAuthenticationManager().authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getEmail(), creds.getPassword(), new ArrayList<>()
                    )
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
//        log.debug(((User)authResult.getPrincipal()).getUsername());
        String userName = ((User)authResult.getPrincipal()).getUsername();
        UserDto userDetails = userService.getUserDetailsByEmail(userName);
        System.out.println("details :" + userDetails);

        String token = Jwts.builder()
                           .setSubject(userDetails.getUserId())
                           .setExpiration(
                                   new Date((System.currentTimeMillis() + Long.parseLong(environment.getProperty("token.expiration_time"))))
                           )
                           .signWith(SignatureAlgorithm.HS512, environment.getProperty("token.secret"))
                           .compact();

        System.out.println("token :" + token);
        response.addHeader("token", token);
        response.addHeader("userId", userDetails.getUserId());
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
//        super.unsuccessfulAuthentication(request, response, failed);
        failed.printStackTrace();
    }
}

 

어느 부분을 어떻게 수정하여 진행을 해야 할까요?

 


AuthenticationFilter에서 attemptAuthentication 함수를 UsernamePasswordAuthenticationToken 받는 것으로 수정하여 디버깅해보니 아래 코드의 authenticationToken 값은 잘 나옵니다. 그런데 위의 질문처럼 getAuthenticationManager().authenticate 여기서 문제가 발생합니다.

문제가 발생하는 부분은 AbstractAuthenticationProcessingFilter의 저 부분으로 잡히며AuthenticationFilter 코드는 다음과 같습니다.

import com.example.userservice.dto.UserDto;
import com.example.userservice.service.UserService;
import com.example.userservice.vo.RequestLogin;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

@Slf4j
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final UserService userService;
    private final Environment environment;

    public AuthenticationFilter(AuthenticationManager authenticationManager,
                                UserService userService,
                                Environment environment) {
        super.setAuthenticationManager(authenticationManager);
        this.userService = userService;
        this.environment = environment;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        try {
            RequestLogin creds = new ObjectMapper().readValue(request.getInputStream(), RequestLogin.class);

            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    creds.getEmail(), creds.getPassword(), new ArrayList<>());

            return getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
//        log.debug(((User)authResult.getPrincipal()).getUsername());
        String userName = ((User)authResult.getPrincipal()).getUsername();
        UserDto userDetails = userService.getUserDetailsByEmail(userName);
        System.out.println("details :" + userDetails);

        String token = Jwts.builder()
                           .setSubject(userDetails.getUserId())
                           .setExpiration(
                                   new Date((System.currentTimeMillis() + Long.parseLong(environment.getProperty("token.expiration_time"))))
                           )
                           .signWith(SignatureAlgorithm.HS512, environment.getProperty("token.secret"))
                           .compact();

        System.out.println("token :" + token);
        response.addHeader("token", token);
        response.addHeader("userId", userDetails.getUserId());
    }

//    @Override
//    protected void unsuccessfulAuthentication(HttpServletRequest request,
//                                              HttpServletResponse response,
//                                              AuthenticationException failed) throws IOException, ServletException {
////        super.unsuccessfulAuthentication(request, response, failed);
//        failed.printStackTrace();
//    }
}

 

해결 방법 좀 알려주시면 감사하겠습니다.

답변 1

답변을 작성해보세요.

-3

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2023.09.17

안녕하세요, 인프런 AI 인턴이에요.

postman으로 회원가입하고 로그인 시 401 Unauthorized 에러가 발생하며 해당 에러의 원인은 다음과 같습니다.

에러 메시지인 “java.lang.UnsupportedOperationException: public abstract java.lang.String javax.servlet.ServletRequest.getRemoteAddr() is not supported”는 Spring MVC에서 HttpServletRequest의 getRemoteAddr() 메서드를 사용하려고 하지만, 이 메서드가 지원되지 않는 경우 발생합니다.

보통 이런 경우는 Spring Security의 hasIpAddress() 메서드를 사용하여 IP 주소를 검사할 때 발생할 수 있습니다.

그러나 해당 에러가 발생하는 이유와 해결 방법에 대해서는 자세한 코드나 설정 정보를 제공해주셔야 합니다. 현재는 build.gradle 파일의 일부 정보만 제공되었기 때문에 정확한 답변을 드리기 어렵습니다.

따라서, 아래의 정보를 함께 제공해주시면 더 정확한 답변을 드릴 수 있을 것입니다.
- 해당 에러가 발생하는 코드 부분
- WebSecurityConfigurerAdapter를 상속받은 클래스에서 어떻게 hasIpAddress() 메서드를 사용하고 있는지 코드 예시
- 사용 중인 Spring Security 버전

추가로, 해당 에러의 원인을 파악하기 위해 로그를 확인해보시는 것도 도움이 될 수 있습니다. 로그에서 더 자세한 에러 메시지와 스택 트레이스를 확인해보시면 원인을 찾을 수 있을 것입니다.

더 자세한 도움이 필요하시다면, 코드나 설정 정보를 더 자세하게 제공해주시거나 Spring Security 관련 문서를 참고하시면 됩니다. 참고로, 인프런에서는 Spring Security 강의도 제공하고 있으니 참고하시면 도움이 될 수 있을 것입니다.

다른 질문이 있으면 언제든지 물어보세요. 좋은 하루 되세요~요.