久久久久久久av_日韩在线中文_看一级毛片视频_日本精品二区_成人深夜福利视频_武道仙尊动漫在线观看

如何讓 @WebMvcTest 與 OAuth 一起使用?

How to get @WebMvcTest work with OAuth?(如何讓 @WebMvcTest 與 OAuth 一起使用?)
本文介紹了如何讓 @WebMvcTest 與 OAuth 一起使用?的處理方法,對大家解決問題具有一定的參考價值,需要的朋友們下面隨著小編來一起學習吧!

問題描述

我很難讓我的控制器單元測試正常工作,因為 IMO,Spring doc 如果使用 OAuth 是不夠的.在我的例子中,它是帶有 JWT 的 Oauth2.

I just had hard time to get my controllers unit tests working because, IMO, what is in Spring doc is not enough if using OAuth. In my case, it's Oauth2 with JWT.

我嘗試使用 @WithMockUser@WithUserDetails 甚至用 @WithSecurityContext 和自定義的 UserSecurityContextFactory<定義我自己的注解/code> 但在評估安全表達式時總是在 UserSecurityContext 中獲得匿名用戶,無論我在工廠中將測試上下文設置為什么......

I tried to use @WithMockUser, @WithUserDetails and even define my own annotation with @WithSecurityContext and a custom UserSecurityContextFactory but always got anonymous user in the UserSecurityContext when security expression where evaluated, whatever I set the test context to in my factory...

我提出了我剛剛提出的解決方案,但由于我不確定模擬 TokenService 是最有效/最干凈的方法,請隨時提供更好的解決方案.

I propose the solution I came to just under, but as I'm not sure mocking the TokenService is the most efficient / clean way to go, please feel free to provide better.

推薦答案

[2019 年 5 月編輯]
以下解決方案特定于 spring-security-oauth2,現已棄用.
我寫了 一個 lib 來實現與 Spring5 相同的目標,其中一些是對 spring-安全測試 5.2.他們選擇僅集成 JWT 流 API,因此如果您需要測試服務(需要使用注釋)或使用不透明令牌自省,您可能需要稍微瀏覽一下我的 repo...


The Solution below is specific to spring-security-oauth2 which is now deprecated.
I wrote a lib to achieve the same goal with Spring5, some of which is contributed to spring-security-test 5.2. They chose to integrate the JWT flow API only, so if you need to test a service (requires the use of an annotation) or use opaque tokens introspection, you might need to browse my repo a bit...

[2019 年 7 月編輯]
我現在發布我的spring-addons".Spring 5 的庫到 maven-central,其中大大提高可用性.
源代碼和自述文件仍在 github 上.


I now publish my "spring-addons" libs for Spring 5 to maven-central, which greatly improve usability.
Source and READMEs still on github.

[spring-security-oauth2 的解決方案]
我迭代的解決方案是結合一個虛擬的授權".請求中的標頭帶有攔截它的模擬令牌服務(如果您查看編輯堆棧,經過多次嘗試).

[solution for spring-security-oauth2]
The solution I iterated to is combining a dummy "Authorization" header in requests with a mocked token service intercepting it (after quite a few tries if you look at edits stack).

我在 Github 上的 lib 你可以找到示例 OAuth2 控制器測試 那里.

I provide with complete helpers source in a lib on Github and you can find sample OAuth2 controller test there.

簡而言之:沒有授權標頭->ResourceServerTokenServices 未觸發 ->SecurityContext 在 OAuth 堆棧中將是匿名的(無論您嘗試使用 @WithMockUser 或類似方法將其設置為什么).

To make it short: no Authorization header -> ResourceServerTokenServices is not triggered -> SecurityContext will be anonymous in OAuth stack (whatever you try to set it to with @WithMockUser or alike).

這里有兩種情況:

  • 您正在編寫集成測試,提供有效令牌并讓真正的令牌服務完成工作并提供包含在此令牌中的身份驗證
  • 您正在編寫單元測試、我的案例和模擬令牌服務,以便它返回模擬身份驗證

