【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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章