JWT 401仅在通过Zuul命中时

时间:2018-11-06 14:58:31

标签: spring-boot tomcat oauth jwt netflix-zuul

当我通过以下方式直接命中时: http://localhost:8081/dictionary/api/dictionary/items

Headers类似:

Authorization,其值例如: Bearer {token}

并带有自定义标头: X-Company的值为X

我得到输出:"Test"

enter image description here

没关系-此 dictionary模块中的代码为:

控制器:

@RestController
@RequestMapping("/api/dictionary/")
@PreAuthorize("isAuthenticated()")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DictionaryController {

    @RequestMapping(value = "items", method = GET)
    public String getAllDictionaryItems() {
        return "Test";
    }
}

拦截器:

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class CompanyFeignInterceptor implements RequestInterceptor {

    private static final String COMPANY_HEADER = "X-Company";

    private final MultitenancyHelper multitenancyHelper;

    @Override
    public void apply(RequestTemplate template) {
        String selectedCompany = null;
        if (!template.url().contains("/login")) {
            selectedCompany = multitenancyHelper.getSelectedCompany();
        }
        if (selectedCompany != null) {
            template.header(COMPANY_HEADER, selectedCompany);
        }
    }
}

另一个拦截器:

@Slf4j
class CompanyHandlerInterceptor extends HandlerInterceptorAdapter {

    private static final String COMPANY_HEADER = "X-Company";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) {
        String headerContent = decodeInsuranceCompany(request.getHeader(COMPANY_HEADER));
        Optional<Authentication> authenticationOpt = Optional
                .ofNullable(SecurityContextHolder.getContext().getAuthentication());
        checkState(authenticationOpt.isPresent(),
                "No authentication information available in securityContext!");

        //noinspection OptionalGetWithoutIsPresent
        Object principal = authenticationOpt.get().getPrincipal();
        if (!(principal instanceof UserDetailsImpl)) {
            return true;
        }
        UserDetailsImpl userDetails = (UserDetailsImpl) principal;

        boolean hasAccess = false;
        if (headerContent != null) {
            Set<String> selectedCompanies = Sets.newHashSet(headerContent.split(";"));

            Set<String> intersect = userDetails.getCompanies().stream()
                    .filter(selectedCompanies::contains).collect(Collectors.toSet());
            //set only these companies for which user has access
            userDetails.setSelectedCompanies(intersect);

            //if user hasn't got access to any company, throw exception
            hasAccess = !intersect.isEmpty();
        }

        if (!hasAccess) {
            log.error("User [{}] is not assigned to company [{}]", userDetails.getUsername(),
                    headerContent);
            throw new NoPermissionToCompanyException(userDetails.getUsername(), headerContent);
        }

        return true;
    }


    private String decodeInsuranceCompany(String insuranceCompany) {
        if (insuranceCompany != null) {
            return CodingUtil.decodeStringInUTF8(insuranceCompany);
        }
        return null;
    }
}

配置:

@Configuration
@EnableWebMvc
@EnableSwagger2
@Import(WebSecurityConfiguration.class)
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Bean
    public LoggingFilter loggingFilter() {
        return new LoggingFilter();
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jackson2Converter());
        converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        converters.add(new ByteArrayHttpMessageConverter());
    }

    @Bean
    public HttpMessageConverters messageConverters() {
        return new HttpMessageConverters(new ArrayList<>()) {
            @Override
            protected List<HttpMessageConverter<?>> postProcessConverters(
                    List<HttpMessageConverter<?>> converters) {
                if (converters == null) {
                    converters = new ArrayList<>();
                }

                converters.add(0, jackson2Converter());
                return converters;
            }
        };
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InsuranceCompanyHandlerInterceptor());
    }

    private MappingJackson2HttpMessageConverter jackson2Converter() {
        return new MappingJackson2HttpMessageConverter(new Jackson2ObjectMapperBuilder()
                .timeZone(TimeZone.getTimeZone("GMT+1:00"))
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .modules(new JavaTimeModule(), new Jdk8Module())
                .failOnUnknownProperties(false)
                .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build()
        );
    }

    @Bean
    @ConditionalOnProperty(prefix = "timeMachine.time.changing", name = "enabled")
    public TimeMachineController timeMachineController(TimeMachineSender sender) {
        return new TimeMachineController(sender);
    }

    @Bean
    @ConditionalOnMissingBean(RequestContextFilter.class)
    public RequestContextFilter requestContextFilter() {
        return new RequestContextFilter();
    }

    @Bean
    public FilterRegistrationBean requestContextFilterChainRegistration(
            @Qualifier("requestContextFilter") Filter securityFilter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
        registration.setName("requestContextFilter");
        return registration;
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("GET", "HEAD", "PUT", "POST", "DELETE");
    }

}