一種類似的方法,我在拉了幾天頭發并從頭開始構建它后了解到,已經描述了這里.我只是在 @WebMvcTests 的模擬 Oauth2Authentication 配置和工具中走得更遠.

A similar approach, I understood after pulling my hair for a few days and building this from ground up, has already been described here. I just went further in mocked Oauth2Authentication configuration and tooling for @WebMvcTests.

使用示例

由于這篇文章很長,公開了一個涉及相當多代碼的解決方案,讓我們從結果開始,以便您決定是否值得閱讀;)

As this post is long, exposing a solution involving quite some code, lets get started with the result so that you can decide if it's worth reading ;)

@WebMvcTest(MyController.class) // Controller to unit-test
@Import(WebSecurityConfig.class) // your class extending WebSecurityConfigurerAdapter
public class MyControllerTest extends OAuth2ControllerTest {

    @Test
    public void testWithUnauthenticatedClient() throws Exception {
        api.post(payload, "/endpoint")
                .andExpect(...);
    }

    @Test
    @WithMockOAuth2Client
    public void testWithDefaultClient() throws Exception {
        api.get("/endpoint")
                .andExpect(...);
    }

    @Test
    @WithMockOAuth2User
    public void testWithDefaultClientOnBehalfDefaultUser() throws Exception {
            MockHttpServletRequestBuilder req = api.postRequestBuilder(null, "/uaa/refresh")
                .header("refresh_token", JWT_REFRESH_TOKEN);

        api.perform(req)
                .andExpect(status().isOk())
                .andExpect(...)
    }

    @Test
    @WithMockOAuth2User(
        client = @WithMockOAuth2Client(
                clientId = "custom-client",
                scope = {"custom-scope", "other-scope"},
                authorities = {"custom-authority", "ROLE_CUSTOM_CLIENT"}),
        user = @WithMockUser(
                username = "custom-username",
                authorities = {"custom-user-authority"}))
    public void testWithCustomClientOnBehalfCustomUser() throws Exception {
        api.get(MediaType.APPLICATION_ATOM_XML, "/endpoint")
                .andExpect(status().isOk())
                .andExpect(xpath(...));
    }
}

時髦,不是嗎?

附:apiMockMvcHelper 的一個實例,它是我自己的 MockMvc 包裝器,在本文末尾提供.

P.S. api is an instance of MockMvcHelper, a wrapper of my own for MockMvc, provided at the end of this post.

@WithMockOAuth2Client 模擬僅客戶端身份驗證(不涉及最終用戶)

@WithMockOAuth2Client to simulate client only authentication (no end-user involved)

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2Client.WithMockOAuth2ClientSecurityContextFactory.class)
public @interface WithMockOAuth2Client {

    String clientId() default "web-client";

    String[] scope() default {"openid"};

    String[] authorities() default {};

    boolean approved() default true;

    class WithMockOAuth2ClientSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Client> {

        public static OAuth2Request getOAuth2Request(final WithMockOAuth2Client annotation) {
            final Set<? extends GrantedAuthority> authorities = Stream.of(annotation.authorities())
                    .map(auth -> new SimpleGrantedAuthority(auth))
                    .collect(Collectors.toSet());

            final Set<String> scope = Stream.of(annotation.scope())
                    .collect(Collectors.toSet());

            return new OAuth2Request(
                    null,
                    annotation.clientId(),
                    authorities,
                    annotation.approved(),
                    scope,
                    null,
                    null,
                    null,
                    null);
        }

        @Override
        public SecurityContext createSecurityContext(final WithMockOAuth2Client annotation) {
            final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
            ctx.setAuthentication(new OAuth2Authentication(getOAuth2Request(annotation), null));
            SecurityContextHolder.setContext(ctx);
            return ctx;
        }
    }

}

@WithMockOAuth2User 代表最終用戶模擬客戶端身份驗證

@WithMockOAuth2User to simulate client authenticating on behalf of an end-user

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2User.WithMockOAuth2UserSecurityContextFactory.class)
public @interface WithMockOAuth2User {

