회원가입

hyuko·2023년 5월 7일
0

팀 프로젝트

목록 보기
3/8

1. 개요

오늘 우리는 Spring Boot 및 React를 이용하여 사용자 인증을 구현하는 방법을 살펴보겠습니다. 이 포스트에서는, 우리는 회원가입 기능을 구현하고, 중복 이메일을 체크하며, JWT 토큰을 사용하여 사용자 인증을 할 예정입니다.

2. 백엔드: Spring Boot

우리의 백엔드는 Spring Boot를 기반으로 하며, 이는 주로 AuthController와 AuthService 클래스를 사용합니다.

2.1. AuthController

AuthController 클래스는 회원가입에 필요한 요청을 처리합니다. POST 요청으로 사용자 정보를 받아오고, 중복 이메일 확인과 회원가입을 위해 AuthService를 호출합니다.

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {

	
	private final AuthService authService;
    private final JwtTokenProvider jwtTokenProvider;
	
	@ValidAspect
    @PostMapping("/user")
    public ResponseEntity<?> signup(@Valid @RequestBody UserReqDto userReqDto, BindingResult bindingResult) {

		authService.checkDuplicatedByEmail(userReqDto.getEmail());
		authService.signup(userReqDto);
        return ResponseEntity.ok(DataRespDto.ofDefault());
    }
}

2.2. AuthService

AuthService는 사용자 인증 서비스를 제공합니다. 사용자의 이메일이 이미 사용 중인지 체크하고 사용자 등록을 처리합니다.

@Service
@RequiredArgsConstructor
public class AuthService implements UserDetailsService, OAuth2UserService<OAuth2UserRequest, OAuth2User> {
	
	private final AuthRepository authRepository;
	private final UserRepository userRepository;
	private final AuthenticationManagerBuilder authenticationManagerBuilder;
	private final JwtTokenProvider jwtTokenProvider;
	
	public void checkDuplicatedByEmail(String email) {
		User userEntity = authRepository.findUserByEmail(email);
		
		if(userEntity != null) {
			throw new CustomException("Duplicated Email", 
					ErrorMap.builder()
					.put("email","이미 사용중인 이메일입니다.").build());
		}
	}
	
	public void signup(UserReqDto userReqDto) {
		User userEntity = userReqDto.toEntity();

		authRepository.saveUser(userEntity);
		
		authRepository.saveAuthority(
				Authority.builder().userId(userEntity.getUserId()).roleId(1).build());

	}
}

2.3. MyBatis Mapper

MyBatis를 사용하여 DB 쿼리를 처리합니다. 이메일을 이용한 사용자 조회, 사용자 정보 저장, 권한 저장 등의 쿼리를 처리합니다.

<?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.korea.triplocation.repository.AuthRepository">


	<resultMap type="com.korea.triplocation.domain.user.entity.User" id="userMap">
		<id property="userId" column="user_id"/>
		<result property="email" column="email"/>
		<result property="password" column="password"/>
		<result property="name" column="name"/>
		<result property="phone" column="phone"/>
		<result property="address" column="address"/>
		<result property="postsImgId" column="posts_img_id"/>
		<result property="createDate" column="create_date"/>
		<collection property="authorities"  javaType="list" resultMap="authorityMap" />
	</resultMap>
	
	<resultMap type="com.korea.triplocation.domain.user.entity.Role" id="roleMap">
		<id property="roleId" column="role_id"/>
		<result property="roleName" column="role_name"/>
	</resultMap>
	
	<resultMap type="com.korea.triplocation.domain.user.entity.Authority" id="authorityMap">
		<id property="authorityId" column="authority_id"/>
		<result property="userId" column="user_id"/>
		<result property="roleId" column="role_id"/>
		<association property="role" resultMap="roleMap" />
	</resultMap>

	<select id="findUserByEmail" resultMap="userMap">
		select
			ut.user_id,
			ut.email,
			ut.password,
			ut.name,
			ut.phone,
			ut.address,
			ut.posts_img_id,
			ut.create_date,
			
			at.authority_id,
			at.user_id,
			at.role_id,
			
			rt.role_id,
			rt.role_name
		from
			user_tb ut
			left outer join authority_tb at on(at.user_id = ut.user_id)
			left outer join role_tb rt on(rt.role_id = at.role_id)
		where
			ut.email = #{email}
			
			
	</select>


	<insert id="saveUser" parameterType="com.korea.triplocation.domain.user.entity.User" useGeneratedKeys="true" keyProperty="userId">
		<choose>
			<when test="provider != null">
-- 조건에 따라 회원가입 진행 하는 로직입니다 provider가 null 값이 아니면 oauth2의 회원가입에 대한 쿼리
				insert into user_tb (email, password, name, phone, address, posts_img_id, create_date, provider)
				values (#{email}, #{password}, #{name}, #{phone}, #{address}, #{postsImgId}, now(), #{provider})
			</when>
			<otherwise>
				insert into user_tb (email, password, name, phone, address, posts_img_id, create_date)
				values (#{email}, #{password}, #{name}, #{phone}, #{address}, #{postsImgId}, now())
			</otherwise>
		</choose>
	</insert>
	
	<insert id="saveAuthority" parameterType="com.korea.triplocation.domain.user.entity.Authority">
		insert into authority_tb
		values (0, #{userId}, #{roleId})
	</insert>

	<update id="updateProvider" parameterType="com.korea.triplocation.domain.user.entity.User">
		UPDATE user_tb
		SET
			provider = #{provider}
		WHERE
			user_id = #{userId}
	</update>
	

</mapper>

2.4. JWT 관련 클래스

우리의 인증 시스템은 JSON Web Token(JWT)를 이용합니다.
JWT는 두 개체 사이에서 정보를 안전하게 전송하기 위한 컴팩트하고 독립적인 방법입니다.

2.4.1. JwtTokenProvider

JwtTokenProvider 클래스는 JWT를 생성하고 검증하는 역할을 담당합니다. 이 클래스에서는 토큰 생성, 토큰 유효성 검사, 토큰에서 인증 정보 추출 등의 메소드를 구현하고 있습니다.


@Component
@Slf4j
public class JwtTokenProvider {
	
	private final Key key;

	public JwtTokenProvider(@Value("${jwt.secret}")  String secretKey) {
		key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
	}
	
	public JwtRespDto generateToken(Authentication authentication) {
		
		StringBuilder builder = new StringBuilder();
		
		authentication.getAuthorities().forEach(authority -> {
			builder.append(authority.getAuthority() + ",");
		});
		builder.delete(builder.length() - 1 , builder.length());
		
		String authorities = builder.toString();
		
		Date tokenExpireDate = new Date(new Date().getTime() + (1000 * 60 * 60 * 24));		//프로젝트 완성후 만료시간 수정하기
		
		String accessToken = Jwts.builder()
				.setSubject(authentication.getName())
				.claim("auth", authorities)
				.setExpiration(tokenExpireDate)
				.signWith(key, SignatureAlgorithm.HS256)
				.compact();

		
		return	JwtRespDto.builder().grantType("Bearer").accessToken(accessToken).build(); 
	}
	
	public boolean validateToken(String token) {
		
		try {
			Jwts.parserBuilder()
					.setSigningKey(key)
					.build()
					.parseClaimsJws(token);
			
			return true;
		} catch (SecurityException | MalformedJwtException e) {	
//			log.info("Invalid JWT Token", e);
			
		} catch (ExpiredJwtException e) {
//			log.info("Expired JWT Token", e);
			
		} catch (UnsupportedJwtException e) {
//			log.info("Unsupported JWT Token", e);
			
		} catch (IllegalArgumentException e) {
//			log.info("IllegalArgument JWT Token", e);
			
		} catch (Exception e) {
//			log.info("JWT Token Error", e);
		}
		
		return false;
					
	}
	
	public String getToken(String token) {
		String type = "Bearer ";
		if(StringUtils.hasText(token) && token.startsWith(type)) {
			return token.substring(type.length());
		}
		return null;	
	}
	
	public Claims getClaims(String token) {
		return Jwts.parserBuilder()
					.setSigningKey(key)
					.build()
					.parseClaimsJws(token)
					.getBody();
	}
	
	public Authentication getAuthentication(String accessToken) {
		Authentication authentication = null;
		
		Claims claims = getClaims(accessToken);
		if(claims.get("auth") == null) {
			throw new CustomException("AccessToken에 관한 정보 없음!");
		}
		
		List<SimpleGrantedAuthority> authorities = new ArrayList<>();
		
		String auth = claims.get("auth").toString();
		for(String role : auth.split(",")) {
			authorities.add(new SimpleGrantedAuthority(role));
		}
		
		UserDetails userDetails = new User(claims.getSubject(), "", authorities);
		
		authentication = new UsernamePasswordAuthenticationToken(userDetails, null, authorities);
		
		return authentication;
	}

	public String generateOAuth2RegisterToken(Authentication authentication) {
		Date tokenExpiresDate = new Date(new Date().getTime() + (1000 * 60 * 10));

		OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
		String email = oAuth2User.getAttribute("email");

		return Jwts.builder()
				.setSubject("OAuth2Register")
				.claim("email", email)
				.setExpiration(tokenExpiresDate)
				.signWith(key, SignatureAlgorithm.HS256)
				.compact();

	}

	public String generateAccessToken(Authentication authentication) {
		String email = null;

		if(authentication.getPrincipal().getClass() == UserDetails.class) {
			// Principal User
			PrincipalUser principalUser = (PrincipalUser) authentication.getPrincipal();
			email = principalUser.getEmail();
		}else {
			// Oauth2 User
			OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
			email = oAuth2User.getAttribute("email");
		}

		if(authentication.getAuthorities() == null) {
			throw new RuntimeException("등록된 권한이 없습니다.");
		}
		StringBuilder roles = new StringBuilder();


		authentication.getAuthorities().forEach(authority -> {
			roles.append(authority.getAuthority() + ",");
		});

		roles.delete(roles.length() - 1, roles.length());
		Date tokenExpiresDate = new Date(new Date().getTime() + (1000 * 60 * 60 * 24));



		String accessToken = "Bearer "+ Jwts.builder()
							.setSubject(authentication.getName())
							.claim("auth", roles)
							.setExpiration(tokenExpiresDate)
							.signWith(key, SignatureAlgorithm.HS256)
							.compact();

		return accessToken;

	}
}

이 클래스에서 사용되는 주요 메소드들은 다음과 같습니다:

  • generateToken(Authentication authentication): 인증 객체를 인수로 받아 JWT를 생성하고 반환합니다.
  • validateToken(String token): 문자열 형태의 토큰을 인수로 받아 토큰의 유효성을 검증합니다.
  • getToken(String token): "Bearer"로 시작하는 토큰을 인수로 받아 실제 토큰을 추출하여 반환합니다.
  • getClaims(String token): 토큰을 인수로 받아 클레임(Claims)을 추출하여 반환합니다.
  • getAuthentication(String accessToken): 토큰을 인수로 받아 인증 객체를 반환합니다.

2.4.2. JwtAuthenticationFilter

JwtAuthenticationFilter 클래스는 HTTP 요청에서 JWT를 검증하는 역할을 담당합니다. 이 클래스는 Servlet Filter로서, 모든 요청에 대해 동작하여 JWT를 확인하고 해당 사용자를 인증합니다.

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilterBean{
	
	private final JwtTokenProvider jwtTokenProvider;
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		String accessToken = httpRequest.getHeader("Authorization");
		accessToken = jwtTokenProvider.getToken(accessToken);
		boolean validationFlag = jwtTokenProvider.validateToken(accessToken);
		
		if(validationFlag) {
			Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
			SecurityContextHolder.getContext().setAuthentication(authentication);
		}
		chain.doFilter(request, response);
		
	}

2.4.3. JwtAuthenticationEntryPoint

JwtAuthenticationEntryPoint 클래스는 인증되지 않은 사용자가 보호된 자원에 액세스하려 할 때 동작합니다. 이 클래스는 인증 실패시 발생하는 예외를 처리하여 적절한 응답을 반환합니다.

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {
		
		response.setContentType(MediaType.APPLICATION_JSON_VALUE);
		response.setStatus(HttpStatus.UNAUTHORIZED.value());
		ErrorRespDto<?> errorResponseDto = 
					new ErrorRespDto<AuthenticationException>(HttpStatus.INTERNAL_SERVER_ERROR, authException);		//임시
		ObjectMapper objectMapper = new ObjectMapper();
		
		String responseJson = objectMapper.writeValueAsString(errorResponseDto);
		
		PrintWriter out = response.getWriter();
		out.println(responseJson);
	}

}

2.4.4. PrincipalUser

PrincipalUser 클래스는 Spring Security에서 사용하는 사용자의 정보를 담는 객체입니다. 이 클래스는 UserDetails 인터페이스를 구현하여 사용자의 권한 정보와 함께 기본적인 사용자 정보를 저장합니다.

@Builder
@Data
public class PrincipalUser implements UserDetails {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -6984381303716862634L;
	private int userId;
	private String email;
	private String password;
	private int postsImgId;
	private List<Authority> authorities;
	
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		List<SimpleGrantedAuthority> authorities = new ArrayList<>();
		
		this.authorities.forEach(authority -> {
			authorities.add(new SimpleGrantedAuthority(authority.getRole().getRoleName()));
		});
		
		return authorities;
	}

	@Override
	public String getPassword() {
		return password;
	}

	@Override
	public String getUsername() {
		return email;
	}

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

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

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

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

}

2.5. 도메인 엔티티

2.5.1 User

User 클래스는 사용자 정보를 표현하는 엔티티입니다. 사용자 ID, 이메일, 비밀번호, 이름, 전화번호, 주소, 게시글 이미지 ID, 제공자 정보 및 계정 생성 날짜 등의 필드를 포함하고 있습니다.

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
	private int userId;
//	role_id => roleId 수정
	private String email;
	private String password;
	private String name;
	private String phone;
	private String address;
	private int postsImgId;
	private String provider;

	private LocalDate createDate;	// 계정 생성 일자
	
	private List<Authority> authorities;
	
	public PrincipalUser toPrincipal() {
		PostsImg postsImg = new PostsImg();
		return PrincipalUser.builder()
				.userId(userId)
				.email(email)
				.password(password)
				.postsImgId(postsImg.getPostsImgId())
				.authorities(authorities)
				.build();
	}
	
	public UserRespDto toDto() {
		PostsImg postsImg = new PostsImg();	
		return UserRespDto.builder()
				.userId(userId)
				.email(email)
				.name(name)
				.phone(phone)
				.address(address)
				.postsImgId(postsImg.getPostsImgId())
				.build();
	}
	
	
	
}

