Java/인증 및 인가, IAM

[Java] Spring Boot Security 이해하기 -2 : Spring Boot 2.x 버전 환경 구성하기

adjh54 2022. 12. 18. 23:15
728x170
해당 글에서는 이전에 Spring Security 5.7.x 버전에 대해 이해한 내용을 기반으로 실제 구현하는 방법에 대해서 공유합니다.




💡 [참고] Spring Boot Security를 적용하기 이전의 이해하기 위한 글을 참고하시면 좋습니다.
 

[Java] Spring Boot Security 이해하기 -1 : 구조 및 파일 이해

해당 글에서는 Spring Boot 기반의 Spring Security Framework를 적용하여 로그인의 API를 구성하는 방법에 대해서 공유합니다. 1) 개발환경 💡 Spring Security 개발 환경을 구성하기 위해 사용한 개발환경입니

adjh54.tistory.com

 

 

1) 개발환경


1. 개발 환경 구성 라이브러리


💡 Spring Security 개발 환경을 구성하기 위해 사용한 개발환경입니다.
개발 환경 버전 비고 
java 1.8  
Spring Boot 2.7.5  
Spring Boot Starter Security 2.7.5 Spring Framework : 5.7.4
Spring Boot Starter Data JDBC 2.7.5  
mybatis 2.2.2  
lombok latest  
postgresql lates  
빌드관리도구 Gradle 7.5  
개발 툴 IntelliJ IDEA 2022.3  

 

 

2. 라이브러리 추가


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-security'          // Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'         // Spring Boot JDBC + HikariCP
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'      // Spring Boot MyBatis
    compileOnly 'org.projectlombok:lombok'                                          // Lombok
    runtimeOnly 'org.postgresql:postgresql'                                         // PostgreSQL
}

 

 

 

2) Spring Security 비즈니스 로직 구현하기


💡데이터 조회 비즈니스 로직 부분 구성

- 비즈니스 로직을 구현하기 위한 파일 구조에 대한 스크린샷입니다.

 

 

1. UserDetailsDto.java


💡UserDetailsDto.java

- Spring Security를 이용하기 위한 객체로 사용합니다.

-  UserDetails의 실제 구현체입니다.
package com.adjh.multiflexapi.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Slf4j
@Getter
@AllArgsConstructor
public class UserDetailsDto implements UserDetails {

    @Delegate
    private UserDto userDto;
    private Collection<? extends GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return userDto.getUserPw();
    }

    @Override
    public String getUsername() {
        return userDto.getUserNm();
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

 

 

 

2. UserDetailsServiceImpl.java


💡UserDetailsServiceImpl.java

- Spring Security를 이용하기 위한 구현체로 사용합니다.

- UserDetailsService의 구현체로 사용합니다.
package com.adjh.multiflexapi.service.impl;


import java.util.Collections;

import com.adjh.multiflexapi.model.UserDetailsDto;
import com.adjh.multiflexapi.model.UserDto;
import com.adjh.multiflexapi.service.UserService;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserService userService;

    public UserDetailsServiceImpl(UserService us) {
        this.userService = us;
    }

    @Override
    public UserDetails loadUserByUsername(String userId) {
        UserDto userDto = UserDto
                .builder()
                .userId(userId)
                .build();

        // 사용자 정보가 존재하지 않는 경우
        if (userId == null || userId.equals("")) {
            return userService.login(userDto)
                    .map(u -> new UserDetailsDto(u, Collections.singleton(new SimpleGrantedAuthority(u.getUserId()))))
                    .orElseThrow(() -> new AuthenticationServiceException(userId));
        }
        // 비밀번호가 맞지 않는 경우
        else {
            return userService.login(userDto)
                    .map(u -> new UserDetailsDto(u, Collections.singleton(new SimpleGrantedAuthority(u.getUserId()))))
                    .orElseThrow(() -> new BadCredentialsException(userId));
        }
    }

}

 

 

 

3) 데이터 조회 비즈니스 로직 부분 구현


💡 데이터 조회 비즈니스 로직 부분 구성

- 데이터 베이스 내에 존재하는 사용자인지에 대한 확인을 위한 DB 데이터 조회 로직을 추가합니다.

 

 