    WithMockOAuth2Client client() default @WithMockOAuth2Client();

    WithMockUser user() default @WithMockUser();

    class WithMockOAuth2UserSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2User> {

        /**
         * Sadly, #WithMockUserSecurityContextFactory is not public,
         * so re-implement mock user authentication creation
         *
         * @param user
         * @return an Authentication with provided user details
         */
        public static UsernamePasswordAuthenticationToken getUserAuthentication(final WithMockUser user) {
            final String principal = user.username().isEmpty() ? user.value() : user.username();

            final Stream<String> grants = user.authorities().length == 0 ?
                    Stream.of(user.roles()).map(r -> "ROLE_" + r) :
                    Stream.of(user.authorities());

            final Set<? extends GrantedAuthority> userAuthorities = grants
                    .map(auth -> new SimpleGrantedAuthority(auth))
                    .collect(Collectors.toSet());

            return new UsernamePasswordAuthenticationToken(
                    new User(principal, user.password(), userAuthorities),
                    principal + ":" + user.password(),
                    userAuthorities);
        }

        @Override
        public SecurityContext createSecurityContext(final WithMockOAuth2User annotation) {
            final SecurityContext ctx = SecurityContextHolder.createEmptyContext();
            ctx.setAuthentication(new OAuth2Authentication(
                    WithMockOAuth2Client.WithMockOAuth2ClientSecurityContextFactory.getOAuth2Request(annotation.client()),
                    getUserAuthentication(annotation.user())));
            SecurityContextHolder.setContext(ctx);
            return ctx;
        }
    }
}

OAuth2MockMvcHelper 幫助構建帶有預期授權標頭的測試請求

OAuth2MockMvcHelper helps build test requests with expected Authorization header

public class OAuth2MockMvcHelper extends MockMvcHelper {
    public static final String VALID_TEST_TOKEN_VALUE = "test.fake.jwt";

    public OAuth2MockMvcHelper(
            final MockMvc mockMvc,
            final ObjectFactory<HttpMessageConverters> messageConverters,
            final MediaType defaultMediaType) {
        super(mockMvc, messageConverters, defaultMediaType);
    }

    /**
     * Adds OAuth2 support: adds an Authorisation header to all request builders
     * if there is an OAuth2Authentication in test security context.
     * 
     * /! Make sure your token services recognize this dummy "VALID_TEST_TOKEN_VALUE" token as valid during your tests /!
     *
     * @param contentType should be not-null when issuing request with body (POST, PUT, PATCH), null otherwise
     * @param accept      should be not-null when issuing response with body (GET, POST, OPTION), null otherwise
     * @param method
     * @param urlTemplate
     * @param uriVars
     * @return a request builder with minimal info you can tweak further (add headers, cookies, etc.)
     */
    @Override
    public MockHttpServletRequestBuilder requestBuilder(
            Optional<MediaType> contentType,
            Optional<MediaType> accept,
            HttpMethod method,
            String urlTemplate,
            Object... uriVars) {
        final MockHttpServletRequestBuilder builder = super.requestBuilder(contentType, accept, method, urlTemplate, uriVars);
        if (SecurityContextHolder.getContext().getAuthentication() instanceof OAuth2Authentication) {
            builder.header("Authorization", "Bearer " + VALID_TEST_TOKEN_VALUE);
        }
        return builder;
    }
}

OAuth2ControllerTest 控制器單元測試的父級

OAuth2ControllerTest a parent for controllers unit-tests

@RunWith(SpringRunner.class)
@Import(OAuth2MockMvcConfig.class)
public class OAuth2ControllerTest {

    @MockBean
    private ResourceServerTokenServices tokenService;

    @Autowired
    protected OAuth2MockMvcHelper api;

    @Autowired
    protected SerializationHelper conv;

    @Before
    public void setUpTokenService() {
        when(tokenService.loadAuthentication(api.VALID_TEST_TOKEN_VALUE))
                .thenAnswer(invocation -> SecurityContextHolder.getContext().getAuthentication());
    }
}

@TestConfiguration
class OAuth2MockMvcConfig {