2.5.2 Role

Role 클래스는 사용자의 권한을 표현하는 엔티티입니다. 각 사용자는 여러 권한을 가질 수 있으며, 권한은 사용자가 수행할 수 있는 작업을 결정합니다.

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
	private int roleId;
	private String roleName;

}

2.5.3 Authority

Authority 클래스는 사용자의 권한을 나타내는 엔티티입니다. 각 사용자는 여러 권한을 가질 수 있으며, 이 클래스는 사용자 ID와 권한 ID를 연결하는 역할을 합니다. 권한 정보는 별도의 Role 엔티티에서 관리합니다.

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Authority {
	
	private int authorityId;
	private int userId;
	private int roleId;
	
	private Role role;

}

2.5.4 PostsImg

PostsImg 클래스는 게시글 이미지 정보를 표현하는 엔티티입니다. 이 엔티티는 사용자가 게시글에 업로드한 이미지에 대한 정보를 저장합니다. 이 정보에는 이미지의 원본 이름, 임시 이름, 크기 등이 포함됩니다.

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PostsImg {
    private int postsImgId;
    private int userId;
    private String originName;
    private String tempName;
    private String imgSize;

}

각 클래스는 특정 작업을 수행하기 위한 메소드를 제공합니다. 예를 들어, User 클래스는 toPrincipal() 메소드를 통해 Spring Security의 PrincipalUser 객체로 변환할 수 있고, toDto() 메소드를 통해 응답 DTO로 변환할 수 있습니다. 이러한 메소드는 서비스 계층에서 해당 엔티티를 다루는 데 사용됩니다.

