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