1. UserDto.java


💡UserDto.java

- 비즈니스 로직 중 '로그인'에 사용이 되는 Model 객체를 생성합니다.
package com.adjh.multiflexapi.model;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserDto {

    // 사용자 시퀀스
    private int userSq;

    // 사용자 아이디
    private String userId;

    // 사용자 패스워드
    private String userPw;

    // 사용자 이름
    private String userNm;

    // 사용자 상태
    private String userSt;

    @Builder
    UserDto(int userSq, String userId, String userPw, String userNm, String userSt) {
        this.userSq = userSq;
        this.userId = userId;
        this.userPw = userPw;
        this.userNm = userNm;
        this.userSt = userSt;
    }

}

 

 

 

2. UserService.java


💡 UserService.java

- 로그인 정보를 DB에서 확인하기 위한 Service interface를 의미합니다.
package com.adjh.multiflexapi.service;

import com.adjh.multiflexapi.model.UserDto;

import java.util.Optional;

public interface UserService {
    Optional<UserDto> login(UserDto userVo);
}

 

 

 

3. UserServiceImpl.java


💡UserServiceImpl.java

- 로그인 정보를 DB에서 확인하기 위한 Service interface의 구현체를 의미합니다.
package com.adjh.multiflexapi.service.impl;

import com.adjh.multiflexapi.mapper.CodeMapper;
import com.adjh.multiflexapi.mapper.UserMapper;
import com.adjh.multiflexapi.model.UserDto;
import com.adjh.multiflexapi.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    private final SqlSession sqlSession;

    public UserServiceImpl(SqlSession ss) {
        this.sqlSession = ss;
    }

    /**
     * 로그인 구현체
     *
     * @param userDto UserDto
     * @return Optional<UserDto>
     */
    @Override
    public Optional<UserDto> login(UserDto userDto) {
        UserMapper um = sqlSession.getMapper(UserMapper.class);
        return um.login(userDto);
    }
}

 

 

 

4. UserMapper.java


💡UserMapper.java

- 로그인 정보를 DB에서 확인하기 위한 SQL문과의 매핑을 수행하는 인터페이스를 의미합니다.
package com.adjh.multiflexapi.mapper;

import com.adjh.multiflexapi.model.UserDto;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserMapper {

    Optional<UserDto> login(UserDto userDto);

}

 

 

 

5. UserMapper.xml


💡UserMapper.xml

- 데이터 베이스 내에서 사용자 아이디를 기반으로 사용자를 조회합니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.adjh.multiflexapi.mapper.UserMapper">

    <!--로그인-->
    <select id="login" resultType="userDto">
        SELECT t1.*
        FROM multiflex_scma.tb_user t1
        WHERE user_id = #{userId}
    </select>
</mapper>

 

 

 

4) Spring Security 환경 설정 구현


💡Spring Security 환경 설정

- Spring Security 환경 설정을 위한 폴더 구조에 대한 스크린샷입니다.

 

 

1. WebSecurityConfig.java


💡 WebSecurityConfig.java

- Spring Security의 환경설정을 구성하기 위한 클래스입니다.

- 웹 서비스가 로드될 때 Spring Container에 의해 관리가 되는 클래스이며 사용자에 대한 ‘인증’과 ‘인가’에 대한 구성을 Bean 메서드로 주입을 한다.
package com.adjh.multiflexapi.config;

import com.adjh.multiflexapi.config.filter.CustomAuthenticationFilter;
import com.adjh.multiflexapi.config.handler.CustomAuthFailureHandler;
import com.adjh.multiflexapi.config.handler.CustomAuthSuccessHandler;
import com.adjh.multiflexapi.config.handler.CustomAuthenticationProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * Spring Security 환경 설정을 구성하기 위한 클래스입니다.
 * 웹 서비스가 로드 될때 Spring Container 의해 관리가 되는 클래스이며 사용자에 대한 ‘인증’과 ‘인가’에 대한 구성을 Bean 메서드로 주입을 한다.
 */