DB


3. 프론트엔드: React

프론트엔드에서는 React와 Recoil을 사용하여 사용자 인증을 관리합니다.

3.1. SignUp Component

SignUp 컴포넌트는 사용자로부터 입력 받은 정보를 이용해 회원가입 요청을 보냅니다. 각 필드의 유효성 검사 후, axios.post를 이용해 서버로 회원가입 요청을 보냅니다.

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { Box, Button, FormControl, Grid, InputLabel, MenuItem, Select, TextField, Typography } from '@mui/material';
import axios from 'axios';
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {useMutation} from "react-query";


const signupContainer = css`
    display: flex;
    align-items: center;
    justify-content: center;
    height: 800px;
`
;
const signupBox = css`
    width: 500px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    margin-top: 8px;
`;

const signupText = css`
    margin-top: 80px;
    margin-bottom: 30px;
`;

const inputContainer = css`
    width: 500px;
`;

const StyleInput = styled(TextField)`
    margin-top: 10px;
    margin-bottom: 10px;
    width: 100%;
`;

const addressForm = css`
    width: 100%;
    margin-top: 15px;
`;

const errorMsg = css`
    margin-left: 5px;
    margin-bottom: 20px;
    font-size: 12px;
    color: red;
`;

const submitButton = css`
    height: 45px;
    margin-top: 30px;
    margin-bottom: 20px;

    background-color: #0BD0AF;
    color: white;

    font-size: 15px;
    
    &:hover {
        background-color: #0BAF94;
    }

    &:active {
        background-color: #40D6BD;
    }
`;

