본문 바로가기
공부/프로그래밍

[SpringSecurity] 인증 실패 시 핸들링 하는 법.

by demonic_ 2018. 8. 9.
반응형

스프링시큐리티로 Authorization인증을 할 때 ID와 Password 인증이 실패할 때 쓰입니다.


Config Server 구성하는데 서버정보를 가져올 때 보안을 위해 인증과정을 거치게 했습니다. 

문제는 이런서버는 로그인 페이지가 있으면 안된다고 판단했기 때문에 요청이 올때(GET으로 요청함) Auth에 username과 password를 보내 인증과정을 통과해야 한다고 판단했습니다.


그런데 이게 웹페이지로 요청을 하면 Spring Security의 기본로그인 화면으로 이동이 되었습니다. 로그인 화면을 아예 제거할 생각했었기에 다음과 같이 커스텀해야 했습니다. 그래서 실패한 경우 Json 형태로 '잘못된 접근'이라고 표기했습니다.



올리는 파일은 다음과 같습니다.

- 의존관계 설정 => build.gradle

- security username, password 설정 => application.yml

- AuthenticationEntryPoint 를 구현한 클래스 => CustomAuthenticationEntryPoint.java

- WebSecurityConfigurerAdapter 를 구현한 클래스 => SecurityConfig.java

- 테스트할 컨트롤러 => DefaultController.java

- 에러처리 컨트롤러 => ErrorHandlerController.java



하나씩 살펴보겠습니다.




# gradle을 다음과 같이 설정합니다.
1
2
3
4
compile('org.springframework.boot:spring-boot-starter')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-security')
compile('com.googlecode.json-simple:json-simple')
cs



# application.yml 에 설정

1
2
3
4
5
spring:
  security:
    user:
      name: test
      password: user
cs



# CustomAuthenticationEntryPoint 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import org.json.simple.JSONObject;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
 
/**
 * 인증되지 않았을 경우(비로그인) AuthenticationEntryPoint 부분에서 AuthenticationException 발생시키면서
 * 비로그인 상태에서 인증실패 시, AuthenticationEntryPoint 로 핸들링되어 이곳에서 처리
 */
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
 
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
            throws IOException, ServletException {
        // status를 401 에러로 지정
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // json 리턴 및 한글깨짐 수정.
        response.setContentType("application/json;charset=utf-8");
        JSONObject json = new JSONObject();
        String message = "잘못된 접근입니다";
        json.put("code""9999");
        json.put("message", message);
 
        PrintWriter out = response.getWriter();
        out.print(json);
    }
}
cs



# SecurityConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
/**
 * security 설정
 */
package kr.demonic.security;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 
/**
 * security 설정
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    private CustomAuthenticationEntryPoint authenticationEntryPoint;
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/test/**").authenticated()
                .and()
                // 인증
                .httpBasic()
                .authenticationEntryPoint(authenticationEntryPoint)
        ;
    }
}
cs



# DefaultController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.HashMap;
import java.util.Map;
 
@RestController
public class DefaultController {
    @GetMapping("/test/dev")
    public Map testDev(){
        Map result = new HashMap();
        result.put("code""0000");
        result.put("message""OK");
        return result;
    }
}
cs



# ErrorHandlerController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
 
@RestController
public class ErrorHandlerController implements ErrorController {
 
    @RequestMapping("/error")
    public Map handleError(HttpServletRequest request) {
        Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
 
        Map result = new HashMap();
        if(status != null){
            int statusCode = Integer.valueOf(status.toString());
 
            if(statusCode == HttpStatus.NOT_FOUND.value()){
                result.put("code""9997");
                result.put("message""[404] NOT_FOUND");
                return result;
            }
            if(statusCode == HttpStatus.FORBIDDEN.value()){
                result.put("code""9996");
                result.put("message""[403] FORBIDDEN");
                return result;
            }
        }else{
            result.put("code""9998");
            result.put("message""[error] ERROR");
        }
 
        return result;
    }
 
    @Override
    public String getErrorPath() {
        return "/error";
    }
}
cs





컨트롤러에 등록한 /test/dev 로 접근해 봅니다.

http://localhost:8080/test/dev




Authorization에 username과 password를 입력하여 전송합니다

계정과 비밀번호가 맞으면 다음과 같은 결과를 리턴합니다.

{

    "code": "0000",

    "message": "OK"

}


인증에 실패하면 다음을 리턴합니다.

{

    "code": "9999",

    "message": "잘못된 접근입니다"

}


의도한 URL 을 벗어날 경우 다음을 리턴합니다.

{

    "code": "9997",

    "message": "[404] NOT_FOUND"

}





해당 코드는 깃헙에 올려두었습니다.
https://github.com/lemontia/SpringSecurityBasicAuth





반응형

댓글