【Bug】SpringSecurity Oauth2 請求 Zuul 網關轉發 token 丟失問題

請求 Zuul 網關轉發參數丟失問題

最近使用 spring security oauth2 進行登錄的權限認證功能實現,認證服務器認證成功後,返回 token 參數。前臺攜帶 token 參數請求資源服務器後,經過 zuul 網關轉發後 token 丟失,請求返回401無法授權。

認證服務器
/**
 * @Classname: HMall
 * @Date: 2019-9-27 13:13
 * @Author: 98
 * @Description: 認證服務器
 */
@Slf4j
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Resource(name = "authenticationManagerBean")
    private AuthenticationManager authenticationManagerBean;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManagerBean);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .checkTokenAccess("isAuthenticated()")
                .allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("client")
                .secret(passwordEncoder.encode("secret"))
                .authorizedGrantTypes("password", "refresh_token")
                .scopes("backend")
                .resourceIds("backend-resources")
                .accessTokenValiditySeconds(60 * 60 * 24)
                .refreshTokenValiditySeconds(60 * 60 * 24 * 30);
    }
}
安全配置
/**
 * @Classname: HMall
 * @Date: 2019-9-27 14:49
 * @Author: 98
 * @Description: 安全配置
 */
@Configuration
@EnableWebSecurity
//方法攔截
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return new UserDetailsServiceImpl();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsServiceBean());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/admin/login");
    }
}
資源服務器
/**
 * @Classname: HMall
 * @Date: 2019-9-29 23:26
 * @Author: 98
 * @Description:
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceSecurityConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //以下爲配置所需保護的資源路徑及權限,需要與認證服務器配置的授權部分對應
        http.exceptionHandling()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/admin/**").hasAuthority("System");
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        //設置資源ID
        resources.resourceId("backend-resources");
    }
}

解決方法

解決思路

由於請求經過網關 zuul 轉發後,zuul 會重新替代掉請求頭的數據,我們可以通過實現 ZuulFilter,編寫一個過濾器,重新封裝請求參數。

過濾器配置
/**
 * @Classname: HMall
 * @Date: 2019-9-30 11:30
 * @Author: 98
 * @Description:
 */
@Component
public class TokenFilter extends ZuulFilter {
    //日誌
    private static Logger logger = LoggerFactory.getLogger(TokenFilter.class);

    //排除攔截的路徑
    private static final String LOGIN_URI = "/admin/login";

    //無權限時的提示語
    private static final String INVALID_TOKEN = "您沒有權限進行此操作";

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //獲取上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        //獲取request對象
        HttpServletRequest request = currentContext.getRequest();
        //驗證token時候 token的參數 從請求頭獲取
        String token = request.getHeader("access_token");
        String method = request.getMethod();
        String requestURL = request.getRequestURL().toString();
        System.out.println("token="+token);
        //封裝請求
        //獲取參數
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap == null) {
            return null;
        }
        //替換,業務邏輯
        Map<String, List<String>> requestQueryParams = currentContext.getRequestQueryParams();
        if (requestQueryParams == null) {
            requestQueryParams = new HashMap<>(parameterMap.size() * 2);
        }
        
        for (String key : parameterMap.keySet()) {
            String[] values = parameterMap.get(key);
            List<String> valueList = new LinkedList<>();
            for (String value : values) {
                valueList.add(value);
            }
            requestQueryParams.put(key, valueList);
        }

        //重新寫入參數
        List<String> valueList = new LinkedList<>();
        valueList.add(token);
        requestQueryParams.put("access_token",valueList);
        
        currentContext.setRequestQueryParams(requestQueryParams);
        
        logger.info("轉譯完成, url = {}", request.getRequestURI());

        if (StringUtils.isEmpty(token) && !requestURL.contains(LOGIN_URI)) {
            //返回錯誤提示
            currentContext.setSendZuulResponse(false); //false不會繼續往下執行 不會調用服務接口 網關直接響應給客戶了
            currentContext.setResponseBody(INVALID_TOKEN);
            currentContext.setResponseStatusCode(401);
            return null;
        }
        //否則正常執行 調用服務接口...
        return null;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章