    @Bean
    public SerializationHelper serializationHelper(ObjectFactory<HttpMessageConverters> messageConverters) {
        return new SerializationHelper(messageConverters);
    }

    @Bean
    public OAuth2MockMvcHelper mockMvcHelper(
            MockMvc mockMvc,
            ObjectFactory<HttpMessageConverters> messageConverters,
            @Value("${controllers.default-media-type:application/json;charset=UTF-8}") MediaType defaultMediaType) {
        return new OAuth2MockMvcHelper(mockMvc, messageConverters, defaultMediaType);
    }

}

上面提到但與 OAuth2 測試沒有直接關系的工具

/**
 * Wraps MockMvc to further ease interaction with tested API:
 * provides with:<ul>
 * <li>many request shortcuts for simple cases (see get, post, put, patch, delete methods)</li>
 * <li>perfom method along with request builder initialisation shortcuts (see getRequestBuilder, etc.) when more control is required (additional headers, ...)</li>
 * </ul>
 */
public class MockMvcHelper {

    private final MockMvc mockMvc;

    private final MediaType defaultMediaType;

    protected final SerializationHelper conv;

    public MockMvcHelper(MockMvc mockMvc, ObjectFactory<HttpMessageConverters> messageConverters, MediaType defaultMediaType) {
        this.mockMvc = mockMvc;
        this.conv = new SerializationHelper(messageConverters);
        this.defaultMediaType = defaultMediaType;
    }

    /**
     * Generic request builder which adds relevant "Accept" and "Content-Type" headers
     *
     * @param contentType should be not-null when issuing request with body (POST, PUT, PATCH), null otherwise
     * @param accept      should be not-null when issuing response with body (GET, POST, OPTION), null otherwise
     * @param method
     * @param urlTemplate
     * @param uriVars
     * @return a request builder with minimal info you can tweak further: add headers, cookies, etc.
     */
    public MockHttpServletRequestBuilder requestBuilder(
            Optional<MediaType> contentType,
            Optional<MediaType> accept,
            HttpMethod method,
            String urlTemplate,
            Object... uriVars) {
        final MockHttpServletRequestBuilder builder = request(method, urlTemplate, uriVars);
        contentType.ifPresent(builder::contentType);
        accept.ifPresent(builder::accept);
        return builder;
    }

    public ResultActions perform(MockHttpServletRequestBuilder request) throws Exception {
        return mockMvc.perform(request);
    }

