From 7dfed155bc4f5a1c7cf5a592e8325dc1ba9243bc Mon Sep 17 00:00:00 2001
From: wayne <wayne8692wayne8692@gmail.com>
Date: 星期一, 22 十一月 2021 15:48:09 +0800
Subject: [PATCH] [update] otp登入、認證、顧問EService登入

---
 pamapi/src/main/java/com/pollex/pam/service/OtpWebService.java                            |   63 +++
 pamapi/src/doc/登入API/客戶發手機OTP.txt                                                         |   15 
 pamapi/src/main/resources/config/application-sit.yml                                      |    1 
 pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java                       |    6 
 pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java                       |   89 ++++
 pamapi/src/doc/登入API/客戶發EmailOTP.txt                                                      |   15 
 pamapi/src/doc/登入API/顧問登入ByEService.txt                                                   |   18 +
 pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java                         |   17 
 pamapi/src/main/java/com/pollex/pam/web/rest/OtpResource.java                             |  100 +++++
 pamapi/src/main/java/com/pollex/pam/web/rest/vm/VerifyOtpVM.java                          |   19 
 pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java                     |    9 
 pamapi/pom.xml                                                                            |   39 ++
 pamapi/src/main/java/com/pollex/pam/repository/CustomerRepository.java                    |    5 
 pamapi/src/main/resources/config/application-dev.yml                                      |    1 
 pamapi/src/main/java/com/pollex/pam/web/rest/vm/OtpAccount.java                           |   27 +
 pamapi/src/main/java/com/pollex/pam/enums/ConsultantDetailEnum.java                       |   17 
 pamapi/src/main/java/com/pollex/pam/web/rest/EServiceResource.java                        |   46 ++
 pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java                           |   16 
 /dev/null                                                                                 |   71 ---
 pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java                     |    2 
 pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceLoginVM.java                      |    2 
 pamapi/src/main/java/com/pollex/pam/service/LoginService.java                             |    1 
 pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java                        |    4 
 pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java                  |    3 
 pamapi/src/main/resources/config/application-uat.yml                                      |  118 ++++++
 pamapi/src/doc/登入API/客戶認證OTP並登入.txt                                                       |   17 
 pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java |  123 ++++++
 pamapi/src/main/java/com/pollex/pam/security/provider/CustomAuthenticationProvider.java   |   41 ++
 pamapi/src/main/java/com/pollex/pam/security/token/OtpAuthenticationToken.java            |   47 ++
 pamapi/src/main/java/com/pollex/pam/security/provider/OtpAuthenticationProvider.java      |   80 ++++
 pamapi/src/main/java/com/pollex/pam/security/token/EServiceAuthenticationToken.java       |   53 ++
 31 files changed, 974 insertions(+), 91 deletions(-)

diff --git a/pamapi/pom.xml b/pamapi/pom.xml
index 1fd6eab..76a25f6 100644
--- a/pamapi/pom.xml
+++ b/pamapi/pom.xml
@@ -984,6 +984,45 @@
             </properties>
         </profile>
         <profile>
+            <id>uat</id>
+            <dependencies>
+                <dependency>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-undertow</artifactId>
+                    <scope>provided</scope>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-clean-plugin</artifactId>
+                        <configuration>
+                            <filesets>
+                                <fileset>
+                                    <directory>target/classes/static/</directory>
+                                </fileset>
+                            </filesets>
+                        </configuration>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.springframework.boot</groupId>
+                        <artifactId>spring-boot-maven-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>build-info</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+            <properties>
+                <!-- default Spring profiles -->
+                <spring.profiles.active>uat{profile.api-docs}${profile.tls}${profile.no-liquibase}</spring.profiles.active>
+            </properties>
+        </profile>
+        <profile>
             <id>prod</id>
             <dependencies>
                 <dependency>
diff --git "a/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\347\231\274EmailOTP.txt" "b/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\347\231\274EmailOTP.txt"
new file mode 100644
index 0000000..d1fba1e
--- /dev/null
+++ "b/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\347\231\274EmailOTP.txt"
@@ -0,0 +1,15 @@
+http post:
+http://localhost:8080/api/otp/byEmail
+
+request body:
+{
+    "email":"test@pollex.com.tw"
+}
+
+response body:
+{
+    "indexKey": "7830d17b",
+    "success": true,
+    "failCode": "",
+    "failReason": ""
+}
diff --git "a/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\347\231\274\346\211\213\346\251\237OTP.txt" "b/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\347\231\274\346\211\213\346\251\237OTP.txt"
new file mode 100644
index 0000000..6072e63
--- /dev/null
+++ "b/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\347\231\274\346\211\213\346\251\237OTP.txt"
@@ -0,0 +1,15 @@
+http post:
+http://localhost:8080/api/otp/byPhone
+
+request body:
+{
+    "phone":"0912345678"
+}
+
+response body:
+{
+    "indexKey": "5913a8cd",
+    "success": true,
+    "failCode": "",
+    "failReason": ""
+}
diff --git "a/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\350\252\215\350\255\211OTP\344\270\246\347\231\273\345\205\245.txt" "b/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\350\252\215\350\255\211OTP\344\270\246\347\231\273\345\205\245.txt"
new file mode 100644
index 0000000..124c914
--- /dev/null
+++ "b/pamapi/src/doc/\347\231\273\345\205\245API/\345\256\242\346\210\266\350\252\215\350\255\211OTP\344\270\246\347\231\273\345\205\245.txt"
@@ -0,0 +1,17 @@
+http post:
+http://localhost:8080/api/otp/verify
+
+request body:
+{
+    "account": "wayne@pollex.com.tw",   // �撣嗅����mail嚗�ㄐ撌涪mail�蝭��
+    "indexKey": "123",  // �otp��pi���ndex key
+    "otpCode": "123" // �����縑蝞望�����Ⅳ
+}
+
+���ev銝剖恥�撣唾���撌崎eams��mail靽∠拳��� (�����垢撌亦�葦��A撣唾�歇蝬�末)��
+�ndexKey��tpCode�dev�銝��遙雿���隞亦���
+
+response body:
+{
+    "id_token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YXluZUBwb2xsZXguY29tLnR3IiwiYXV0aCI6IlJPTEVfVVNFUiIsImRldGFpbHMiOnsiQ3VzdG9tZXJBY2NvdW50Ijoid2F5bmVAcG9sbGV4LmNvbS50dyIsIkN1c3RvbWVySWQiOiI2IiwiQ3VzdG9tZXJOYW1lIjoiV2F5bmUifSwiZXhwIjoxNjM3NjQ5NzUzfQ.6xqkWG7kQPUHOys8vPdx6ebgH1wgZ4gysFEa1t1jCnKB44VsFZ8PjtUlN2mvroBdGtPwpOynoTHU7HvAQ3_mnQ"
+}
diff --git "a/pamapi/src/doc/\347\231\273\345\205\245API/\351\241\247\345\225\217\347\231\273\345\205\245ByEService.txt" "b/pamapi/src/doc/\347\231\273\345\205\245API/\351\241\247\345\225\217\347\231\273\345\205\245ByEService.txt"
new file mode 100644
index 0000000..1c0158e
--- /dev/null
+++ "b/pamapi/src/doc/\347\231\273\345\205\245API/\351\241\247\345\225\217\347\231\273\345\205\245ByEService.txt"
@@ -0,0 +1,18 @@
+http post:
+http://localhost:8080/api/eService/authenticate
+
+request body:
+{
+    "username": "D265260662",
+    "password": "test123"
+}
+
+����憿批����ngela���10�“��葫閰西���
+瑼��:PCA-Matching Platform-test-data 1108.xlsx
+
+撣唾��xcel銝��憛怠神嚗�Ⅳ�dev蝡臭蒂�敺Service瑼X嚗隞亦���
+
+response body:
+{
+    "id_token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJEMjY1MjYwNjYyIiwiYXV0aCI6IlJPTEVfVVNFUiIsImRldGFpbHMiOnsiQ29uc3VsdGFudEFnZW50Tm8iOiJEMjY1MjYwNjYyIiwiQ29uc3VsdGFudE5hbWUiOiLmnpfmiJDlrIwiLCJDb25zdWx0YW50SWQiOiI1In0sImV4cCI6MTYzNzY0OTQ5NX0.BjLiA-nuJOsUtD6UqyONr3brPXbdhjA_5srFBS2qgcvFhfznSOCZMlHtl4NtphiZ1CAk2jyg79QFlqGvM6Uk1A"
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java b/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java
index 6270c33..68ed03f 100644
--- a/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java
+++ b/pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java
@@ -11,11 +11,20 @@
 @ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
 public class ApplicationProperties {
 
+    private boolean mockLogin;
     private String otpWebServiceUrl;
     private String otpWebServicePassword;
     private String otpWebServiceSystemType;
     private String eServiceLoginUrl;
 
+    public boolean isMockLogin() {
+        return mockLogin;
+    }
+
+    public void setMockLogin(boolean mockLogin) {
+        this.mockLogin = mockLogin;
+    }
+
     public String getOtpWebServiceUrl() {
         return otpWebServiceUrl;
     }
diff --git a/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java b/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
index 5052121..4349448 100644
--- a/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
+++ b/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
@@ -82,6 +82,8 @@
             .antMatchers("/api/register").permitAll()
             .antMatchers("/api/activate").permitAll()
             .antMatchers("/api/testLogin/**").permitAll()
+            .antMatchers("/api/otp/**").permitAll()
+            .antMatchers("/api/eService/authenticate").permitAll()
             .antMatchers("/api/account/reset-password/init").permitAll()
             .antMatchers("/api/account/reset-password/finish").permitAll()
             .antMatchers("/api/consultant/recommend").permitAll()
diff --git a/pamapi/src/main/java/com/pollex/pam/enums/ConsultantDetailEnum.java b/pamapi/src/main/java/com/pollex/pam/enums/ConsultantDetailEnum.java
new file mode 100644
index 0000000..331dd27
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/enums/ConsultantDetailEnum.java
@@ -0,0 +1,17 @@
+package com.pollex.pam.enums;
+
+public enum ConsultantDetailEnum {
+    ID("ConsultantId"),
+    NAME("ConsultantName"),
+    AGENT_NO("ConsultantAgentNo");
+
+    private final String value;
+
+    ConsultantDetailEnum(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java b/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
new file mode 100644
index 0000000..692c87a
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
@@ -0,0 +1,17 @@
+package com.pollex.pam.enums;
+
+public enum CustomerDetailEnum {
+    ID("CustomerId"),
+    NAME("CustomerName"),
+    ACCOUNT("CustomerAccount");
+
+    private final String value;
+
+    CustomerDetailEnum(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java
index d373143..af244ab 100644
--- a/pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java
+++ b/pamapi/src/main/java/com/pollex/pam/repository/ConsultantRepository.java
@@ -10,8 +10,7 @@
 
 @Repository
 public interface ConsultantRepository extends JpaRepository<Consultant, Long>, JpaSpecificationExecutor<Consultant> {
-    Optional<Consultant> findFirstByAgentNo(String agentNo);
+    Optional<Consultant> findOneByAgentNo(String agentNo);
     List<Consultant> findAllByRecommendIsTrue();
     List<Consultant> findAllByAgentNoIn(List<String> agentNoList);
-
 }
diff --git a/pamapi/src/main/java/com/pollex/pam/repository/CustomerRepository.java b/pamapi/src/main/java/com/pollex/pam/repository/CustomerRepository.java
index c4b87cf..eb84506 100644
--- a/pamapi/src/main/java/com/pollex/pam/repository/CustomerRepository.java
+++ b/pamapi/src/main/java/com/pollex/pam/repository/CustomerRepository.java
@@ -5,7 +5,10 @@
 
 import com.pollex.pam.domain.Customer;
 
+import java.util.List;
+import java.util.Optional;
+
 @Repository
 public interface CustomerRepository extends JpaRepository<Customer, Long>{
-
+    Optional<Customer> findOneByEmailEqualsOrPhoneEquals(String email, String phone);
 }
diff --git a/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java b/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
index ef4095a..5e6c77b 100644
--- a/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
+++ b/pamapi/src/main/java/com/pollex/pam/security/SecurityUtils.java
@@ -5,6 +5,8 @@
 import java.util.Optional;
 import java.util.stream.Stream;
 
+import com.pollex.pam.enums.ConsultantDetailEnum;
+import com.pollex.pam.enums.CustomerDetailEnum;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
@@ -99,18 +101,18 @@
     private static Stream<String> getAuthorities(Authentication authentication) {
         return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority);
     }
-    
+
     public static String getAgentNo() {
-    	return getCurrentUserLogin().orElseGet(null);
+        Map<String, String> userDetails = getCurrentUserDetails();
+        return userDetails.get(ConsultantDetailEnum.AGENT_NO.getValue());
     }
-    
+
     // todo , should get id from user details
     public static Long getCustomerId() {
-//    	Map<String, String> userDetails = getCurrentUserDetails();
-//    	return Long.parseLong(userDetails.get("id"));
-    	return Long.parseLong("2");
+    	Map<String, String> userDetails = getCurrentUserDetails();
+    	return Long.parseLong(userDetails.get(CustomerDetailEnum.ID.getValue()));
     }
-    
+
     public static Map<String, String> getCurrentUserDetails() {
    	 SecurityContext securityContext = SecurityContextHolder.getContext();
    	 if(securityContext.getAuthentication()==null ||
diff --git a/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java b/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java
index d17fca0..1986286 100644
--- a/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java
+++ b/pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java
@@ -24,6 +24,7 @@
     private final Logger log = LoggerFactory.getLogger(TokenProvider.class);
 
     private static final String AUTHORITIES_KEY = "auth";
+    private static final String AUTHORITIES_DETAILS = "details";
 
     private final Key key;
 
@@ -69,6 +70,7 @@
             .builder()
             .setSubject(authentication.getName())
             .claim(AUTHORITIES_KEY, authorities)
+            .claim(AUTHORITIES_DETAILS, authentication.getDetails())
             .signWith(key, SignatureAlgorithm.HS512)
             .setExpiration(validity)
             .compact();
@@ -84,8 +86,10 @@
             .collect(Collectors.toList());
 
         User principal = new User(claims.getSubject(), "", authorities);
+        UsernamePasswordAuthenticationToken authInfo = new UsernamePasswordAuthenticationToken(principal, token, authorities);
+        authInfo.setDetails(claims.get(AUTHORITIES_DETAILS));
 
-        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
+        return authInfo;
     }
 
     public boolean validateToken(String authToken) {
diff --git a/pamapi/src/main/java/com/pollex/pam/security/provider/CustomAuthenticationProvider.java b/pamapi/src/main/java/com/pollex/pam/security/provider/CustomAuthenticationProvider.java
new file mode 100644
index 0000000..079a473
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/security/provider/CustomAuthenticationProvider.java
@@ -0,0 +1,41 @@
+package com.pollex.pam.security.provider;
+
+import com.pollex.pam.config.ApplicationProperties;
+import com.pollex.pam.security.token.EServiceAuthenticationToken;
+import com.pollex.pam.security.token.OtpAuthenticationToken;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+
+
+@Component
+public class CustomAuthenticationProvider implements AuthenticationProvider {
+
+    @Autowired
+    EServiceAuthenticationProvider eServiceAuthenticationProvider;
+
+    @Autowired
+    OtpAuthenticationProvider otpAuthenticationProvider;
+
+    @Autowired
+    ApplicationProperties applicationProperty;
+
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        if(authentication instanceof OtpAuthenticationToken) {
+            return otpAuthenticationProvider.authenticate((OtpAuthenticationToken) authentication);
+        }
+        else if(authentication instanceof EServiceAuthenticationToken) {
+            return eServiceAuthenticationProvider.authenticate((EServiceAuthenticationToken) authentication);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean supports(Class<?> authentication) {
+        return AbstractAuthenticationToken.class.isAssignableFrom(authentication);
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java b/pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java
new file mode 100644
index 0000000..28a2a19
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java
@@ -0,0 +1,123 @@
+package com.pollex.pam.security.provider;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.pollex.pam.config.ApplicationProperties;
+import com.pollex.pam.domain.Consultant;
+import com.pollex.pam.enums.ConsultantDetailEnum;
+import com.pollex.pam.enums.CustomerDetailEnum;
+import com.pollex.pam.repository.ConsultantRepository;
+import com.pollex.pam.security.token.EServiceAuthenticationToken;
+import com.pollex.pam.service.dto.EServiceRequest;
+import com.pollex.pam.service.dto.EServiceResponse;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContexts;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import javax.net.ssl.SSLContext;
+import java.security.KeyManagementException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class EServiceAuthenticationProvider {
+
+    private static final String E_SERVICE_LOGIN_SUCCESS_CODE = "0";
+
+    @Autowired
+    ApplicationProperties applicationProperty;
+
+    @Autowired
+    ConsultantRepository consultantRepository;
+
+    public Authentication authenticate(EServiceAuthenticationToken authenticationToken) throws AuthenticationException {
+        String account = authenticationToken.getPrincipal();
+        String credentials = authenticationToken.getCredentials();
+
+        if(applicationProperty.isMockLogin()){
+            return getConsultantToken(account, credentials);
+        }
+
+        try {
+            ResponseEntity<EServiceResponse> responseEntity = loginByEService(account, credentials);
+            if(HttpStatus.OK.equals(responseEntity.getStatusCode())) {
+                EServiceResponse eServiceResponse = responseEntity.getBody();
+
+                if(E_SERVICE_LOGIN_SUCCESS_CODE.equals(eServiceResponse.getCode())){
+                    return getConsultantToken(account, credentials);
+                }
+            }
+
+            throw new AuthenticationCredentialsNotFoundException("");
+        } catch (Exception e) {
+            throw new AuthenticationCredentialsNotFoundException("");
+        }
+    }
+
+    private UsernamePasswordAuthenticationToken getConsultantToken(String account, String credential) {
+        Consultant consultant = consultantRepository.findOneByAgentNo(account).orElseThrow(() -> new UsernameNotFoundException("consultant is not in db, consultant agentNo = " + account));
+
+        List<GrantedAuthority> grantedAuths = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(account, credential, grantedAuths);
+
+        Map<String, String> details = new HashMap<>();
+        details.put(ConsultantDetailEnum.ID.getValue(), consultant.getId().toString());
+        details.put(ConsultantDetailEnum.NAME.getValue(), consultant.getName());
+        details.put(ConsultantDetailEnum.AGENT_NO.getValue(), account);
+        authenticationToken.setDetails(details);
+
+        return authenticationToken;
+    }
+
+    private ResponseEntity<EServiceResponse> loginByEService(String account, String paxxword) throws Exception{
+        EServiceRequest dto = new EServiceRequest();
+        dto.setFunc("ValidateUserLogin");
+        dto.setId(account);
+        dto.setPin(paxxword);
+        dto.setPwd(paxxword);
+        dto.setSys("epos");
+
+        String dtoJson = new ObjectMapper().writeValueAsString(dto);
+
+        RestTemplate restTemplate = getTrustAllRestTemplate();
+
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+
+        HttpEntity<String> entity = new HttpEntity<>(dtoJson, headers);
+        return restTemplate.exchange(applicationProperty.geteServiceLoginUrl(), HttpMethod.POST, entity, EServiceResponse.class);
+    }
+
+    private RestTemplate getTrustAllRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
+        SSLContext sslContext = SSLContexts.custom()
+            .loadTrustMaterial(null, (X509Certificate[] x509Certs, String s) -> true)
+            .build();
+        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
+        CloseableHttpClient httpClient = HttpClients.custom()
+            .setSSLSocketFactory(csf)
+            .build();
+        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
+        requestFactory.setHttpClient(httpClient);
+        requestFactory.setConnectTimeout(300000);
+        requestFactory.setReadTimeout(300000);
+        return new RestTemplate(requestFactory);
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/security/provider/OtpAuthenticationProvider.java b/pamapi/src/main/java/com/pollex/pam/security/provider/OtpAuthenticationProvider.java
new file mode 100644
index 0000000..6fadbba
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/security/provider/OtpAuthenticationProvider.java
@@ -0,0 +1,80 @@
+package com.pollex.pam.security.provider;
+
+import com.pollex.pam.config.ApplicationProperties;
+import com.pollex.pam.domain.Customer;
+import com.pollex.pam.enums.CustomerDetailEnum;
+import com.pollex.pam.repository.CustomerRepository;
+import com.pollex.pam.security.token.OtpAuthenticationToken;
+import com.pollex.pam.service.OtpWebService;
+import com.pollex.pam.service.dto.OtpResponseDTO;
+import com.pollex.pam.web.rest.vm.OtpAccount;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class OtpAuthenticationProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(OtpAuthenticationProvider.class);
+
+    @Autowired
+    ApplicationProperties applicationProperty;
+
+    @Autowired
+    OtpWebService otpWebService;
+
+    @Autowired
+    CustomerRepository customerRepository;
+
+    public Authentication authenticate(OtpAuthenticationToken otpAuthenticationToken) throws AuthenticationException {
+        OtpAccount otpAccount = otpAuthenticationToken.getPrincipal();
+        String account = otpAccount.getAccount();
+        String indexKey = otpAccount.getIndexKey();
+        String otpCode = otpAuthenticationToken.getCredentials();
+
+        if(applicationProperty.isMockLogin()){
+            return getCustomerToken(account, otpCode);
+        }
+
+        try {
+            OtpResponseDTO otpResponseDTO = otpWebService.verifyOTP(indexKey, otpCode);
+            if(otpResponseDTO.isSuccess()) {
+                return getCustomerToken(account, otpCode);
+            }
+        } catch (Exception e) {
+            log.error("Exception: ", e);
+            throw new AuthenticationCredentialsNotFoundException("");
+        }
+
+        throw new AuthenticationCredentialsNotFoundException("");
+    }
+
+    private UsernamePasswordAuthenticationToken getCustomerToken(String account, String otpCode) {
+        // todo �摮�DB��撅祆迤撣貊鞊∴����畾essage����垢��脰�酉���
+        Customer customer = customerRepository.findOneByEmailEqualsOrPhoneEquals(account, account).orElseThrow(() -> new UsernameNotFoundException("this customer is not in db, account = " + account));
+
+        List<GrantedAuthority> grantedAuths = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
+        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(account, otpCode, grantedAuths);
+
+        Map<String, String> details = new HashMap<>();
+        details.put(CustomerDetailEnum.ID.getValue(), customer.getId().toString());
+        details.put(CustomerDetailEnum.NAME.getValue(), customer.getName());
+        details.put(CustomerDetailEnum.ACCOUNT.getValue(), account);
+        authenticationToken.setDetails(details);
+
+        return authenticationToken;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/security/token/EServiceAuthenticationToken.java b/pamapi/src/main/java/com/pollex/pam/security/token/EServiceAuthenticationToken.java
new file mode 100644
index 0000000..6bc8410
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/security/token/EServiceAuthenticationToken.java
@@ -0,0 +1,53 @@
+package com.pollex.pam.security.token;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.SpringSecurityCoreVersion;
+import org.springframework.util.Assert;
+
+import java.util.Collection;
+
+public class EServiceAuthenticationToken extends AbstractAuthenticationToken {
+
+    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
+
+    private final String principle;
+    private String credentials;
+
+    public EServiceAuthenticationToken(String principle, String credentials) {
+        super(null);
+        this.principle = principle;
+        this.credentials = credentials;
+        setAuthenticated(false);
+    }
+
+    public EServiceAuthenticationToken(String principle, String credentials, Collection<? extends GrantedAuthority> authorities) {
+        super(authorities);
+        this.principle = principle;
+        this.credentials = credentials;
+        setAuthenticated(true);
+    }
+
+    @Override
+    public String getCredentials() {
+        return credentials;
+    }
+
+    @Override
+    public String getPrincipal() {
+        return principle;
+    }
+
+    @Override
+    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+        Assert.isTrue(!isAuthenticated,
+            "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+        super.setAuthenticated(false);
+    }
+
+    @Override
+    public void eraseCredentials() {
+        super.eraseCredentials();
+        this.credentials = null;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/security/token/OtpAuthenticationToken.java b/pamapi/src/main/java/com/pollex/pam/security/token/OtpAuthenticationToken.java
new file mode 100644
index 0000000..9652e9c
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/security/token/OtpAuthenticationToken.java
@@ -0,0 +1,47 @@
+package com.pollex.pam.security.token;
+
+import com.pollex.pam.web.rest.vm.OtpAccount;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.SpringSecurityCoreVersion;
+import org.springframework.util.Assert;
+
+import java.util.Collection;
+
+public class OtpAuthenticationToken extends AbstractAuthenticationToken {
+
+    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
+
+    private final OtpAccount principle;
+    private String credentials;
+
+    public OtpAuthenticationToken(OtpAccount principle, String credentials) {
+        super(null);
+        this.principle = principle;
+        this.credentials = credentials;
+        setAuthenticated(false);
+    }
+
+    @Override
+    public String getCredentials() {
+        return this.credentials;
+    }
+
+    @Override
+    public OtpAccount getPrincipal() {
+        return this.principle;
+    }
+
+    @Override
+    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+        Assert.isTrue(!isAuthenticated,
+            "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+        super.setAuthenticated(false);
+    }
+
+    @Override
+    public void eraseCredentials() {
+        super.eraseCredentials();
+        this.credentials = null;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java b/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
index 3dbcbd9..af9c247 100644
--- a/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
+++ b/pamapi/src/main/java/com/pollex/pam/service/ConsultantService.java
@@ -73,7 +73,7 @@
     }
 
     public ConsultantDetailDTO getConsultantDetail(String agentNo) {
-        Consultant consultant = consultantRepository.findFirstByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
+        Consultant consultant = consultantRepository.findOneByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
         return consultantMapper.toDetailDto(consultant);
     }
 
@@ -108,7 +108,7 @@
 
     public void removeConsultantFromCustomList(String agentNo) {
         Long customId = SecurityUtils.getCustomerId();
-        Consultant consultant = consultantRepository.findFirstByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
+        Consultant consultant = consultantRepository.findOneByAgentNo(agentNo).orElseThrow(ConsultantNotFoundException::new);
         CustomerFavoriteConsultant target = customerFavoriteConsultantRepository.findOneByCustomerIdAndConsultant(customId, consultant).orElse(null);
 
         if(target != null) {
diff --git a/pamapi/src/main/java/com/pollex/pam/service/LoginService.java b/pamapi/src/main/java/com/pollex/pam/service/LoginService.java
index 3c50045..0cd4b29 100644
--- a/pamapi/src/main/java/com/pollex/pam/service/LoginService.java
+++ b/pamapi/src/main/java/com/pollex/pam/service/LoginService.java
@@ -25,7 +25,6 @@
 import com.pollex.pam.config.ApplicationProperties;
 import com.pollex.pam.service.dto.EServiceRequest;
 import com.pollex.pam.service.dto.EServiceResponse;
-import com.pollex.pam.web.rest.vm.EServiceRequestVM;
 import com.pollex.pam.web.rest.vm.OtpEmailLoginVM;
 import com.pollex.pam.web.rest.vm.OtpSMSLoginVM;
 import com.pollex.pam.web.rest.vm.VerifyOtpVM;
diff --git a/pamapi/src/main/java/com/pollex/pam/service/OtpWebService.java b/pamapi/src/main/java/com/pollex/pam/service/OtpWebService.java
new file mode 100644
index 0000000..35c9a63
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/service/OtpWebService.java
@@ -0,0 +1,63 @@
+package com.pollex.pam.service;
+
+import com.pollex.pam.config.ApplicationProperties;
+import com.pollex.pam.service.dto.OtpResponseDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import tw.com.softleader.otp.ws.OtpWebServiceLocator;
+import tw.com.softleader.otp.ws.OtpWebServicePortBindingStub;
+
+import javax.xml.rpc.ServiceException;
+import java.rmi.RemoteException;
+
+@Service
+public class OtpWebService {
+
+    private static final Logger log = LoggerFactory.getLogger(OtpWebService.class);
+
+    @Autowired
+    ApplicationProperties applicationProperty;
+
+    public OtpResponseDTO sendByPhone(String phone) throws ServiceException, RemoteException {
+        OtpWebServicePortBindingStub stub = getOtpWebServicePortBindingStub();
+        log.info("call OtpService snedOtpBySMS, ");
+
+        String[] result =
+            stub.sendOtpBySMS(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), phone);
+
+        return new OtpResponseDTO(result);
+    }
+
+    public OtpResponseDTO sendByEmail(String email) throws ServiceException, RemoteException {
+        OtpWebServicePortBindingStub stub = getOtpWebServicePortBindingStub();
+
+        String[] result =
+            stub.sendOtpByEmail(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), email);
+
+        final OtpResponseDTO otpResponseDTO = new OtpResponseDTO(result);
+        if(otpResponseDTO.isSuccess()) {
+            return otpResponseDTO;
+        }
+        else {
+            throw new RuntimeException("error code = " + otpResponseDTO.getFailCode() + ", error reason = " + otpResponseDTO.getFailReason());
+        }
+    }
+
+    public OtpResponseDTO verifyOTP(String indexKey, String otpCode) throws ServiceException, RemoteException {
+        OtpWebServicePortBindingStub stub = getOtpWebServicePortBindingStub();
+
+        String[] result =
+            stub.verifyOtp(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), indexKey, otpCode);
+
+        return new OtpResponseDTO(result);
+    }
+
+    private OtpWebServicePortBindingStub getOtpWebServicePortBindingStub() throws ServiceException {
+        OtpWebServiceLocator locator = new OtpWebServiceLocator();
+        locator.setOtpWebServicePortEndpointAddress(applicationProperty.getOtpWebServiceUrl());
+
+        return (OtpWebServicePortBindingStub) locator.getOtpWebServicePort();
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/EServiceResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/EServiceResource.java
new file mode 100644
index 0000000..1ee9eed
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/EServiceResource.java
@@ -0,0 +1,46 @@
+package com.pollex.pam.web.rest;
+
+import com.pollex.pam.security.jwt.JWTFilter;
+import com.pollex.pam.security.jwt.TokenProvider;
+import com.pollex.pam.security.token.EServiceAuthenticationToken;
+import com.pollex.pam.web.rest.vm.EServiceLoginVM;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/eService")
+public class EServiceResource {
+
+    @Autowired
+    AuthenticationManagerBuilder authenticationManagerBuilder;
+
+    @Autowired
+    TokenProvider tokenProvider;
+
+    @PostMapping("/authenticate")
+    public ResponseEntity<UserJWTController.JWTToken> authorize(@RequestBody EServiceLoginVM eServiceLoginVM) {
+        // todo 撽�Ⅳ隤��
+
+
+        EServiceAuthenticationToken authenticationToken = new EServiceAuthenticationToken(
+            eServiceLoginVM.getUsername(),
+            eServiceLoginVM.getPassword()
+        );
+
+        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        String jwt = tokenProvider.createToken(authentication, false);
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt);
+        return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK);
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/LoginResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/LoginResource.java
deleted file mode 100644
index 3734775..0000000
--- a/pamapi/src/main/java/com/pollex/pam/web/rest/LoginResource.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.pollex.pam.web.rest;
-
-import com.pollex.pam.config.ApplicationProperties;
-import com.pollex.pam.service.LoginService;
-import com.pollex.pam.service.dto.OtpResponseDTO;
-import com.pollex.pam.web.rest.vm.EServiceRequestVM;
-import com.pollex.pam.service.dto.EServiceResponse;
-import com.pollex.pam.web.rest.vm.OtpEmailLoginVM;
-import com.pollex.pam.web.rest.vm.OtpSMSLoginVM;
-import com.pollex.pam.web.rest.vm.VerifyOtpVM;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
-import tw.com.softleader.otp.ws.OtpWebServiceLocator;
-import tw.com.softleader.otp.ws.OtpWebServicePortBindingStub;
-
-import javax.xml.rpc.ServiceException;
-import java.rmi.RemoteException;
-
-@RestController
-@RequestMapping("/api/testLogin")
-public class LoginResource {
-
-    private final static Logger log = LoggerFactory.getLogger(LoginResource.class);
-
-    private final LoginService loginService;
-    private final ApplicationProperties applicationProperty;
-
-    public LoginResource(LoginService loginService, ApplicationProperties applicationProperty) {
-        this.loginService = loginService;
-        this.applicationProperty = applicationProperty;
-    }
-
-    @GetMapping("/bySMS")
-    public ResponseEntity<OtpResponseDTO> sendOtpBySMS(@RequestParam("phone") String phone) throws ServiceException, RemoteException {
-        OtpWebServiceLocator locator = new OtpWebServiceLocator();
-        locator.setOtpWebServicePortEndpointAddress(applicationProperty.getOtpWebServiceUrl());
-
-        OtpWebServicePortBindingStub otpWebServicePort = (OtpWebServicePortBindingStub) locator.getOtpWebServicePort();
-        String[] result =
-            otpWebServicePort.sendOtpBySMS(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), phone);
-
-        return new ResponseEntity<>(new OtpResponseDTO(result), HttpStatus.OK);
-    }
-
-    @GetMapping("/byEmail")
-    public ResponseEntity<OtpResponseDTO> sendOtpByEmail(@RequestParam("email") String email) throws RemoteException, ServiceException {
-        OtpWebServiceLocator locator = new OtpWebServiceLocator();
-        locator.setOtpWebServicePortEndpointAddress(applicationProperty.getOtpWebServiceUrl());
-
-        OtpWebServicePortBindingStub otpWebServicePort = (OtpWebServicePortBindingStub) locator.getOtpWebServicePort();
-        String[] result =
-            otpWebServicePort.sendOtpByEmail(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), email);
-
-        return new ResponseEntity<>(new OtpResponseDTO(result), HttpStatus.OK);
-    }
-
-    // todo: �ㄐ蝘餃�隤����rovider
-    @PostMapping("/verifyOtp")
-    public void verifyOtp(@RequestBody VerifyOtpVM verifyOtpParam) {
-        OtpWebServiceLocator locator = new OtpWebServiceLocator();
-    }
-
-    // todo: �ㄐ蝘餃�隤����rovider
-    @GetMapping("/byEService")
-    public ResponseEntity<EServiceResponse> loginByEService(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception {
-        return loginService.loginByEService(account, password);
-    }
-}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/OtpResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/OtpResource.java
new file mode 100644
index 0000000..bb55739
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/OtpResource.java
@@ -0,0 +1,100 @@
+package com.pollex.pam.web.rest;
+
+import com.pollex.pam.config.ApplicationProperties;
+import com.pollex.pam.security.jwt.JWTFilter;
+import com.pollex.pam.security.jwt.TokenProvider;
+import com.pollex.pam.security.token.OtpAuthenticationToken;
+import com.pollex.pam.service.OtpWebService;
+import com.pollex.pam.service.dto.OtpResponseDTO;
+import com.pollex.pam.web.rest.vm.OtpAccount;
+import com.pollex.pam.web.rest.vm.OtpEmailLoginVM;
+import com.pollex.pam.web.rest.vm.OtpSMSLoginVM;
+import com.pollex.pam.web.rest.vm.VerifyOtpVM;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.xml.rpc.ServiceException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.rmi.RemoteException;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.UUID;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@RestController
+@RequestMapping("/api/otp")
+public class OtpResource {
+
+    private final static Logger log = LoggerFactory.getLogger(OtpResource.class);
+
+    @Autowired
+    ApplicationProperties applicationProperty;
+
+    @Autowired
+    OtpWebService otpWebService;
+
+    @Autowired
+    AuthenticationManagerBuilder authenticationManagerBuilder;
+
+    @Autowired
+    TokenProvider tokenProvider;
+
+    @PostMapping("/byPhone")
+    public ResponseEntity<Object> sendOtpByPhone(@RequestBody OtpSMSLoginVM login) {
+        try {
+            if(applicationProperty.isMockLogin()) {
+                return new ResponseEntity<>(getMockOtpResponse(), HttpStatus.OK);
+            }
+
+            OtpResponseDTO otpResponseDTO = otpWebService.sendByPhone(login.getPhone());
+            return new ResponseEntity<>(otpResponseDTO, HttpStatus.OK);
+        } catch (ServiceException | RemoteException e) {
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("connecting otp web service error");
+        }
+    }
+
+    @PostMapping("/byEmail")
+    public ResponseEntity<Object> sendOtpByEmail(@RequestBody OtpEmailLoginVM login) {
+        try {
+            if(applicationProperty.isMockLogin()) {
+                return new ResponseEntity<>(getMockOtpResponse(), HttpStatus.OK);
+            }
+
+            OtpResponseDTO otpResponseDTO = otpWebService.sendByEmail(login.getEmail());
+            return new ResponseEntity<>(otpResponseDTO, HttpStatus.OK);
+        } catch (ServiceException | RemoteException e) {
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("connecting otp web service error");
+        }
+    }
+
+    @PostMapping("/verify")
+    public ResponseEntity<UserJWTController.JWTToken> verifyOtp(@RequestBody VerifyOtpVM verifyOtpParam) {
+        OtpAccount otpAccount = new OtpAccount(verifyOtpParam.getAccount(), verifyOtpParam.getIndexKey());
+        OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(
+            otpAccount,
+            verifyOtpParam.getOtpCode()
+        );
+
+        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        String jwt = tokenProvider.createToken(authentication, false);
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt);
+        return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK);
+    }
+
+    private OtpResponseDTO getMockOtpResponse() {
+        String indexKey = UUID.randomUUID().toString().substring(0, 8);
+        return new OtpResponseDTO(new String[]{indexKey, "0", "", ""});
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java b/pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java
new file mode 100644
index 0000000..07691a3
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java
@@ -0,0 +1,89 @@
+package com.pollex.pam.web.rest;
+
+import com.pollex.pam.config.ApplicationProperties;
+import com.pollex.pam.security.jwt.JWTFilter;
+import com.pollex.pam.security.jwt.TokenProvider;
+import com.pollex.pam.security.token.EServiceAuthenticationToken;
+import com.pollex.pam.security.token.OtpAuthenticationToken;
+import com.pollex.pam.service.LoginService;
+import com.pollex.pam.service.OtpWebService;
+import com.pollex.pam.service.dto.OtpResponseDTO;
+import com.pollex.pam.web.rest.vm.OtpAccount;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import javax.xml.rpc.ServiceException;
+import java.rmi.RemoteException;
+
+@RestController
+@RequestMapping("/api/testLogin")
+public class TestLoginResource {
+
+    private final static Logger log = LoggerFactory.getLogger(TestLoginResource.class);
+
+    @Autowired
+    LoginService loginService;
+
+    @Autowired
+    ApplicationProperties applicationProperty;
+
+    @Autowired
+    OtpWebService otpWebService;
+
+    @Autowired
+    AuthenticationManagerBuilder authenticationManagerBuilder;
+
+    @Autowired
+    TokenProvider tokenProvider;
+
+    @GetMapping("/bySMS")
+    public ResponseEntity<OtpResponseDTO> sendOtpBySMS(@RequestParam("phone") String phone) throws ServiceException, RemoteException {
+        otpWebService.sendByPhone(phone);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @GetMapping("/byEmail")
+    public ResponseEntity<OtpResponseDTO> sendOtpByEmail(@RequestParam("email") String email) throws RemoteException, ServiceException {
+        otpWebService.sendByEmail(email);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @GetMapping("/verifyOtp")
+    public ResponseEntity<UserJWTController.JWTToken> verifyOtp(@RequestParam("account") String account, @RequestParam("indexKey") String indexKey, @RequestParam("otpCode") String otpCode) throws ServiceException, RemoteException {
+        OtpAccount otpAccount = new OtpAccount(account, indexKey);
+        OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken(
+            otpAccount,
+            otpCode
+        );
+
+        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        String jwt = tokenProvider.createToken(authentication, false);
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt);
+        return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK);
+    }
+
+    @GetMapping("/byEService")
+    public ResponseEntity<UserJWTController.JWTToken> loginByEService(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception {
+        EServiceAuthenticationToken authenticationToken = new EServiceAuthenticationToken(
+            account,
+            password
+        );
+
+        Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
+        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
+        String jwt = tokenProvider.createToken(authentication, false);
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt);
+        return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK);
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceRequestVM.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceLoginVM.java
similarity index 92%
rename from pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceRequestVM.java
rename to pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceLoginVM.java
index 91e3b50..2cebe8e 100644
--- a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceRequestVM.java
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/EServiceLoginVM.java
@@ -1,6 +1,6 @@
 package com.pollex.pam.web.rest.vm;
 
-public class EServiceRequestVM {
+public class EServiceLoginVM {
     private String username;
     private String password;
 
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/OtpAccount.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/OtpAccount.java
new file mode 100644
index 0000000..4ee1a74
--- /dev/null
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/OtpAccount.java
@@ -0,0 +1,27 @@
+package com.pollex.pam.web.rest.vm;
+
+public class OtpAccount {
+    private String account;
+    private String indexKey;
+
+    public OtpAccount(String account, String indexKey) {
+        this.account = account;
+        this.indexKey = indexKey;
+    }
+
+    public String getAccount() {
+        return account;
+    }
+
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public String getIndexKey() {
+        return indexKey;
+    }
+
+    public void setIndexKey(String indexKey) {
+        this.indexKey = indexKey;
+    }
+}
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/VerifyOtpVM.java b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/VerifyOtpVM.java
index 0f40cd6..8465eee 100644
--- a/pamapi/src/main/java/com/pollex/pam/web/rest/vm/VerifyOtpVM.java
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/vm/VerifyOtpVM.java
@@ -1,15 +1,24 @@
 package com.pollex.pam.web.rest.vm;
 
 public class VerifyOtpVM {
-    private String identityCode;
+    private String account;
+    private String indexKey;
     private String otpCode;
 
-    public String getIdentityCode() {
-        return identityCode;
+    public String getAccount() {
+        return account;
     }
 
-    public void setIdentityCode(String identityCode) {
-        this.identityCode = identityCode;
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public String getIndexKey() {
+        return indexKey;
+    }
+
+    public void setIndexKey(String indexKey) {
+        this.indexKey = indexKey;
     }
 
     public String getOtpCode() {
diff --git a/pamapi/src/main/resources/config/application-dev.yml b/pamapi/src/main/resources/config/application-dev.yml
index 890f872..450ab2a 100644
--- a/pamapi/src/main/resources/config/application-dev.yml
+++ b/pamapi/src/main/resources/config/application-dev.yml
@@ -111,6 +111,7 @@
 # ===================================================================
 
 application:
+  mock-login: true
   otp-web-service-url: https://vtwlifeopensyssit.pru.intranet.asia:443/pcalife-otp/ws/otpWebService
   otp-web-service-password: es20!%Pass
   otp-web-service-system-type: epos
diff --git a/pamapi/src/main/resources/config/application-sit.yml b/pamapi/src/main/resources/config/application-sit.yml
index bd1e2ab..93ca69e 100644
--- a/pamapi/src/main/resources/config/application-sit.yml
+++ b/pamapi/src/main/resources/config/application-sit.yml
@@ -111,6 +111,7 @@
 # ===================================================================
 
 application:
+  mock-login: false
   otp-web-service-url: https://vtwlifeopensyssit.pru.intranet.asia:443/pcalife-otp/ws/otpWebService
   otp-web-service-password: es20!%Pass
   otp-web-service-system-type: epos
diff --git a/pamapi/src/main/resources/config/application-uat.yml b/pamapi/src/main/resources/config/application-uat.yml
new file mode 100644
index 0000000..e5ddb97
--- /dev/null
+++ b/pamapi/src/main/resources/config/application-uat.yml
@@ -0,0 +1,118 @@
+# ===================================================================
+# Spring Boot configuration for the "sit" profile.
+#
+# This configuration overrides the application.yml file.
+#
+# More information on profiles: https://www.jhipster.tech/profiles/
+# More information on configuration properties: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+# ===================================================================
+# Standard Spring Boot properties.
+# Full reference is available at:
+# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
+# ===================================================================
+
+logging:
+  level:
+    ROOT: DEBUG
+    tech.jhipster: DEBUG
+    org.hibernate.SQL: DEBUG
+    com.pollex.pam: DEBUG
+
+spring:
+  devtools:
+    restart:
+      enabled: true
+      additional-exclude: static/**
+    livereload:
+      enabled: false # we use Webpack dev server + BrowserSync for livereload
+  jackson:
+    serialization:
+      indent-output: true
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    url: jdbc:postgresql://VTWLIFELDPOS01:5006/omo?currentSchema=omo
+    username: omo_svc
+    password: omo_svc-100784c
+    hikari:
+      poolName: Hikari
+      auto-commit: false
+  jpa:
+    database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect
+  liquibase:
+    # Remove 'faker' if you do not want the sample data to be loaded automatically
+    contexts: uat, faker
+  mail:
+    host: localhost
+    port: 25
+    username:
+    password:
+  messages:
+    cache-duration: PT1S # 1 second, see the ISO 8601 standard
+  thymeleaf:
+    cache: false
+  sleuth:
+    sampler:
+      probability: 1 # report 100% of traces
+  zipkin: # Use the "zipkin" Maven profile to have the Spring Cloud Zipkin dependencies
+    base-url: http://localhost:9411
+    enabled: false
+    locator:
+      discovery:
+        enabled: true
+
+server:
+  port: 8080
+
+# ===================================================================
+# JHipster specific properties
+#
+# Full reference is available at: https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+jhipster:
+  cache: # Cache configuration
+    ehcache: # Ehcache configuration
+      time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache
+      max-entries: 100 # Number of objects in each cache entry
+  #  cors:
+  #    # Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+)
+  #    allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000'
+  #    allowed-methods: '*'
+  #    allowed-headers: '*'
+  #    exposed-headers: 'Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params'
+  #    allow-credentials: true
+  #    max-age: 1800
+  security:
+    authentication:
+      jwt:
+        # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one)
+        base64-secret: MjI3YWRiMzg1ZTY3ZmZkZDgxZmI5Yjc5YjVkOTIzMzc4MmI2OWM3NWVkZjFiOTNmNjg0YWFjZWQ4YzhlOTUzYzk3MGUzMzM5Y2U5MDdkZTMyN2Q0N2E0M2ZmM2FhYzkyNDY4MjBkYTY5OGM4YmIzMmYxODJhNWFkMmVhNmVhNTM=
+        # Token is valid 24 hours
+        token-validity-in-seconds: 86400
+        token-validity-in-seconds-for-remember-me: 2592000
+  mail: # specific JHipster mail property, for standard properties see MailProperties
+    base-url: http://127.0.0.1:8080
+  logging:
+    use-json-format: false # By default, logs are not in Json format
+    logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration
+      enabled: false
+      host: localhost
+      port: 5000
+      queue-size: 512
+# ===================================================================
+# Application specific properties
+# Add your own application properties here, see the ApplicationProperties class
+# to have type-safe configuration, like in the JHipsterProperties above
+#
+# More documentation is available at:
+# https://www.jhipster.tech/common-application-properties/
+# ===================================================================
+
+application:
+  mock-login: false
+  otp-web-service-url: https://vtwlifeopensyssit.pru.intranet.asia:443/pcalife-otp/ws/otpWebService
+  otp-web-service-password: es20!%Pass
+  otp-web-service-system-type: epos
+  e-service-login-url: https://ssotwsit.eservice.pcalife.com.tw/sso/acctValidate

--
Gitblit v1.8.0