保誠-保戶業務員媒合平台
[ADD] 為了解決滲透測試JWT token 登出未失效問題, 在登出後會需要建立黑名單並在filter中確定token是否非黑名單
修改5個檔案
新增2個檔案
修改1個檔案名稱
84 ■■■■■ 已變更過的檔案
pamapi/src/doc/sql/20230803_j.sql 7 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/sql/executed/20230727_j.sql 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/登出API/登出API.txt 3 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/jwt/JWTFilter.java 22 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java 16 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/UserJWTController.java 30 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/doc/sql/20230803_j.sql
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,7 @@
-- ç‚ºäº†å¼±é»žæŽƒæåœ¨jwt token登出後須列入黑名單讓token失效
CREATE TABLE public.tokens_black_list (
    jwt_token text NOT NULL,
    created_date timestamp NULL,
    CONSTRAINT tokens_black_list_pkey PRIMARY KEY (jwt_token)
);
pamapi/src/doc/sql/executed/20230727_j.sql
pamapi/src/doc/µn¥XAPI/µn¥XAPI.txt
¤ñ¹ï·sÀÉ®×
@@ -0,0 +1,3 @@
http post :
http://localhost:8080/api/logout
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()
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
@@ -23,6 +32,7 @@
    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);
            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;
        }
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();
    }
}
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);
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;
@@ -45,6 +56,25 @@
        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.
     */