const address = [
    "서울특별시",
    "부산광역시",
    "인천광역시",
    "대구광역시",
    "대전광역시",
    "광주광역시",
    "울산광역시",
    "세종특별자치시",
    "경기도",
    "강원도",
    "충청북도",
    "충청남도",
    "전라북도",
    "전라남도",
    "경상북도",
    "경상남도",
    "제주특별자치도"
];

const SignUp = () => {
    const navigate = useNavigate();
    const [ signupUser, setSignupUser ] = useState({
        profileImgPath: '',
        email: '',
        password: '',
        name: '',
        phone: '',
        address: ''
    });

    const [ errorMessages, setErrorMessages ] = useState({
        profileImgPath: '',
        email: '',
        password: '',
        name: '',
        phone: '',
        address: ''
    });
      
    // 로그인
    const onChangeHandler = (e) => {
        const { name, value } = e.target;
        setSignupUser(
            {
                ...signupUser,
                [name]: value
            }
        )
    };



    const signupHandleSubmit = async () => {
        try {
            const option = {
                headers: {
                    'Content-Type': 'application/json'
                }
            }

            const response = await axios.post(`http://localhost:8080/api/v1/auth/user`, signupUser, option);
            setErrorMessages({
                profileImgPath: '',
                email: '',
                password: '',
                name: '',
                phone: '',
                address: ''});

            const accessToken = response.data.grantType + " " + response.data.accessToken;
            localStorage.setItem("accessToken", accessToken);
            alert("회원가입 완료");
            window.location.replace("/auth/login");
            return response;
        }catch (error) {
            setErrorMessages(error.response.data);
        }

    }

    return (
        <Grid component="main" maxWidth="xs" css={signupContainer}>

            <Box css={signupBox}>
                <Typography component="h1" variant="h5" css={signupText}>
                    Sign Up
                </Typography>


                <Box component="form" css={inputContainer}>
                    <StyleInput
                        required
                        id="email"
                        label="이메일"
                        placeholder="abc@gmail.com"
                        name="email"
                        autoComplete="email"
                        onChange={onChangeHandler}
                        autoFocus
                        />
                    <div css={errorMsg}>{errorMessages.email}</div>

                    <StyleInput
                        required
                        id="password"
                        label="비밀번호"
                        name="password"
                        type="password"
                        autoComplete="current-password"
                        onChange={onChangeHandler}
                    />
                    <div css={errorMsg}>{errorMessages.password}</div>

                    <StyleInput
                        required
                        id="name"
                        label="이름"
                        name="name"
                        autoComplete="name"
                        onChange={onChangeHandler}
                    />
                    <div css={errorMsg}>{errorMessages.name}</div>

                    <StyleInput
                        required
                        id="phone"
                        label="연락처"
                        placeholder="010-1234-1234"
                        name="phone"
                        autoComplete="tel"
                        onChange={onChangeHandler}
                    />
                    <div css={errorMsg}>{errorMessages.phone}</div>

                    <Box>
                        <FormControl css={addressForm}>
                            <InputLabel id="addressSelectLabel">주소</InputLabel>
                            <Select
                                required
                                labelId="addressSelectLabel"
                                id="address"
                                value={signupUser.address}
                                label="주소"
                                onChange={(event) => setSignupUser({...signupUser, address: event.target.value})}
                            >
                                {address.map((item) => (
                                    <MenuItem key={item} value={item}>
                                        {item}
                                    </MenuItem>
                                ))}

                            </Select>
                        </FormControl>
                    </Box>
                    <div css={errorMsg}>{errorMessages.address}</div>

                    <Button css={submitButton}
                        type='button'
                        onClick={signupHandleSubmit}
                        fullWidth
                        variant="contained"
                        sx={{ mt: 3, mb: 2 }}
                        >
                        Sign Up
                    </Button>
                </Box>
            </Box>
            
        </Grid>
    );
};

