본문 바로가기
관리자

Programming-[Backend]/Spring Security

[스프링 시큐리티] 13. 인증 부가/ 성공 처리 기능 : WebAuthenticationDetails, AuthenticationSuccessHandler

728x90
반응형

 

1. Details 정보 넣기

 

Details는 기본적으로는 username, password 정보를 갖는 객체이지만, 추가로 정보를 넣어 인증 과정에 활용할 수도 있다. 아래 예시는 클라이언트에서 form 태그에서 secret_key라는 정보를 서버로 넘겨주어야만 인증이 되게 하는 방식이다.

 

 

SecurityConfig에서 authenticationDetailsSource 사용 설정

우선 SecurityConfig 설정 파일에서 authenticationDetailsSource 메서드를 사용하여 Details 정보를 추가할 수 있도록 설정해준다. .authenticationDetailsSource에 대해 의존성 추가 작업도 해준다.

 

private final AuthenticationDetailsSource authenticationDetailsSource;

////

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/", "/users").permitAll()
            .antMatchers("/mypage").hasRole("USER")
            .antMatchers("/manager").hasRole("MANAGER")
            .antMatchers("/admin").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .authenticationDetailsSource(authenticationDetailsSource)
            .loginPage("/login")
            .loginProcessingUrl("/login_proc")
            .defaultSuccessUrl("/")
            .permitAll()
    ;

}

 

Details를 담는 그릇, AuthenticationDetailsSource 작성

 

패키지 구조는 위와 같은데, 1차 목표는 authenticationDetailsSource를 구성해야한다. 이를 위해 FormAuthenticationDetailsSource를 작성한다.

 

 

@Component
public class FormAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
    @Override
    public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
        return new FormWebAuthenticationDetails(context);
    }
}

 

AuthenticationDetailsSource를 타입에 맞게 상속하여 buildDetails 메서드를 Override해주면 된다. 이때 반환이 필요한 객체는 WebAuthenticationDetails로 정의되어 있기 때문에 FormWebAuthenticationDetails라는 클래스로 하나 만들어준다. 정리하자면 그릇과 같은 AuthenticationDetailsSource를 만들어야되는데, 이 객체가 사용할 수 있는 WebAuthenticationDetails 알맹이를 만드는 것이다.

 

이 객체는 @Component로 Bean으로 등록되므로, SecurityConfig 파일에서 authenticationDetailsSource를 의존하는 구문을 작성하면 해당 객체가 참조되는 구조이다.

 

 

Details 정보 : WebAuthenticationDetails 작성

 

//사용자가 전달하는 추가적인 파라미터들을 저장
public class FormWebAuthenticationDetails extends WebAuthenticationDetails {

    private String secretKey;

    public FormWebAuthenticationDetails(HttpServletRequest request) {
        super(request);
        secretKey = request.getParameter("secret_key");
    }

    public String getSecretKey() {
        return secretKey;
    }

}

 

WebAuthenticationDetails를 상속하고 인증에 사용할 secretKey를 정의한다. request.getParameter를 통해 "secret_key"라는 파라미터값을 받아와서 Details의 secretKey로 저장한다. 따라서 요청에 "secret_key"라는 값을 넣어줘야 하는데, 이 부분이 아래 클라이언트에서 전달해주는 값이 된다.

 

 

클라이언트에서의 전달값 : secret_key 전달

<form th:action="@{/login_proc}" class="form-signin" method="post">
<!--            <form name="frm" id="frm" action="#" method="post" onsubmit="return false" class="form-signin">-->
                <input th:type="hidden" th:value="secret" name="secret_key">
                <div class="form-group">
                    <input type="text" class="form-control" name="username" placeholder="아이디" required="required"
                           autofocus="autofocus">
                </div>
                <div class="form-group">
                    <input type="password" class="form-control" name="password" placeholder="비밀번호" required="required">
                </div>

                <div class="form-group">
                    Remember Me<input type="checkbox" name="remember-me" />
                </div>
                <!--<div th:if="${param.error}" class="form-group">
                    <span th:text="${session[SPRING_SECURITY_LAST_EXCEPTION]}"
                          class="alert alert-danger">잘못된 아이디나 암호입니다</span>
                </div>-->
                <button type="submit" class="btn btn-lg btn-primary btn-block">로그인</button>
<!--                <button type="submit" onclick="formLogin()" id="formbtn" class="btn btn-lg btn-primary btn-block">로그인</button>-->
            </form>

 