@Slf4j
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    /**
     * 1. 정적 자원(Resource)에 대해서 인증된 사용자가  정적 자원의 접근에 대해 ‘인가’에 대한 설정을 담당하는 메서드이다.
     *
     * @return WebSecurityCustomizer
     */
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // 정적 자원에 대해서 Security를 적용하지 않음으로 설정
        return (web) -> web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }

    /**
     * 2. HTTP에 대해서 ‘인증’과 ‘인가’를 담당하는 메서드이며 필터를 통해 인증 방식과 인증 절차에 대해서 등록하며 설정을 담당하는 메서드이다.
     *
     * @param http HttpSecurity
     * @return SecurityFilterChain
     * @throws Exception Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        // [STEP1] 서버에 인증정보를 저장하지 않기에 csrf를 사용하지 않는다.
        http.csrf().disable();

        // [STEP2] form 기반의 로그인에 대해 비 활성화하며 커스텀으로 구성한 필터를 사용한다.
        http.formLogin().disable();

        // [STEP3] 토큰을 활용하는 경우 모든 요청에 대해 '인가'에 대해서 사용.
        http.authorizeHttpRequests((authz) -> authz.anyRequest().permitAll());

        // [STEP4] Spring Security Custom Filter Load - Form '인증'에 대해서 사용
        http.addFilterBefore(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        // [STEP5] Session 기반의 인증기반을 사용하지 않고 추후 JWT를 이용하여서 인증 예정
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // [STEP6] Spring Security JWT Filter Load
//        http.addFilterBefore(jwtAuthorizationFilter(), BasicAuthenticationFilter.class);

        // [STEP7] 최종 구성한 값을 사용함.
        return http.build();
    }


    /**
     * 3. authenticate 의 인증 메서드를 제공하는 매니져로'Provider'의 인터페이스를 의미합니다.
     * - 과정: CustomAuthenticationFilter → AuthenticationManager(interface) → CustomAuthenticationProvider(implements)
     *
     * @return AuthenticationManager
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(customAuthenticationProvider());
    }

    /**
     * 4. '인증' 제공자로 사용자의 이름과 비밀번호가 요구됩니다.
     * - 과정: CustomAuthenticationFilter → AuthenticationManager(interface) → CustomAuthenticationProvider(implements)
     *
     * @return CustomAuthenticationProvider
     */
    @Bean
    public CustomAuthenticationProvider customAuthenticationProvider() {
        return new CustomAuthenticationProvider(bCryptPasswordEncoder());
    }

    /**
     * 5. 비밀번호를 암호화하기 위한 BCrypt 인코딩을 통하여 비밀번호에 대한 암호화를 수행합니다.
     *
     * @return BCryptPasswordEncoder
     */
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 6. 커스텀을 수행한 '인증' 필터로 접근 URL, 데이터 전달방식(form) 등 인증 과정 및 인증 후 처리에 대한 설정을 구성하는 메서드입니다.
     *
     * @return CustomAuthenticationFilter
     */
    @Bean
    public CustomAuthenticationFilter customAuthenticationFilter() {
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManager());
        customAuthenticationFilter.setFilterProcessesUrl("/api/v1/user/login");     // 접근 URL
        customAuthenticationFilter.setAuthenticationSuccessHandler(customLoginSuccessHandler());    // '인증' 성공 시 해당 핸들러로 처리를 전가한다.
        customAuthenticationFilter.setAuthenticationFailureHandler(customLoginFailureHandler());    // '인증' 실패 시 해당 핸들러로 처리를 전가한다.
        customAuthenticationFilter.afterPropertiesSet();
        return customAuthenticationFilter;
    }

    /**
     * 7. Spring Security 기반의 사용자의 정보가 맞을 경우 수행이 되며 결과값을 리턴해주는 Handler
     *
     * @return CustomLoginSuccessHandler
     */
    @Bean
    public CustomAuthSuccessHandler customLoginSuccessHandler() {
        return new CustomAuthSuccessHandler();
    }

    /**
     * 8. Spring Security 기반의 사용자의 정보가 맞지 않을 경우 수행이 되며 결과값을 리턴해주는 Handler
     *
     * @return CustomAuthFailureHandler
     */
    @Bean
    public CustomAuthFailureHandler customLoginFailureHandler() {
        return new CustomAuthFailureHandler();
    }


    /**
     * 9. JWT 토큰을 통하여서 사용자를 인증합니다.
     *
     * @return JwtAuthorizationFilter
     */
