Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
J
java-jwt
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Phùng Quốc Toàn
java-jwt
Commits
bf445329
Commit
bf445329
authored
Apr 25, 2025
by
Phùng Quốc Toàn
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: implement login and OTP verification functionality
parent
58a1b9d6
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
92 additions
and
17 deletions
+92
-17
SecurityConfig.java
...evteria/identityservice/configuration/SecurityConfig.java
+1
-1
AuthenticationController.java
.../identityservice/controller/AuthenticationController.java
+11
-4
LoginRequest.java
...om/devteria/identityservice/dto/request/LoginRequest.java
+2
-2
VerifyOtpLoginRequest.java
...ia/identityservice/dto/request/VerifyOtpLoginRequest.java
+14
-0
AuthenticationResponse.java
.../identityservice/dto/response/AuthenticationResponse.java
+1
-1
LoginResponse.java
.../devteria/identityservice/dto/response/LoginResponse.java
+16
-0
OtpType.java
...main/java/com/devteria/identityservice/enums/OtpType.java
+2
-1
AuthenticationService.java
...vteria/identityservice/service/AuthenticationService.java
+28
-8
EmailService.java
...va/com/devteria/identityservice/service/EmailService.java
+17
-0
No files found.
src/main/java/com/devteria/identityservice/configuration/SecurityConfig.java
View file @
bf445329
...
...
@@ -20,7 +20,7 @@ import org.springframework.security.web.SecurityFilterChain;
@RequiredArgsConstructor
public
class
SecurityConfig
{
private
final
String
[]
PUBLIC_ENDPOINTS
=
new
String
[]{
"/auth/
token"
,
"/auth/introspect"
,
"/users"
,
"/auth/logout"
,
"/auth/refresh-token"
,
"/auth/forgot-password"
,
"/auth/reset-password
"
};
private
final
String
[]
PUBLIC_ENDPOINTS
=
new
String
[]{
"/auth/
login"
,
"/auth/introspect"
,
"/users"
,
"/auth/logout"
,
"/auth/refresh-token"
,
"/auth/forgot-password"
,
"/auth/reset-password"
,
"/auth/verify-otp
"
};
CustomJwtDecoder
customJwtDecoder
;
...
...
src/main/java/com/devteria/identityservice/controller/AuthenticationController.java
View file @
bf445329
...
...
@@ -3,6 +3,7 @@ package com.devteria.identityservice.controller;
import
com.devteria.identityservice.dto.request.*
;
import
com.devteria.identityservice.dto.response.AuthenticationResponse
;
import
com.devteria.identityservice.dto.response.IntrospectResponse
;
import
com.devteria.identityservice.dto.response.LoginResponse
;
import
com.devteria.identityservice.service.AuthenticationService
;
import
com.nimbusds.jose.JOSEException
;
import
jakarta.mail.MessagingException
;
...
...
@@ -23,11 +24,17 @@ import java.text.ParseException;
public
class
AuthenticationController
{
AuthenticationService
authenticationService
;
@PostMapping
(
"/token"
)
ApiResponse
<
AuthenticationResponse
>
authenticate
(
@RequestBody
AuthenticationRequest
request
)
{
var
result
=
authenticationService
.
authenticate
(
request
);
@PostMapping
(
"/login"
)
ApiResponse
<
LoginResponse
>
login
(
@RequestBody
LoginRequest
request
)
throws
MessagingException
{
return
ApiResponse
.<
LoginResponse
>
builder
()
.
result
(
authenticationService
.
login
(
request
))
.
build
();
}
@PostMapping
(
"/verify-otp"
)
ApiResponse
<
AuthenticationResponse
>
verifyOtp
(
@RequestBody
VerifyOtpLoginRequest
request
)
{
return
ApiResponse
.<
AuthenticationResponse
>
builder
()
.
result
(
result
)
.
result
(
authenticationService
.
verifyOtp
(
request
)
)
.
build
();
}
...
...
src/main/java/com/devteria/identityservice/dto/request/
Authenticatio
nRequest.java
→
src/main/java/com/devteria/identityservice/dto/request/
Logi
nRequest.java
View file @
bf445329
...
...
@@ -8,7 +8,7 @@ import lombok.experimental.FieldDefaults;
@AllArgsConstructor
@Builder
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
Authenticatio
nRequest
{
String
username
;
public
class
Logi
nRequest
{
String
email
;
String
password
;
}
src/main/java/com/devteria/identityservice/dto/request/VerifyOtpLoginRequest.java
0 → 100644
View file @
bf445329
package
com
.
devteria
.
identityservice
.
dto
.
request
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
VerifyOtpLoginRequest
{
String
email
;
String
otp
;
}
src/main/java/com/devteria/identityservice/dto/response/AuthenticationResponse.java
View file @
bf445329
...
...
@@ -9,6 +9,6 @@ import lombok.experimental.FieldDefaults;
@Builder
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
AuthenticationResponse
{
String
token
;
boolean
authenticated
;
String
token
;
}
src/main/java/com/devteria/identityservice/dto/response/LoginResponse.java
0 → 100644
View file @
bf445329
package
com
.
devteria
.
identityservice
.
dto
.
response
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
LoginResponse
{
boolean
authenticated
;
boolean
requiredOtp
;
String
sentTo
;
int
resendAfter
;
}
src/main/java/com/devteria/identityservice/enums/OtpType.java
View file @
bf445329
...
...
@@ -10,7 +10,8 @@ import lombok.experimental.FieldDefaults;
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
,
makeFinal
=
true
)
public
enum
OtpType
{
RESET_PASSWORD
(
"reset-password:"
,
2
*
60
),
// 2 phút
REGISTRATION
(
"registration:"
,
5
*
60
);
// 5 phút
REGISTRATION
(
"registration:"
,
5
*
60
),
// 5 phút
LOGIN
(
"login:"
,
5
*
60
);
// 5 phút
String
name
;
int
expireTimeInSeconds
;
...
...
src/main/java/com/devteria/identityservice/service/AuthenticationService.java
View file @
bf445329
...
...
@@ -3,6 +3,7 @@ package com.devteria.identityservice.service;
import
com.devteria.identityservice.dto.request.*
;
import
com.devteria.identityservice.dto.response.AuthenticationResponse
;
import
com.devteria.identityservice.dto.response.IntrospectResponse
;
import
com.devteria.identityservice.dto.response.LoginResponse
;
import
com.devteria.identityservice.entity.InvalidatedToken
;
import
com.devteria.identityservice.entity.User
;
import
com.devteria.identityservice.enums.OtpType
;
...
...
@@ -23,7 +24,6 @@ import lombok.extern.slf4j.Slf4j;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.data.redis.core.RedisTemplate
;
import
org.springframework.security.core.context.SecurityContextHolder
;
import
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.stereotype.Service
;
import
org.springframework.util.CollectionUtils
;
...
...
@@ -41,7 +41,7 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
,
makeFinal
=
true
)
public
class
AuthenticationService
{
private
static
String
TOKEN_KEY_PREFIX
=
"invalid_token:"
;
private
static
final
String
TOKEN_KEY_PREFIX
=
"invalid_token:"
;
RedisTemplate
<
String
,
Object
>
redisTemplate
;
PasswordEncoder
passwordEncoder
;
OtpService
otpService
;
...
...
@@ -78,9 +78,8 @@ public class AuthenticationService {
}
public
AuthenticationResponse
authenticate
(
AuthenticationRequest
request
)
{
PasswordEncoder
passwordEncoder
=
new
BCryptPasswordEncoder
(
10
);
var
user
=
userRepository
.
findByUsername
(
request
.
getUsername
())
public
LoginResponse
login
(
LoginRequest
request
)
throws
MessagingException
{
var
user
=
userRepository
.
findByEmail
(
request
.
getEmail
())
.
orElseThrow
(()
->
new
AppException
(
ErrorCode
.
USER_NOT_EXISTED
));
boolean
authenticated
=
passwordEncoder
.
matches
(
request
.
getPassword
(),
...
...
@@ -88,12 +87,33 @@ public class AuthenticationService {
if
(!
authenticated
)
throw
new
AppException
(
ErrorCode
.
UNAUTHENTICATED
);
String
otp
=
otpService
.
generateAndSaveOtp
(
request
.
getEmail
(),
OtpType
.
LOGIN
);
emailService
.
sendOtpEmail
(
request
.
getEmail
(),
otp
,
OtpType
.
LOGIN
);
return
LoginResponse
.
builder
()
.
authenticated
(
false
)
.
requiredOtp
(
true
)
.
sentTo
(
request
.
getEmail
())
.
resendAfter
(
OtpType
.
LOGIN
.
getExpireTimeInSeconds
())
.
build
();
}
public
AuthenticationResponse
verifyOtp
(
VerifyOtpLoginRequest
request
)
{
if
(!(
otpService
.
validateOtp
(
request
.
getOtp
(),
request
.
getEmail
(),
OtpType
.
LOGIN
)))
{
throw
new
AppException
(
ErrorCode
.
INVALID_OTP
);
}
User
user
=
userRepository
.
findByEmail
(
request
.
getEmail
())
.
orElseThrow
(()
->
new
AppException
(
ErrorCode
.
UNAUTHENTICATED
));
var
token
=
generateToken
(
user
);
return
AuthenticationResponse
.
builder
()
.
token
(
token
)
.
authenticated
(
true
)
.
token
(
token
)
.
build
();
}
private
String
generateToken
(
User
user
)
{
...
...
@@ -197,8 +217,8 @@ public class AuthenticationService {
saveInvalidatedToken
(
invalidatedToken
);
String
user
name
=
signedJWT
.
getJWTClaimsSet
().
getSubject
();
User
user
=
userRepository
.
findBy
Username
(
username
)
String
user
Id
=
signedJWT
.
getJWTClaimsSet
().
getSubject
();
User
user
=
userRepository
.
findBy
Id
(
userId
)
.
orElseThrow
(()
->
new
AppException
(
ErrorCode
.
UNAUTHENTICATED
));
var
token
=
generateToken
(
user
);
...
...
src/main/java/com/devteria/identityservice/service/EmailService.java
View file @
bf445329
...
...
@@ -52,6 +52,9 @@ public class EmailService {
case
REGISTRATION:
sendRegistrationOtp
(
to
,
otp
);
break
;
case
LOGIN:
sendLoginOtp
(
to
,
otp
);
break
;
default
:
log
.
error
(
"Unknown OTP type: {}"
,
otpType
);
throw
new
IllegalArgumentException
(
"Không hỗ trợ loại OTP này"
);
...
...
@@ -72,6 +75,20 @@ public class EmailService {
sendEmail
(
to
,
subject
,
htmlContent
,
true
);
}
private
void
sendLoginOtp
(
String
to
,
String
otp
)
throws
MessagingException
{
String
subject
=
"Mã OTP đăng nhập của bạn"
;
String
htmlContent
=
String
.
format
(
"<div style='font-family: Arial, sans-serif;'>"
+
"<h2>Đăng nhập tài khoản</h2>"
+
"<p>Mã OTP của bạn để đăng nhập là:</p>"
+
"<h1 style='color: #4285f4; font-size: 32px; letter-spacing: 2px;'>%s</h1>"
+
"<p>Mã này có hiệu lực trong 5 phút.</p>"
+
"<p>Nếu bạn không yêu cầu đăng nhập, vui lòng bỏ qua email này.</p>"
+
"</div>"
,
otp
);
sendEmail
(
to
,
subject
,
htmlContent
,
true
);
}
private
void
sendPasswordResetOtp
(
String
to
,
String
otp
)
throws
MessagingException
{
String
subject
=
"Mã OTP đổi mật khẩu của bạn"
;
String
htmlContent
=
String
.
format
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment