From 6bcbe72b43d6fa041d06878d1dae09a6d8903895 Mon Sep 17 00:00:00 2001
From: jack <jack.su@pollex.com.tw>
Date: 星期五, 11 八月 2023 16:19:02 +0800
Subject: [PATCH] [ADD] 為了解決滲透測試JWT token 登出未失效問題, 在登出後會需要建立黑名單並在filter中確定token是否非黑名單

---
 pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java                     |    1 
 pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java                       |   30 +++++++++++++++
 pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java                       |   16 ++++++++
 pamapi/src/doc/sql/executed/20230727_j.sql                                                |    0 
 pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java                           |   24 +++++++++++
 pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java |    5 +-
 pamapi/src/doc/sql/20230803_j.sql                                                         |    7 +++
 pamapi/src/doc/登出API/登出API.txt                                                            |    3 +
 8 files changed, 83 insertions(+), 3 deletions(-)

diff --git a/pamapi/src/doc/sql/20230803_j.sql b/pamapi/src/doc/sql/20230803_j.sql
new file mode 100644
index 0000000..1aff561
--- /dev/null
+++ b/pamapi/src/doc/sql/20230803_j.sql
@@ -0,0 +1,7 @@
+-- �鈭摹暺��jwt token��敺��暺�霈oken憭望��
+
+CREATE TABLE public.tokens_black_list (
+	jwt_token text NOT NULL,
+	created_date timestamp NULL,
+	CONSTRAINT tokens_black_list_pkey PRIMARY KEY (jwt_token)
+);
\ No newline at end of file
diff --git a/pamapi/src/doc/sql/20230727_j.sql b/pamapi/src/doc/sql/executed/20230727_j.sql
similarity index 100%
rename from pamapi/src/doc/sql/20230727_j.sql
rename to pamapi/src/doc/sql/executed/20230727_j.sql
diff --git "a/pamapi/src/doc/\347\231\273\345\207\272API/\347\231\273\345\207\272API.txt" "b/pamapi/src/doc/\347\231\273\345\207\272API/\347\231\273\345\207\272API.txt"
new file mode 100644
index 0000000..11dadef
--- /dev/null
+++ "b/pamapi/src/doc/\347\231\273\345\207\272API/\347\231\273\345\207\272API.txt"
@@ -0,0 +1,3 @@
+http post : 
+
+http://localhost:8080/api/logout
\ No newline at end of file
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 b9d3baf..191ed67 100644
--- a/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
+++ b/pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
@@ -80,6 +80,7 @@
             .authorizeRequests()
             .antMatchers("/api/access_analysis/**").permitAll()
             .antMatchers("/api/authenticate").permitAll()
+            .antMatchers("/api/logout").permitAll()
             .antMatchers("/api/register").permitAll()
             .antMatchers("/api/activate").permitAll()
             .antMatchers("/api/testLogin/**").permitAll()
diff --git a/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java b/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java
index a574659..7e3a0b9 100644
--- a/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java
+++ b/pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java
@@ -1,15 +1,24 @@
 package com.pollex.pam.security.jwt;
 
 import java.io.IOException;
+import java.util.Optional;
+
 import javax.servlet.FilterChain;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.filter.GenericFilterBean;