export default SignUp;

3.2. AuthRouter Component

AuthRouter 컴포넌트는 사용자의 로그인 상태를 체크하여 인증이 필요한 페이지에 대한 접근을 제어합니다. Recoil을 사용하여 전역 상태를 관리하고, react-query를 이용하여 서버와의 통신을 관리합니다.


import React, {useEffect} from 'react';
import {useRecoilState} from "recoil";
import {useQuery} from "react-query";
import axios from "axios";
import {useNavigate} from "react-router-dom";
import {authenticationState} from "../../../store/atoms/AuthAtoms";

const AuthRouter = ({ path, element }) => {
    const navigate = useNavigate();
    const [authState, setAuthState] = useRecoilState(authenticationState);

    const authenticated = useQuery(["authenticated"], async () => {
        const option = {
            headers: {
                "Authorization": `${localStorage.getItem("accessToken")}`
            }
        }
        return await axios.get('http://localhost:8080/api/v1/auth/authenticated', option)
    }, {
        onSuccess: (response) => {
            if (response.status === 200) {
                if(response.data) {
                    setAuthState(true);
                }
            }
        },
        // onError: () => {
        //     setAuthState(false);
        //     alert("로그인이 필요한 페이지입니다.")
        //     navigate("/auth/login")
        // }
    });

    useEffect(() => {

        if(authenticated.isSuccess) {
            const authenticatedPaths = ['/user', '/contents']
            const authPath = '/auth';
            console.log(authState)
            if(authState && path.startsWith(authPath)) {
                navigate("/");
            }

            if(!authState && authenticatedPaths.filter(authenticatedPath => path.startsWith(authenticatedPath)).length > 0) {
                alert("로그인이 필요한 페이지입니다.")
                navigate("/auth/login");
            }
        }
    }, [authState, authenticated.isSuccess, path, navigate])

    if (authenticated.isLoading) {
        return <></>
    }

    return element;

};

export default AuthRouter;

다음으로..

Spring Boot와 React를 이용하여 사용자 인증 시스템을 구현하는 방법을 살펴보았습니다.
중복 이메일 체크, 회원가입, 그리고 인증 상태 관리 등 기본적인 인증 시스템을 구현하였습니다
회원가입 로직이 완성 되었으니 회원가입된 계정으로 로그인을 해보는
로직을 작성해보도록 하겠습니다!!

profile
백엔드 개발자 준비중

0개의 댓글