//    @Bean
//    public JwtAuthorizationFilter jwtAuthorizationFilter() {
//        return new JwtAuthorizationFilter();
//    }


}

 

 

2. CustomAuthenticationFilter.java


💡CustomAuthenticationFilter.java

- 해당 메서드 내에서 AuthenticationManager를 호출하여 전달합니다.

- 인증 성공 시 CustomAuthSuccessHandler를 호출하고, 실패 시 CustomAuthFailureHandler를 호출합니다.
-  커스텀을 수행한 '인증' 필터로 접근 URL, 데이터 전달 방식(form) 등 인증 과정 및 인증 후 처리에 대한 설정을 구성하는 메서드입니다.
-  최종 인증이 완료되면 사용자 아이디와 비밀번호 기반으로 토큰을 발급합니다.
package com.adjh.multiflexapi.config.filter;

import com.adjh.multiflexapi.model.UserDto;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
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.UsernameNotFoundException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


/**
 * 아이디와 비밀번호 기반의 데이터를 Form 데이터로 전송을 받아 '인증'을 담당하는 필터입니다.
 */
@Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    /**
     * 지정된 URL로 form 전송을 하였을 경우 파라미터 정보를 가져온다.
     *
     * @param request  from which to extract parameters and perform the authentication
     * @param response the response, which may be needed if the implementation has to do a
     *                 redirect as part of a multi-stage authentication process (such as OpenID).
     * @return Authentication {}
     * @throws AuthenticationException {}
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authRequest;
        try {
            authRequest = getAuthRequest(request);
            setDetails(request, authRequest);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return this.getAuthenticationManager().authenticate(authRequest);
    }


    /**
     * Request로 받은 ID와 패스워드 기반으로 토큰을 발급한다.
     *
     * @param request HttpServletRequest
     * @return UsernamePasswordAuthenticationToken
     * @throws Exception e
     */
    private UsernamePasswordAuthenticationToken getAuthRequest(HttpServletRequest request) throws Exception {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
            UserDto user = objectMapper.readValue(request.getInputStream(), UserDto.class);
            log.debug("1.CustomAuthenticationFilter :: userId:" + user.getUserId() + " userPw:" + user.getUserPw());

            // ID와 패스워드를 기반으로 토큰 발급
            return new UsernamePasswordAuthenticationToken(user.getUserId(), user.getUserPw());
        } catch (UsernameNotFoundException ae) {
            throw new UsernameNotFoundException(ae.getMessage());
        } catch (Exception e) {
            throw new Exception(e.getMessage(), e.getCause());
        }

    }

}

 

 

 

3. CustomAuthenticationProvider.java


💡CustomAuthenticationProvider.java

- 전달받은 사용자의 아이디와 비밀번호를 기반으로 비즈니스 로직을 처리하여 사용자의 ‘인증’에 대해서 검증을 수행하는 클래스입니다.

- CustomAuthenticationFilter로부터 생성한 토큰을 통하여 ‘UserDetailsService’를 통해 데이터베이스에서 정보를 조회합니다.
package com.adjh.multiflexapi.config.handler;

import com.adjh.multiflexapi.model.UserDetailsDto;
import com.adjh.multiflexapi.model.UserDto;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import javax.annotation.Resource;


/**
 * 전달받은 사용자의 아이디와 비밀번호를 기반으로 비즈니스 로직을 처리하여 사용자의 ‘인증’에 대해서 검증을 수행하는 클래스입니다.
 * CustomAuthenticationFilter로 부터 생성한 토큰을 통하여 ‘UserDetailsService’를 통해 데이터베이스 내에서 정보를 조회합니다.
 */