hidden 타입의 input 태그로 name="secret_key", value = "secret"으로 전달한다. 이것이 앞서 작성한 request.getParameter("secret_key")이다.

 

 

Provider에서 인증 과정에 추가

 

이제 details를 이용해서 인증을 가능하게 했고(SecurityConfig), 이것을 사용하는 구조 및 내용도 만들었다. 이제 인증 과정에서 어떤 로직을 통해서 이 구조 및 내용을 활용할지를 작성해주면 된다.

 

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    //설계나 정책에 따라서 인증 메서드를 다양하게 구현할 수 있다. 아래는 가장 기본적인 예시이다.

    String username = authentication.getName();
    String password = (String) authentication.getCredentials(); //Object 타입으로 반환되어 캐스팅

    AccountContext accountContext = (AccountContext) userDetailsService.loadUserByUsername(username); //직접 만든 클래스로 캐스팅

    if(!passwordEncoder.matches(password, accountContext.getAccount().getPassword())) {
        throw new BadCredentialsException("BadCredentialsException");
    }

    FormWebAuthenticationDetails formWebAuthenticationDetails = (FormWebAuthenticationDetails) authentication.getDetails();
    String secretKey = formWebAuthenticationDetails.getSecretKey();

    if(!"secret".equals(secretKey)) {
        throw new InsufficientAuthenticationException("InsufficientAuthenticationException");
    }

    //인증 성공 시 성공한 인증 객체를 생성 후 반환
    return new UsernamePasswordAuthenticationToken(accountContext.getAccount(), null, accountContext.getAuthorities());
}

 

중간부분 BadCredentials... 아래 부분부터 FormWebAuthenticationDetails를 검사한다. 만약 클라이언트로부터 받아온 secretKey의 value 값이 "secret"이 아니라면 InsufficientAuthenticationException을 발생시킨다.

 

Details 정보를 이용하여 추가적인 인증과정이 필요하다면 이 내용들을 적용하면 될 것 같다.

 

 

 


 

2. 인증 성공 핸들러

 

인증 성공 핸들러는 AuthenticationSuccessHandler이다. 이전 글들에서 이미 살펴봤던 기능으로, 간단하게만 서술한다. 사용자가 로그인 전에 접속한 페이지를 RequestCache에 저장했다가 로그인이 되면 해당 페이지로 보내주는 기능을 작성해본다. 인증 성공 시 지정한 로직으로 처리를 하기 위해서 CustomAuthenticationHandler를 만든다.

 

@Component
public class CustomAuthenticationHandler extends SimpleUrlAuthenticationSuccessHandler {

    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        //기본 이동 url 설정
        setDefaultTargetUrl("/");

        SavedRequest savedRequest = requestCache.getRequest(request, response);

        //사용자가 인증 전에 다른 자원에 접근한 적이 없다면 savedRequest 객체는 null 일수도 있다.
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            redirectStrategy.sendRedirect(request, response, targetUrl);
        } else {
            redirectStrategy.sendRedirect(request, response, getDefaultTargetUrl());
        }
    }
}

 

 

SimpleUrlAuthenticationSuccessHandler를 상속하는데, 이것은 AuthenticationSuccessHandler를 상속하는 클래스이다. 여기서 onAuthenticationSuccess 메서드를 상속하여 로직을 처리한다. 로직을 구현하는데 필요한 RequestCache, RedirectStrategy 객체를 불러오고, if문으로 분기하여 처리한다. setDefaultTargetUrl, getDefaultTargetUrl로 defaultUrl을 설정할 수 있다는 것도 기억하자.

 

다른 부분들과 똑같이, 이 클래스도 Bean으로 스캔되게 하기 위해서 @Component 어노테이션을 추가한다.

 

그리고 SecurityConfig에서 SuccessHandler를 추가해준다.

 

private final AuthenticationSuccessHandler authenticationSuccessHandler;

//중략

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
            .antMatchers("/", "/users").permitAll()
            .antMatchers("/mypage").hasRole("USER")
            .antMatchers("/manager").hasRole("MANAGER")
            .antMatchers("/admin").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .authenticationDetailsSource(authenticationDetailsSource)
            .loginPage("/login")
            .loginProcessingUrl("/login_proc")
            .defaultSuccessUrl("/")
            .successHandler(authenticationSuccessHandler)
            .permitAll()
    ;

}

 

이제 동작을 테스트 해보면 로그인 후, 로그인 전에 접근했던 페이지로 이동하는 것을 확인할 수 있다.

 

 


 

참조

 

1) 인프런 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 정수원님 강의

https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0

728x90
반응형