我还有另一个模块-Api 我在apiGateway作为contextPath的tomcat上运行了它。

输出为401-我想找出原因。令牌与上面的令牌相同,因此是有效令牌。

当我将SpringBoot从1.5.x更改为2.0.3时出现了问题。

enter image description here

zuul-routes-ext.properties

zuul.routes.dictionary.path=/dictionary/**
zuul.routes.dictionary.url=http://localhost:8081/dictionary/
zuul.routes.dictionary.sensitiveHeaders=Cookie,Set-Cookie

Ouath配置:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class XOAuth2AuthenticationEntryPoint extends AbstractOAuth2SecurityExceptionHandler implements AuthenticationEntryPoint {

    private String typeName = "Bearer";
    private String realmName = "oauth";

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        doHandle(request, response, authException);
    }

    @Override
    protected ResponseEntity<OAuth2Exception> enhanceResponse(ResponseEntity<OAuth2Exception> response, Exception exception) {
        HttpHeaders headers = response.getHeaders();
        String existing = null;
        if (headers.containsKey("WWW-Authenticate")) {
            existing = extractTypePrefix(headers.getFirst("WWW-Authenticate"));
        }

        StringBuilder builder = new StringBuilder();
        builder.append(this.typeName + " ");
        builder.append("realm=\"" + this.realmName + "\"");
        if (existing != null) {
            builder.append(", " + existing);
        }

        HttpHeaders update = new HttpHeaders();
        update.putAll(response.getHeaders());
        update.set("WWW-Authenticate", builder.toString());
        update.set("Access-Control-Allow-Origin", "*");
        return new ResponseEntity(response.getBody(), update, response.getStatusCode());
    }

    private String extractTypePrefix(String header) {
        String existing = header;
        String[] tokens = header.split(" +");
        if (tokens.length > 1 && !tokens[0].endsWith(",")) {
            existing = StringUtils.arrayToDelimitedString(tokens, " ").substring(header.indexOf(" ") + 1);
        }

        return existing;
    }
}

WebConfig

@Configuration
@EnableZuulProxy
@PropertySource(value = {"classpath:x-gateway.properties",
        "classpath:x-nodes.properties",
        "classpath:zuul-routes.properties",
        "classpath:x-gateway-ext.properties",
        "classpath:zuul-routes-ext.properties"},
        ignoreResourceNotFound = true)
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("GET", "HEAD", "PUT", "POST", "DELETE");
    }

}

ResourceServerConfiguration

@Configuration
@EnableResourceServer
@Order(Integer.MAX_VALUE)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().authenticationEntryPoint(new XOAuth2AuthenticationEntryPoint());
        http.httpBasic().authenticationEntryPoint(new XOAuth2AuthenticationEntryPoint());
        http.authorizeRequests()
                .antMatchers("/index.html", "/home.html").permitAll()
                .anyRequest().authenticated();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.authenticationEntryPoint(new XOAuth2AuthenticationEntryPoint());
    }
}

RequestContextFilterConfiguration

@Configuration
public class RequestContextFilterConfiguration {

    @Bean
    @ConditionalOnMissingBean(RequestContextFilter.class)
    public RequestContextFilter requestContextFilter() {
        return new RequestContextFilter();
    }

    @Bean
    public FilterRegistrationBean requestContextFilterChainRegistration(
            @Qualifier("requestContextFilter") Filter securityFilter) {
        FilterRegistrationBean registration = new FilterRegistrationBean(securityFilter);
        registration.setName("requestContextFilter");
        return registration;
    }
}

CorsRequestIdFilter

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class CorsRequestIdFilter extends OncePerRequestFilter {

    private static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        if (isCorsRequest(request) && isPreFlightRequest(request)) {

            Set<String> headers = new HashSet<>();
            headers.add("Authorization");
            headers.add("X-Company");

            String requestHeader = request.getHeader("Access-Control-Request-Headers");
            if (requestHeader != null) {
                headers.addAll(Arrays.asList(requestHeader.split(",")));
            }

            response.addHeader("Access-Control-Allow-Origin", "*");
            response.addHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
            response.addHeader("Access-Control-Max-Age", "3600");
            response.addHeader("Access-Control-Allow-Headers",
                    headers.stream().collect(Collectors.joining(",")));

            return;
        }

        filterChain.doFilter(request, response);
    }

    private boolean isCorsRequest(HttpServletRequest request) {
        return (request.getHeader(HttpHeaders.ORIGIN) != null);
    }

    private boolean isPreFlightRequest(HttpServletRequest request) {
        return (isCorsRequest(request) && request.getMethod().equals(HttpMethod.OPTIONS.name()) &&
                request.getHeader(ACCESS_CONTROL_REQUEST_METHOD) != null);
    }
}

此模块pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api</artifactId>
    <version>4.6.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <parent>
        <groupId>Y</groupId>
        <artifactId>Z-parent</artifactId>
        <version>4.6.0-SNAPSHOT</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.0.3.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-parent</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-collections4</artifactId>
                <version>${commons-collections4.version}</version>
            </dependency>
            <dependency>
                <groupId>net.logstash.logback</groupId>
                <artifactId>logstash-logback-encoder</artifactId>
                <version>4.11</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>B</groupId>
            <artifactId>A-common-api</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <!-- SECURITY -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>${spring-security-oauth2.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>${spring-security-jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>A</groupId>
            <artifactId>B</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

    <profiles>
        <profile>
            <id>default</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-war-plugin</artifactId>
                        <version>${maven-war-plugin-version}</version>
                        <configuration>
                            <failOnMissingWebXml>false</failOnMissingWebXml>
                            <archive>
                                <manifestEntries>
                                    <Sys-Version>${project.version}</Sys-Version>
                                    <!-- Variables set by Hudson -->
                                    <Build-Number>${BUILD_NUMBER}</Build-Number>
                                    <Build-Date>${BUILD_TIMESTAMP}</Build-Date>
                                    <!-- Next two for potential usage in the next AppInfo versions -->
                                    <Job-Name>${JOB_NAME}</Job-Name>
                                    <Git-Branch>${GIT_BRANCH}</Git-Branch>
                                    <Git-Commit>${GIT_COMMIT}</Git-Commit>
                                </manifestEntries>
                            </archive>
                        </configuration>
                    </plugin>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <version>2.0.3.RELEASE</version>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <!-- Enable this profile to run in IntelliJ. IntelliJ excludes provided dependencies from compile by default. -->
            <id>intellij</id>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                    <scope>compile</scope>
                </dependency>
            </dependencies>
        </profile>
    </profiles>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>http://repo.spring.io/libs-snapshot-local</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/libs-milestone-local</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>http://repo.spring.io/libs-release-local</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>http://repo.spring.io/libs-snapshot-local</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>http://repo.spring.io/libs-milestone-local</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

0 个答案:

没有答案