    /* GET */
    public MockHttpServletRequestBuilder getRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
        return requestBuilder(Optional.empty(), Optional.of(accept), HttpMethod.GET, urlTemplate, uriVars);
    }

    public MockHttpServletRequestBuilder getRequestBuilder(String urlTemplate, Object... uriVars) {
        return getRequestBuilder(defaultMediaType, urlTemplate, uriVars);
    }

    public ResultActions get(MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(getRequestBuilder(accept, urlTemplate, uriVars));
    }

    public ResultActions get(String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(getRequestBuilder(urlTemplate, uriVars));
    }

    /* POST */
    public <T> MockHttpServletRequestBuilder postRequestBuilder(final T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
        return feed(
                requestBuilder(Optional.of(contentType), Optional.of(accept), HttpMethod.POST, urlTemplate, uriVars),
                payload,
                contentType);
    }

    public <T> MockHttpServletRequestBuilder postRequestBuilder(final T payload, String urlTemplate, Object... uriVars) throws Exception {
        return postRequestBuilder(payload, defaultMediaType, defaultMediaType, urlTemplate, uriVars);
    }

    public <T> ResultActions post(final T payload, MediaType contentType, MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(postRequestBuilder(payload, contentType, accept, urlTemplate, uriVars));
    }

    public <T> ResultActions post(final T payload, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(postRequestBuilder(payload, urlTemplate, uriVars));
    }


    /* PUT */
    public <T> MockHttpServletRequestBuilder putRequestBuilder(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
        return feed(
                requestBuilder(Optional.of(contentType), Optional.empty(), HttpMethod.PUT, urlTemplate, uriVars),
                payload,
                contentType);
    }

    public <T> MockHttpServletRequestBuilder putRequestBuilder(final T payload, String urlTemplate, Object... uriVars) throws Exception {
        return putRequestBuilder(payload, defaultMediaType, urlTemplate, uriVars);
    }

    public <T> ResultActions put(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(putRequestBuilder(payload, contentType, urlTemplate, uriVars));
    }

    public <T> ResultActions put(final T payload, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(putRequestBuilder(payload, urlTemplate, uriVars));
    }


    /* PATCH */
    public <T> MockHttpServletRequestBuilder patchRequestBuilder(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
        return feed(
                requestBuilder(Optional.of(contentType), Optional.empty(), HttpMethod.PATCH, urlTemplate, uriVars),
                payload,
                contentType);
    }

    public <T> MockHttpServletRequestBuilder patchRequestBuilder(final T payload, String urlTemplate, Object... uriVars) throws Exception {
        return patchRequestBuilder(payload, defaultMediaType, urlTemplate, uriVars);
    }

    public <T> ResultActions patch(final T payload, MediaType contentType, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(patchRequestBuilder(payload, contentType, urlTemplate, uriVars));
    }

    public <T> ResultActions patch(final T payload, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(patchRequestBuilder(payload, urlTemplate, uriVars));
    }


    /* DELETE */
    public MockHttpServletRequestBuilder deleteRequestBuilder(String urlTemplate, Object... uriVars) {
        return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.DELETE, urlTemplate, uriVars);
    }

    public ResultActions delete(String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(deleteRequestBuilder(urlTemplate, uriVars));
    }


    /* HEAD */
    public MockHttpServletRequestBuilder headRequestBuilder(String urlTemplate, Object... uriVars) {
        return requestBuilder(Optional.empty(), Optional.empty(), HttpMethod.HEAD, urlTemplate, uriVars);
    }

    public ResultActions head(String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(headRequestBuilder(urlTemplate, uriVars));
    }


    /* OPTION */
    public MockHttpServletRequestBuilder optionRequestBuilder(MediaType accept, String urlTemplate, Object... uriVars) {
        return requestBuilder(Optional.empty(), Optional.of(accept), HttpMethod.OPTIONS, urlTemplate, uriVars);
    }

    public MockHttpServletRequestBuilder optionRequestBuilder(String urlTemplate, Object... uriVars) {
        return requestBuilder(Optional.empty(), Optional.of(defaultMediaType), HttpMethod.OPTIONS, urlTemplate, uriVars);
    }

    public ResultActions option(MediaType accept, String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(optionRequestBuilder(accept, urlTemplate, uriVars));
    }

    public ResultActions option(String urlTemplate, Object... uriVars) throws Exception {
        return mockMvc.perform(optionRequestBuilder(urlTemplate, uriVars));
    }

    /**
     * Adds serialized payload to request content
     *
     * @param request
     * @param payload
     * @param mediaType
     * @param <T>
     * @return the request with provided payload as content
     * @throws Exception if things go wrong (no registered serializer for payload type and asked MediaType, serialization failure, ...)
     */
    public <T> MockHttpServletRequestBuilder feed(
            MockHttpServletRequestBuilder request,
            final T payload,
            final MediaType mediaType) throws Exception {
        if (payload == null) {
            return request;
        }

        final SerializationHelper.ByteArrayHttpOutputMessage msg = conv.outputMessage(payload, mediaType);
        return request
                .headers(msg.headers)
                .content(msg.out.toByteArray());
    }
}

/**
 * Serialize objects to given media type using registered message converters
 */
public class SerializationHelper {

    private final ObjectFactory<HttpMessageConverters> messageConverters;

    public SerializationHelper(ObjectFactory<HttpMessageConverters> messageConverters) {
        this.messageConverters = messageConverters;
    }

