반응형
반응형
해당 글에서는 이전에 Spring Security 5.7.x 버전에 대해 이해한 내용을 기반으로 실제 구현하는 방법에 대해서 공유합니다.
💡 [참고] Spring Boot Security를 적용하기 이전의 이해하기 위한 글을 참고하시면 좋습니다.
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를 이용한 인증 방법에 대한 이해에 대해서 공유드립니다.
[참고] Spring Security의 인증 방법인 JWT를 이용한 실제 환경설정 방법에 대해서 공유드립니다.
[참고] 해당 글을 기반으로 구성한 Git Repository입니다. JWT를 추가적으로 적용하여 구성하였습니다.
오늘도 감사합니다 😄
반응형
'Java > Spring Boot' 카테고리의 다른 글
[Java] Spring Boot Security 이해하기 -4: JWT 환경 설정 및 구성 하기 (4) | 2023.01.01 |
---|---|
[Java] Spring Boot Security 이해하기 -3: JWT(JSON Web Token) 이해하기 (0) | 2022.12.21 |
[Java] Spring Boot Security 이해하기 -1 : 2.7.x 버전 구조 및 파일 이해 (0) | 2022.12.18 |
[Java] Business Exception 이해하고 구성하기 : Service Exception (0) | 2022.12.10 |
[Java] Global Exception 이해하고 구성하기 : Controller Exception (5) | 2022.11.13 |