+
+import com.pollex.pam.business.domain.TokenBlackList;
+import com.pollex.pam.business.repository.TokenBlackListRepository;
 
 /**
  * Filters incoming requests and installs a Spring Security principal if a header corresponding to a valid user is
@@ -22,6 +31,7 @@
     public static final String AUTHORIZATION_TOKEN = "access_token";
 
     private final TokenProvider tokenProvider;
+    
 
     public JWTFilter(TokenProvider tokenProvider) {
         this.tokenProvider = tokenProvider;
@@ -32,19 +42,31 @@
         throws IOException, ServletException {
         HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
         String jwt = resolveToken(httpServletRequest);
+        if(StringUtils.hasText(jwt) && !jwt.equals("null")) {
+        	boolean isBlackToken = this.tokenProvider.isBlackListToken(jwt);
+        	if(isBlackToken) {
+        		HttpServletResponse response = (HttpServletResponse) servletResponse;
+            	response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        	}
+        }
+        
         if (StringUtils.hasText(jwt) && this.tokenProvider.validateToken(jwt)) {
-            Authentication authentication = this.tokenProvider.getAuthentication(jwt);
+ 
+        	Authentication authentication = this.tokenProvider.getAuthentication(jwt);
             SecurityContextHolder.getContext().setAuthentication(authentication);
         }
+        
         filterChain.doFilter(servletRequest, servletResponse);
     }
 
     private String resolveToken(HttpServletRequest request) {
+    	
         String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
         if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
             return bearerToken.substring(7);
         }
         String jwt = request.getParameter(AUTHORIZATION_TOKEN);
+        
         if (StringUtils.hasText(jwt)) {
             return jwt;
         }
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 1986286..6cff94b 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
@@ -7,8 +7,12 @@
 import java.security.Key;
 import java.util.*;
 import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletResponse;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
@@ -16,6 +20,10 @@
 import org.springframework.security.core.userdetails.User;
 import org.springframework.stereotype.Component;
 import org.springframework.util.ObjectUtils;
+
+import com.pollex.pam.business.domain.TokenBlackList;
+import com.pollex.pam.business.repository.TokenBlackListRepository;
+
 import tech.jhipster.config.JHipsterProperties;
 
 @Component
@@ -33,6 +41,9 @@
     private final long tokenValidityInMilliseconds;
 
     private final long tokenValidityInMillisecondsForRememberMe;
+
+    @Autowired
+    TokenBlackListRepository tokenBlackListRepository;
 
     public TokenProvider(JHipsterProperties jHipsterProperties) {
         byte[] keyBytes;
@@ -102,4 +113,9 @@
         }
         return false;
     }
+
+	public boolean isBlackListToken(String jwt) {
+		Optional<TokenBlackList> tokenBlack = tokenBlackListRepository.findById(jwt);
+    	return tokenBlack.isPresent();
+	}
 }
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
index f157b02..b5924f2 100644
--- a/pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java
+++ b/pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java
@@ -58,7 +58,8 @@
                     return getConsultantTokenAndRecordLoginTime(account, credentials);
                 }
                 else {
-                    throw new EServiceErrorException(eServiceResponse.getMsg());
+                	log.debug("account:{},error:{}",account,eServiceResponse.getMsg());
+                    throw new EServiceErrorException("撣唾��Ⅳ�隤�");
                 }
             }
 
@@ -69,7 +70,7 @@
     }
 
     private UsernamePasswordAuthenticationToken getConsultantTokenAndRecordLoginTime(String account, String credential) throws ConsultantDisableException {
-        Consultant consultant = consultantRepository.findOneByAgentNo(account).orElseThrow(() -> new UsernameNotFoundException("閰脤“����蒂銝��慦�像�蝟餌絞銝�"));
+        Consultant consultant = consultantRepository.findOneByAgentNo(account).orElseThrow(() -> new UsernameNotFoundException("撣唾��Ⅳ�隤�"));
 
         List<GrantedAuthority> grantedAuths = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(account, credential, grantedAuths);
diff --git a/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java b/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java
index 90c0be8..a0ce0eb 100644
--- a/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java
+++ b/pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java
@@ -3,8 +3,15 @@
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.pollex.pam.security.jwt.JWTFilter;
 import com.pollex.pam.security.jwt.TokenProvider;
+import com.pollex.pam.business.domain.TokenBlackList;
+import com.pollex.pam.business.repository.TokenBlackListRepository;
 import com.pollex.pam.business.web.vm.LoginVM;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
 import javax.validation.Valid;
+
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -12,6 +19,7 @@
 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.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -24,6 +32,9 @@
     private final TokenProvider tokenProvider;
 
     private final AuthenticationManagerBuilder authenticationManagerBuilder;
+    
+    @Autowired
+    TokenBlackListRepository tokenBlackListRepository;
 
     public UserJWTController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
         this.tokenProvider = tokenProvider;
@@ -44,6 +55,25 @@
         httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
         return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK);
     }
+    
+    @PostMapping("/logout")
+    public void logout(HttpServletRequest servletRequest) {
+        String jwtToken = resolveToken(servletRequest);
+        TokenBlackList blackList = new TokenBlackList(jwtToken);
+        tokenBlackListRepository.save(blackList);
+    }
+    
+    private String resolveToken(HttpServletRequest request) {
+        String bearerToken = request.getHeader(JWTFilter.AUTHORIZATION_HEADER);
+        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
+            return bearerToken.substring(7);
+        }
+        String jwt = request.getParameter(JWTFilter.AUTHORIZATION_TOKEN);
+        if (StringUtils.hasText(jwt)) {
+            return jwt;
+        }
+        return null;
+    }
 
     /**
      * Object to return as body in JWT Authentication.

--
Gitblit v1.8.0