    public <T> ByteArrayHttpOutputMessage outputMessage(final T payload, final MediaType mediaType) throws Exception {
        if (payload == null) {
            return null;
        }

        List<HttpMessageConverter<?>> relevantConverters = messageConverters.getObject().getConverters().stream()
                .filter(converter -> converter.canWrite(payload.getClass(), mediaType))
                .collect(Collectors.toList());

        final ByteArrayHttpOutputMessage converted = new ByteArrayHttpOutputMessage();
        boolean isConverted = false;
        for (HttpMessageConverter<?> converter : relevantConverters) {
            try {
                ((HttpMessageConverter<T>) converter).write(payload, mediaType, converted);
                isConverted = true; //won't be reached if a conversion error occurs
                break; //stop iterating over converters after first successful conversion
            } catch (IOException e) {
                //swallow exception so that next converter is tried
            }
        }

        if (!isConverted) {
            throw new Exception("Could not convert " + payload.getClass() + " to " + mediaType.toString());
        }

        return converted;
    }

    /**
     * Provides a String representation of provided payload
     *
     * @param payload
     * @param mediaType
     * @param <T>
     * @return
     * @throws Exception if things go wrong (no registered serializer for payload type and asked MediaType, serialization failure, ...)
     */
    public <T> String asString(T payload, MediaType mediaType) throws Exception {
        return payload == null ?
                null :
                outputMessage(payload, mediaType).out.toString();
    }

    public <T> String asJsonString(T payload) throws Exception {
        return asString(payload, MediaType.APPLICATION_JSON_UTF8);
    }

    public static final class ByteArrayHttpOutputMessage implements HttpOutputMessage {
        public final ByteArrayOutputStream out = new ByteArrayOutputStream();
        public final HttpHeaders headers = new HttpHeaders();

        @Override
        public OutputStream getBody() {
            return out;
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}

這篇關于如何讓 @WebMvcTest 與 OAuth 一起使用?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!

【網站聲明】本站部分內容來源于互聯網,旨在幫助大家更快的解決問題,如果有圖片或者內容侵犯了您的權益,請聯系我們刪除處理,感謝您的支持!

相關文檔推薦

How to wrap text around components in a JTextPane?(如何在 JTextPane 中的組件周圍環繞文本?)
MyBatis, how to get the auto generated key of an insert? [MySql](MyBatis,如何獲取插入的自動生成密鑰?[MySql])
Inserting to Oracle Nested Table in Java(在 Java 中插入 Oracle 嵌套表)
Java: How to insert CLOB into oracle database(Java:如何將 CLOB 插入 oracle 數據庫)
Why does Spring-data-jdbc not save my Car object?(為什么 Spring-data-jdbc 不保存我的 Car 對象?)
Use threading to process file chunk by chunk(使用線程逐塊處理文件)
主站蜘蛛池模板: 91精品国产综合久久婷婷香蕉 | 国产中文视频 | 欧美精品一区免费 | 九九九视频在线观看 | 一区二区三区在线播放视频 | 91视频官网 | 免费视频久久久久 | 2022国产精品 | 久久久噜噜噜久久中文字幕色伊伊 | 欧美精品一区在线 | 亚洲成av| 亚洲午夜小视频 | 午夜色婷婷 | 一级网站| 91视频在线观看免费 | 亚洲视频欧美视频 | 黑人巨大精品欧美一区二区免费 | 日韩美香港a一级毛片免费 国产综合av | a在线观看免费 | 亚洲天堂一区 | 国产精品久久久久久久久动漫 | 在线免费中文字幕 | 国产性生活一级片 | 色婷婷久久久亚洲一区二区三区 | 在线国产一区 | 亚洲日本一区二区三区四区 | 精品国产乱码久久久久久a丨 | 亚洲精品自拍 | 一级毛片在线播放 | 亚洲一区二区在线播放 | 国产成都精品91一区二区三 | 91精品久久久久久久久99蜜臂 | 黄色片免费看视频 | 中国大陆高清aⅴ毛片 | 国产专区在线 | 国产精品免费小视频 | 日韩精品免费一区二区在线观看 | 国产在视频一区二区三区吞精 | 一级黄色裸片 | 国产精品一区二区三区在线 | 亚洲精品无人区 |