Notice
Recent Posts
Recent Comments
Link
Ssul's Blog
[Django] username대신, email 사용하기, jwt로그인 2 본문
jwt토큰으로 로그인 전략.
1. 첫 로그인시 아이디/패스워드 입력
"use client";
import { useState } from "react";
export function useLoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
return {
email,
setEmail,
password,
setPassword,
};
}
export default function LoginForm({
email,
password,
setEmail,
setPassword,
}: ReturnType<typeof useLoginForm>) {
return (
<>
<form className="space-y-6">
<div>
<label
htmlFor="email"
className="block text-sm font-medium leading-6 text-gray-900"
>
Email address
</label>
<div className="mt-2">
<input
id="email"
name="email"
type="email"
autoComplete="email"
required
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
onChange={(e) => setEmail(e.currentTarget.value)}
/>
</div>
</div>
<div>
<div className="flex items-center justify-between">
<label
htmlFor="password"
className="block text-sm font-medium leading-6 text-gray-900"
>
Password
</label>
<div className="text-sm">
<a
href="#"
className="font-semibold text-indigo-600 hover:text-indigo-500"
>
Forgot password?
</a>
</div>
</div>
<div className="mt-2">
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
onChange={(e) => setPassword(e.currentTarget.value)}
/>
</div>
</div>
</form>
</>
);
}
- id/pw입력받아, api로 호출하는 코드
2. 장고 authenticate로 인증확인 후
3. token(access,refresh)을 생성해서, json으로 user정보와 함께 client에 전달
#views.py
class LoginView(APIView):
permission_classes = [permissions.AllowAny]
serializer_class = CustomTokenObtainPairSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# serializer의 validate함수 호출
# user = serializer.validated_data
# login(request, user)
# return Response({"detail": "Logged in successfully!"}, status=status.HTTP_200_OK)
# validate 함수에서 생성된 토큰을 반환합니다.
data = serializer.validated_data
return Response(data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#serializers.py
# 고객에게 email통해서 로그인 > username기반 토큰 검색
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
username_field = get_user_model().USERNAME_FIELD
email = serializers.EmailField()
password = serializers.CharField(write_only=True)
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# 필요한 경우 추가 사용자 정보를 토큰에 포함시킬 수 있습니다.
# 예: token['username'] = user.username
return token
def validate(self, attrs):
# attrs는 유효성 검증된 데이터, initial_data는 원래 데이터
# email을 사용해 사용자 인증을 시도합니다.
user = authenticate(email=attrs['email'], password=attrs['password'])
if user and user.is_active:
data = super().validate(attrs)
#user객체로 토큰 발급
refresh = self.get_token(user)
#토큰을 client에 전달 준비
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
# 여기에 사용자 정보 추가해서 함께 전달
data['user'] = {
'email': user.email,
'nickname': user.nickname,
'status': user.status,
# 필요한 경우 여기에 다른 필드 추가
}
return data
raise serializers.ValidationError("Incorrect Credentials")
4. 클라이언트는 로컬스토리지에 토큰저장, 리덕스에도 저장
// Function to handle form submission
const login = async (e) => {
e.preventDefault();
const { email, password } = loginForm;
try {
const response = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/accounts/login/`,
{
email,
password,
},
);
//api서버로부터 받은 토큰들
const accessToken = response.data.access;
const refreshToken = response.data.refresh;
// user정보는 리덕스에 저장
dispatch(
//요 친구들이 다 action.payload에 저장
userSlice.actions.setUser({
user: response.data.user,
access: response.data.access,
refresh: response.data.refresh,
}),
);
// 토큰은 로컬 스토리지에 저장
localStorage.setItem("accessToken", accessToken);
localStorage.setItem("refreshToken", refreshToken);
alert("알림: 로그인 완료");
// Redirect or perform other actions on successful login
} catch (error) {
console.log("Failed to sign in. Please check your credentials.");
}
};
5. 리덕스상태를 통해 로그인여부를 확인, 리덕스가 clear해지면, 로컬스토리지에서 refresh토큰 체크
//생략
// 훅을 통하여 로그인여부 체크하여 user객체 가져오기
const user = useAuth();
//user객체와, email이 리덕스에 있으면 로그인 한것
{user && user.userInfo.email ? ( // 로그인한 경우 마이페이지 링크를 표시합니다.
<div className="flex items-center space-x-4">
<div>
<Link href="#">마이페이지</Link>
</div>
<Logout />
</div>
) : (
<div className="flex items-center space-x-4">
<div>
<Link href="/login">로그인</Link>
</div>
<div>회원가입</div>
</div>
)}
6. refresh토큰이 없으면 로그인화면, 있으면 api에 refresh토큰과 함께 호출
const useAuth = () => {
const dispatch = useDispatch();
const user = useSelector((state) => state.user);
useEffect(() => {
const fetchUserData = async () => {
// 로컬스토리지에서 토큰 가져오기
const accessToken = localStorage.getItem("accessToken");
const refreshToken = localStorage.getItem("refreshToken");
//토큰이 있으면, refresh토큰으로 로그인 정보 받아오기
if (accessToken && refreshToken) {
try {
// JWT 토큰을 사용하여 서버로부터 사용자 정보를 요청합니다.
const response = await axios.post(
`${process.env.NEXT_PUBLIC_API_URL}/api/token/refresh/`,
{
refresh: refreshToken,
},
);
// 받아온 유저정보 업데이트
dispatch(
userSlice.actions.setUser({
user: response.data.user,
access: response.data.access,
refresh: response.data.refresh,
}),
);
} catch (error) {
console.error("User authentication failed", error);
// 에러 처리 로직 (예: 로그아웃 처리, 상태 업데이트 등)
}
}
};
fetchUserData();
}, [dispatch]);
return user;
};
export default useAuth;
7. 원래는 토큰만 주지만, user정보도 함께 주도록 커스텀마이징(refresh api커스터마이징)
#urls.py
path('api/token/refresh/', CustomTokenRefreshView.as_view(), name='token_refresh'),
#views.py
class CustomTokenRefreshView(TokenRefreshView):
serializer_class = CustomTokenRefreshSerializer
#serializers.py
class CustomTokenRefreshSerializer(TokenRefreshSerializer):
def validate(self, attrs):
data = super().validate(attrs)
# 여기에서 사용자 정보를 가져옵니다.
refresh = RefreshToken(attrs['refresh'])
user = get_user_model().objects.get(id=refresh['user_id'])
# 사용자 정보를 응답 데이터에 추가합니다.
data['user'] = {
'email': user.email,
'nickname': user.nickname,
'status': user.status,
# 필요한 경우 다른 필드 추가
}
return data
매번 구현할때마다, 다시 찾아서 구현하던 것을 정리.
이런 순서로 체크하며, 본인 서비스에 적용하면 된다.
'dev > 기능구현' 카테고리의 다른 글
[Django, tailwind] AI가 상담글에 자동으로 댓글 달아주기 #1(signal, threading사용) (0) | 2024.01.18 |
---|---|
Youtube 영상 정보/자막 정보 추출방법 (2) | 2024.01.12 |
[Django] username대신, email 사용하기, jwt로그인 1 (0) | 2023.12.19 |
소셜로그인 구현 - apple 로그인 (0) | 2023.06.28 |
restframework-simplejwt 사용하기 (0) | 2023.04.19 |