Created LoginExceptionView to explain to client why login or refresh have failed.
This commit is contained in:
parent
1976e345b6
commit
08787d50b0
@ -46,29 +46,24 @@ public final class AuthController {
|
|||||||
.body(loginView);
|
.body(loginView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(LoginException.class)
|
||||||
|
public ResponseEntity<LoginExceptionView> onLoginException(LoginException ex) {
|
||||||
|
final LoginExceptionView loginExceptionView = new LoginExceptionView(ex.getReason(), ex.getMessage());
|
||||||
|
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(loginExceptionView);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<LoginView> login(@RequestBody LoginBody loginBody) {
|
public ResponseEntity<LoginView> login(@RequestBody LoginBody loginBody) throws LoginException {
|
||||||
try {
|
final LoginDetails loginDetails = this.authService.login(loginBody.getUsername(), loginBody.getPassword());
|
||||||
final LoginDetails loginDetails = this.authService.login(loginBody.getUsername(), loginBody.getPassword());
|
return this.getLoginViewResponseEntity(loginDetails);
|
||||||
return this.getLoginViewResponseEntity(loginDetails);
|
|
||||||
} catch (LoginException loginException) {
|
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/refresh")
|
@PostMapping("/refresh")
|
||||||
public ResponseEntity<LoginView> refresh(
|
public ResponseEntity<LoginView> refresh(
|
||||||
@CookieValue(value = "refresh-token", required = false) @Nullable String oldRefreshToken
|
@CookieValue(value = "refresh-token", required = false) @Nullable String oldRefreshToken
|
||||||
) {
|
) throws LoginException {
|
||||||
if (oldRefreshToken != null) {
|
final LoginDetails loginDetails = this.authService.refresh(oldRefreshToken);
|
||||||
try {
|
return this.getLoginViewResponseEntity(loginDetails);
|
||||||
final LoginDetails loginDetails = this.authService.refresh(oldRefreshToken);
|
|
||||||
return this.getLoginViewResponseEntity(loginDetails);
|
|
||||||
} catch (LoginException loginException) {
|
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package app.mealsmadeeasy.api.auth;
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public interface AuthService {
|
public interface AuthService {
|
||||||
LoginDetails login(String username, String password) throws LoginException;
|
LoginDetails login(String username, String password) throws LoginException;
|
||||||
void logout(String refreshToken);
|
void logout(String refreshToken);
|
||||||
LoginDetails refresh(String refreshToken) throws LoginException;
|
LoginDetails refresh(@Nullable String refreshToken) throws LoginException;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package app.mealsmadeeasy.api.auth;
|
|||||||
|
|
||||||
import app.mealsmadeeasy.api.jwt.JwtService;
|
import app.mealsmadeeasy.api.jwt.JwtService;
|
||||||
import app.mealsmadeeasy.api.user.UserEntity;
|
import app.mealsmadeeasy.api.user.UserEntity;
|
||||||
import io.jsonwebtoken.JwtException;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
@ -45,10 +45,9 @@ public final class AuthServiceImpl implements AuthService {
|
|||||||
@Override
|
@Override
|
||||||
public LoginDetails login(String username, String password) throws LoginException {
|
public LoginDetails login(String username, String password) throws LoginException {
|
||||||
try {
|
try {
|
||||||
final Authentication authentication = this.authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
final Authentication authentication = this.authenticationManager.authenticate(
|
||||||
username,
|
new UsernamePasswordAuthenticationToken(username, password)
|
||||||
password
|
);
|
||||||
));
|
|
||||||
final UserEntity principal = (UserEntity) authentication.getPrincipal();
|
final UserEntity principal = (UserEntity) authentication.getPrincipal();
|
||||||
return new LoginDetails(
|
return new LoginDetails(
|
||||||
username,
|
username,
|
||||||
@ -56,7 +55,7 @@ public final class AuthServiceImpl implements AuthService {
|
|||||||
this.createRefreshToken(principal)
|
this.createRefreshToken(principal)
|
||||||
);
|
);
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
throw new LoginException(e);
|
throw new LoginException(LoginExceptionReason.INVALID_CREDENTIALS, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,28 +65,32 @@ public final class AuthServiceImpl implements AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginDetails refresh(String refreshToken) throws LoginException {
|
public LoginDetails refresh(@Nullable String refreshToken) throws LoginException {
|
||||||
try {
|
if (refreshToken == null) {
|
||||||
final RefreshTokenEntity old = this.refreshTokenRepository.findByToken(refreshToken)
|
throw new LoginException(LoginExceptionReason.NO_REFRESH_TOKEN, "No refresh token provided.");
|
||||||
.orElseThrow(() -> new LoginException("No such refresh-token: " + refreshToken));
|
|
||||||
if (old.isRevoked()) {
|
|
||||||
throw new LoginException("RefreshToken is revoked.");
|
|
||||||
}
|
|
||||||
if (old.getExpires().isBefore(LocalDateTime.now())) {
|
|
||||||
throw new LoginException("RefreshToken is expired.");
|
|
||||||
}
|
|
||||||
final UserEntity principal = old.getOwner();
|
|
||||||
this.refreshTokenRepository.delete(old);
|
|
||||||
|
|
||||||
final String username = principal.getUsername();
|
|
||||||
return new LoginDetails(
|
|
||||||
username,
|
|
||||||
this.jwtService.generateAccessToken(username),
|
|
||||||
this.createRefreshToken(principal)
|
|
||||||
);
|
|
||||||
} catch (JwtException e) {
|
|
||||||
throw new LoginException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final RefreshTokenEntity old = this.refreshTokenRepository.findByToken(refreshToken)
|
||||||
|
.orElseThrow(() -> new LoginException(
|
||||||
|
LoginExceptionReason.INVALID_REFRESH_TOKEN,
|
||||||
|
"No such refresh-token: " + refreshToken
|
||||||
|
));
|
||||||
|
if (old.isRevoked()) {
|
||||||
|
throw new LoginException(LoginExceptionReason.INVALID_REFRESH_TOKEN, "RefreshToken is revoked.");
|
||||||
|
}
|
||||||
|
if (old.getExpires().isBefore(LocalDateTime.now())) {
|
||||||
|
throw new LoginException(LoginExceptionReason.EXPIRED_REFRESH_TOKEN, "RefreshToken is expired.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final UserEntity principal = old.getOwner();
|
||||||
|
this.refreshTokenRepository.delete(old);
|
||||||
|
|
||||||
|
final String username = principal.getUsername();
|
||||||
|
return new LoginDetails(
|
||||||
|
username,
|
||||||
|
this.jwtService.generateAccessToken(username),
|
||||||
|
this.createRefreshToken(principal)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,20 @@ package app.mealsmadeeasy.api.auth;
|
|||||||
|
|
||||||
public final class LoginException extends Exception {
|
public final class LoginException extends Exception {
|
||||||
|
|
||||||
public LoginException(String message) {
|
private final LoginExceptionReason reason;
|
||||||
|
|
||||||
|
public LoginException(LoginExceptionReason reason, String message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
this.reason = reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoginException(String message, Throwable cause) {
|
public LoginException(LoginExceptionReason reason, Throwable cause) {
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginException(Throwable cause) {
|
|
||||||
super(cause);
|
super(cause);
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginExceptionReason getReason() {
|
||||||
|
return this.reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
public enum LoginExceptionReason {
|
||||||
|
INVALID_CREDENTIALS,
|
||||||
|
EXPIRED_REFRESH_TOKEN,
|
||||||
|
INVALID_REFRESH_TOKEN,
|
||||||
|
NO_REFRESH_TOKEN
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package app.mealsmadeeasy.api.auth;
|
||||||
|
|
||||||
|
public class LoginExceptionView {
|
||||||
|
|
||||||
|
private final LoginExceptionReason reason;
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
public LoginExceptionView(LoginExceptionReason reason, String message) {
|
||||||
|
this.reason = reason;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginExceptionReason getReason() {
|
||||||
|
return this.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return this.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user