請求 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;
}
}