@Slf4j
@RequiredArgsConstructor
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Resource
    private UserDetailsService userDetailsService;

    @NonNull
    private BCryptPasswordEncoder passwordEncoder;


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        log.debug("2.CustomAuthenticationProvider");

        UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;

        // 'AuthenticaionFilter' 에서 생성된 토큰으로부터 아이디와 비밀번호를 조회함
        String userId = token.getName();
        String userPw = (String) token.getCredentials();

        // Spring Security - UserDetailsService를 통해 DB에서 아이디로 사용자 조회
        UserDetailsDto userDetailsDto = (UserDetailsDto) userDetailsService.loadUserByUsername(userId);

        if (!(userDetailsDto.getUserPw().equalsIgnoreCase(userPw) && userDetailsDto.getUserPw().equalsIgnoreCase(userPw))) {
            throw new BadCredentialsException(userDetailsDto.getUserNm() + "Invalid password");
        }
        return new UsernamePasswordAuthenticationToken(userDetailsDto, userPw, userDetailsDto.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }


}

 

 

 

4. CustomAuthSuccessHandler.java


💡CustomAuthSuccessHandler.java

- 사용자의 ‘인증’에 대해 성공하였을 경우, 수행되는 Handler로 성공에 대한 사용자에게 반환 값을 구성하여 전달하는 클래스입니다.
package com.adjh.multiflexapi.config.handler;

import com.adjh.multiflexapi.common.utils.ConvertUtil;
import com.adjh.multiflexapi.model.UserDetailsDto;
import com.adjh.multiflexapi.model.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

/**
 * 사용자의 ‘인증’에 대해 성공하였을 경우 수행되는 Handler로 성공에 대한 사용자에게 반환값을 구성하여 전달합니다
 */
@Slf4j
@Configuration
public class CustomAuthSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException {
        log.debug("3.CustomLoginSuccessHandler");

        // [STEP1] 사용자와 관련된 정보를 모두 조회합니다.
        UserDto userDto = ((UserDetailsDto) authentication.getPrincipal()).getUserDto();

        // [STEP2] 조회한 데이터를 JSONObject 형태로 파싱을 수행합니다.
        JSONObject userVoObj = (JSONObject) ConvertUtil.convertObjectToJsonObject(userDto);


        HashMap<String, Object> responseMap = new HashMap<>();

        JSONObject jsonObject;

        // [STEP3-1] 사용자의 상태가 '휴면 상태' 인 경우 응답 값으로 전달 할 데이터
        if (userDto.getUserSt().equals("D")) {
            responseMap.put("userInfo", userVoObj);
            responseMap.put("resultCode", 9001);
            responseMap.put("token", null);
            responseMap.put("failMsg", "휴면 계정입니다.");
            jsonObject = new JSONObject(responseMap);
        }

        // [STEP3-2] 사용자의 상태가 '휴면 상태'가 아닌 경우 응답 값으로 전달 할 데이터
        else {
            // 1. 일반 계정일 경우 데이터 세팅
            responseMap.put("userInfo", userVoObj);
            responseMap.put("resultCode", 200);
            responseMap.put("failMsg", null);
            jsonObject = new JSONObject(responseMap);

            // TODO: 추후 JWT 발급에 사용 할 예정
            // String token = TokenUtils.generateJwtToken(userVo);
            // jsonObject.put("token", token);
            // response.addHeader(AuthConstants.AUTH_HEADER, AuthConstants.TOKEN_TYPE + " " + token);
        }

        // [STEP4] 구성한 응답 값을 전달합니다.
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();
        printWriter.print(jsonObject);  // 최종 저장된 '사용자 정보', '사이트 정보' Front 전달
        printWriter.flush();
        printWriter.close();
    }
}

 

 

5. CustomAuthFailureHandler.java


💡CustomAuthFailureHandler.java

- 사용자의 ‘인증’에 대해 실패하였을 경우, 수행되는 Handler로 실패에 대한 사용자에게 반환값을 구성하여 전달하는 클래스입니다.
package com.adjh.multiflexapi.config.handler;

import lombok.extern.slf4j.Slf4j;
import org.json.simple.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;

/**
 * 사용자의 ‘인증’에 대해 실패하였을 경우 수행되는 Handler로 실패에 대한 사용자에게 반환값을 구성하여 전달합니다.
 */
@Slf4j
@Configuration
public class CustomAuthFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {

        // [STEP1] 클라이언트로 전달 할 응답 값을 구성합니다.
        JSONObject jsonObject = new JSONObject();
        String failMsg = "";

        // [STEP2] 발생한 Exception 에 대해서 확인합니다.
        if (exception instanceof AuthenticationServiceException) {
            failMsg = "로그인 정보가 일치하지 않습니다.";

        } else if (exception instanceof BadCredentialsException) {
            failMsg = "로그인 정보가 일치하지 않습니다.";

        } else if (exception instanceof LockedException) {
            failMsg = "로그인 정보가 일치하지 않습니다.";

        } else if (exception instanceof DisabledException) {
            failMsg = "로그인 정보가 일치하지 않습니다.";

        } else if (exception instanceof AccountExpiredException) {
            failMsg = "로그인 정보가 일치하지 않습니다.";

        } else if (exception instanceof CredentialsExpiredException) {
            failMsg = "로그인 정보가 일치하지 않습니다.";
        }
        // [STEP4] 응답 값을 구성하고 전달합니다.
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        PrintWriter printWriter = response.getWriter();

        log.debug(failMsg);

        HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("userInfo", null);
        resultMap.put("resultCode", 9999);
        resultMap.put("failMsg", failMsg);
        jsonObject = new JSONObject(resultMap);

        printWriter.print(jsonObject);
        printWriter.flush();
        printWriter.close();
    }
}

 

 

 

5) 작업 테스트 하기 


1. DB 데이터 확인


💡 tb_user 테이블에 user_id로 "admin"과 user_pw로 "1"을 지정한 데이터를 작성하였습니다.

 

 

 

2. URL 호출하기


💡 CustomAuthenticationFilter내에 setFilterProcessesUrl로 지정한 값으로 호출을 해봅니다.

 

 

 

3. 데이터 확인


3.1. 사용자 정보를 인증하지 못한 경우 

💡 데이터 값이 존재하지 않는 경우 (로그인 정보가 존재하지 않는 경우)

[ Spring Security 수행과정 ]

1. 로그 상으로 CustomAuthenticationFilter의 수행을 확인하였습니다.
2. 로그 상으로 CustomAuthenticationProvider를 수행하였으나 데이터가 존재하지 않아서 CustomAuthFailureHandler로 이동하여 에러 메시지를 발생시켰습니다.

 

 

 

3.2. 사용자 정보를 인증한 경우 

💡 데이터 값이 존재하는 경우 -> 사용자 정보를 인증한 경우 

[ Spring Security 수행과정 ]

1. 로그 상으로 CustomAuthenticationFilter의 수행을 확인하였습니다.
2. 로그 상으로 CustomAuthenticationProvider를 수행하여 데이터 조회를 수행을 확인하였습니다.
3. 로그 상으로 CustomLoginSuccessHandler를 잘 수행하여 응답 값을 반환받음을 확인하였습니다.

 

 

 

[참고] Spring Security의 인증 방법으로 JWT를 이용한 인증 방법에 대한 이해에 대해서 공유드립니다.
 

[Java] Spring Boot Security 이해하기 -3: JWT(JSON Web Token) 이해하기

해당 글에서는 Spring Security의 인증을 위한 ‘JWT: JSON Web Token’를 이해하고 적용하기 위해 우선 이해를 목적으로 작성한 글입니다. 추후 적용을 위한 환경 설정 방법에 대해서 공유합니다. [참고]

adjh54.tistory.com

 

[참고] Spring Security의 인증 방법인 JWT를 이용한 실제 환경설정 방법에 대해서 공유드립니다.
 

[Java] Spring Boot Security 이해하기 -4: JWT 환경 설정 및 구성 하기

해당 글에서는 Spring Boot Security내에 ‘인증’ 방식을 JWT를 이용하여서 사용자의 인증을 구성하는 환경 설정방법에 대해서 이해하기 위한 글입니다. [참고] 해당 글은 이전에 작성한 'JWT 이론'에

adjh54.tistory.com

 

 

[참고] 해당 글을 기반으로 구성한 Git Repository입니다. JWT를 추가적으로 적용하여 구성하였습니다.
 

blog-codes/spring-boot2-security at main · adjh54ir/blog-codes

Contributor9 티스토리 블로그 내에서 활용한 내용들을 담은 레포지토리입니다. Contribute to adjh54ir/blog-codes development by creating an account on GitHub.

github.com

 

 

 

 

 

 

오늘도 감사합니다 😄

 

 

 

 

 

 

 

그리드형