login(@Valid @RequestBody LoginPayload payload) {
+ return ResponseBodyBean.ofSuccess(authenticationService.login(payload));
+ }
+}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/LoginPayload.java b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/LoginPayload.java
new file mode 100644
index 00000000..44ab8fea
--- /dev/null
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/LoginPayload.java
@@ -0,0 +1,36 @@
+package com.jmsoftware.apiportal.auth.entity;
+
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * LoginPayload
+ *
+ * Change description here.
+ *
+ * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
+ * @date 5/8/20 2:08 PM
+ **/
+@Data
+public class LoginPayload {
+ /**
+ * The Login token: username / email
+ */
+ @NotEmpty
+ @Length(max = 100)
+ private String loginToken;
+ /**
+ * The Password.
+ */
+ @NotEmpty
+ @Length(max = 60)
+ private String password;
+ /**
+ * Remember me
+ */
+ @NotNull
+ private Boolean rememberMe;
+}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/LoginResponse.java b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/LoginResponse.java
new file mode 100644
index 00000000..e8f0c87a
--- /dev/null
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/LoginResponse.java
@@ -0,0 +1,16 @@
+package com.jmsoftware.apiportal.auth.entity;
+
+import lombok.Data;
+
+/**
+ *
LoginResponse
+ *
+ * Change description here.
+ *
+ * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
+ * @date 5/8/20 2:07 PM
+ **/
+@Data
+public class LoginResponse {
+ private String jwt;
+}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/RegisterPayload.java b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/RegisterPayload.java
new file mode 100644
index 00000000..84055d5f
--- /dev/null
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/entity/RegisterPayload.java
@@ -0,0 +1,37 @@
+package com.jmsoftware.apiportal.auth.entity;
+
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.Size;
+
+/**
+ *
RegisterPayload
+ *
+ * Change description here.
+ *
+ * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
+ * @date 5/9/20 3:53 PM
+ **/
+@Data
+public class RegisterPayload {
+ /**
+ * Username (Unique)
+ */
+ @NotEmpty
+ @Length(min = 4, max = 50)
+ private String username;
+ /**
+ * Email (Unique)
+ */
+ @NotEmpty
+ @Size(max = 30)
+ private String email;
+ /**
+ * Password
+ */
+ @NotEmpty
+ @Length(min = 8, max = 30)
+ private String password;
+}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/auth/service/AuthenticationService.java b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/service/AuthenticationService.java
new file mode 100644
index 00000000..e92f12ac
--- /dev/null
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/service/AuthenticationService.java
@@ -0,0 +1,30 @@
+package com.jmsoftware.apiportal.auth.service;
+
+import com.jmsoftware.apiportal.auth.entity.LoginPayload;
+import com.jmsoftware.apiportal.auth.entity.LoginResponse;
+import com.jmsoftware.apiportal.auth.entity.RegisterPayload;
+
+/**
+ *
AuthenticationService
+ *
+ * Change description here.
+ *
+ * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
+ * @date 5/8/20 2:04 PM
+ */
+public interface AuthenticationService {
+ /**
+ * Register.
+ *
+ * @param payload the payload
+ */
+ void register(RegisterPayload payload);
+
+ /**
+ * Login login response.
+ *
+ * @param payload the payload
+ * @return the login response
+ */
+ LoginResponse login(LoginPayload payload);
+}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/auth/service/impl/AuthenticationServiceImpl.java b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/service/impl/AuthenticationServiceImpl.java
new file mode 100644
index 00000000..c67e077d
--- /dev/null
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/auth/service/impl/AuthenticationServiceImpl.java
@@ -0,0 +1,55 @@
+package com.jmsoftware.apiportal.auth.service.impl;
+
+import com.jmsoftware.apiportal.auth.entity.LoginPayload;
+import com.jmsoftware.apiportal.auth.entity.LoginResponse;
+import com.jmsoftware.apiportal.auth.entity.RegisterPayload;
+import com.jmsoftware.apiportal.auth.service.AuthenticationService;
+import com.jmsoftware.apiportal.universal.domain.UserPO;
+import com.jmsoftware.apiportal.universal.service.JwtService;
+import com.jmsoftware.apiportal.universal.service.UserService;
+import lombok.RequiredArgsConstructor;
+import lombok.val;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+
+/**
+ *
AuthenticationServiceImpl
+ *
+ * Change description here.
+ *
+ * @author Johnny Miller (鍾俊), email: johnnysviva@outlook.com
+ * @date 5/8/20 2:04 PM
+ **/
+@Service
+@RequiredArgsConstructor
+public class AuthenticationServiceImpl implements AuthenticationService {
+ private final BCryptPasswordEncoder encoder;
+ private final AuthenticationManager authenticationManager;
+ private final JwtService jwtService;
+ private final UserService userService;
+
+ @Override
+ public void register(RegisterPayload payload) {
+ val userPo = new UserPO();
+ userPo.setUsername(payload.getUsername());
+ userPo.setEmail(payload.getEmail());
+ userPo.setPassword(encoder.encode(payload.getPassword()));
+ // TODO: user service should be in the `auth-center`
+ userService.saveUser(userPo);
+ }
+
+ @Override
+ public LoginResponse login(LoginPayload payload) {
+ val authenticationToken = new UsernamePasswordAuthenticationToken(payload.getLoginToken(),
+ payload.getPassword());
+ val authentication = authenticationManager.authenticate(authenticationToken);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ val jwt = jwtService.createJwt(authentication, payload.getRememberMe());
+ val loginResponse = new LoginResponse();
+ loginResponse.setJwt(jwt);
+ return loginResponse;
+ }
+}
diff --git a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/aspect/ExceptionControllerAdvice.java b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/aspect/ExceptionControllerAdvice.java
index 2c8bcab9..03230056 100644
--- a/api-portal/src/main/java/com/jmsoftware/apiportal/universal/aspect/ExceptionControllerAdvice.java
+++ b/api-portal/src/main/java/com/jmsoftware/apiportal/universal/aspect/ExceptionControllerAdvice.java
@@ -11,6 +11,7 @@
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -117,6 +118,16 @@ public ResponseBodyBean