中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

企業(yè)網(wǎng)站的主要類型廣東的seo產(chǎn)品推廣服務(wù)公司

企業(yè)網(wǎng)站的主要類型,廣東的seo產(chǎn)品推廣服務(wù)公司,免費(fèi)網(wǎng)站優(yōu)化怎么做,深圳網(wǎng)絡(luò)安全公司目錄 示例 大體流程 配置器 OAuth2AuthorizationServerConfigurer createConfigurers ???????configure 過(guò)濾器 ???????OAuth2TokenEndpointFilter ???????自定義配置 配置示例 ???????tokenEndpoint ???????自定義請(qǐng)求參數(shù)轉(zhuǎn)換…

目錄

示例

大體流程

配置器

OAuth2AuthorizationServerConfigurer

createConfigurers

???????configure

過(guò)濾器

???????OAuth2TokenEndpointFilter

???????自定義配置

配置示例

???????tokenEndpoint

???????自定義請(qǐng)求參數(shù)轉(zhuǎn)換器

CustomDelegatingAuthenticationConverter

???????PasswordGrantAuthenticationConverter

???????PasswordRefreshTokenAuthenticationConverter

自定義認(rèn)證提供者

PasswordGrantAuthenticationProvider

???????PasswordRefreshTokenAuthenticationProvider

時(shí)序圖


? ? ? ? 本篇文章我們來(lái)研究spring-security5框架如何實(shí)現(xiàn)OAuth2的密碼授權(quán)模式,即用戶向客戶端提供自己的用戶名和密碼,客戶端使用這些信息向“服務(wù)提供商”索要授權(quán)。

? ? ? ? 先從一個(gè)簡(jiǎn)單示例開始,如下圖所示:

示例

? ? ? ? 首先,新建一個(gè)config包用于存放spring-security通用配置;

? ? ? ? 然后,新建一個(gè)AuthSecurityConfig類,給AuthSecurityConfig類中加上@EnableWebSecurity 注解后,這樣便會(huì)自動(dòng)被 Spring發(fā)現(xiàn)并注冊(cè)。

@Configuration

@EnableWebSecurity

public class AuthSecurityConfig{

??@Bean

??@Order(1)

??public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, ??????????????????????????????????????????????????????????????????OAuth2AuthorizationService authorizationService, ??????????????????????????????????????????????????????????????OAuth2TokenGenerator<?> tokenGenerator) throws Exception {

??????OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =

????????????????new OAuth2AuthorizationServerConfigurer<>();

??????authorizationServerConfigurer.tokenEndpoint(tokenEndpoint ->

????????tokenEndpoint

.accessTokenRequestConverter(new CustomDelegatingAuthenticationConverter())

.authenticationProvider(new PasswordGrantAuthenticationProvider())

??????????.authenticationProvider(new PasswordRefreshTokenAuthenticationProvider()));

RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

??????http.requestMatcher(endpointsMatcher)

??????????.apply(authorizationServerConfigurer);

??????return http.build();

??}

? ? ? ? 在這里,首先實(shí)例化一個(gè)配置器OAuth2AuthorizationServerConfigurer對(duì)象;

? ? ? ? 然后,自定義accessTokenRequestConverter和authenticationProvider配置信息;

? ? ? ? 最后,將該配置器對(duì)象應(yīng)用到HttpSecurity 對(duì)象即可。

大體流程

? ? ? ? 點(diǎn)擊示例里的OAuth2AuthorizationServerConfigurer類,如下所示:

???????配置器

OAuth2AuthorizationServerConfigurer

public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBuilder<B>>

extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {

private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();

? ? ? ? 在這里,調(diào)用createConfigurers()方法,創(chuàng)建各種相關(guān)的子配置器對(duì)象。

? ? ? ? 點(diǎn)擊createConfigurers()方法,如下所示:

???????createConfigurers

private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {

Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = new LinkedHashMap<>();

configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));

configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));

configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));

configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));

configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));

configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));

return configurers;

}

? ? ? ? 在這里,我們看到程序創(chuàng)建了配置器OAuth2TokenEndpointConfigurer對(duì)象。配置器對(duì)象創(chuàng)建好了之后,重點(diǎn)要關(guān)注該對(duì)象的configure()方法。

? ? ? ? 點(diǎn)擊對(duì)象的configure()方法,如下所示:

???????configure

public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configurer {

??... ...

@Override

<B extends HttpSecurityBuilder<B>> void configure(B builder) {

AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);

ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);

OAuth2TokenEndpointFilter tokenEndpointFilter =

new OAuth2TokenEndpointFilter(

authenticationManager,

providerSettings.getTokenEndpoint());

if (this.accessTokenRequestConverter != null) {

tokenEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);

}

... ...

builder.addFilterAfter(postProcess(tokenEndpointFilter), FilterSecurityInterceptor.class);

}

...

}

? ? ? ?在這里,我們看到程序創(chuàng)建了過(guò)濾器OAuth2TokenEndpointFilter對(duì)象,并且傳入請(qǐng)求參數(shù)轉(zhuǎn)換器對(duì)象accessTokenRequestConverter和認(rèn)證管理器對(duì)象authenticationManager。

? ? ? ? 過(guò)濾器對(duì)象如下所示:

過(guò)濾器

???????OAuth2TokenEndpointFilter

public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter {

... ...

@Override

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

if (!this.tokenEndpointMatcher.matches(request)) {

filterChain.doFilter(request, response);

return;

}

try {

... ...

Authentication authorizationGrantAuthentication = this.authenticationConverter.convert(request);

... ...

OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =

(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);

this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);

} catch (OAuth2AuthenticationException ex) {

... ...

}

}

private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {

... ...

}

}

? ? ? ? 在這里,我們看到整個(gè)過(guò)濾邏輯主要包括請(qǐng)求參數(shù)轉(zhuǎn)換操作convert(request)和認(rèn)證操作authenticate(authorizationGrantAuthentication)兩個(gè)。

? ? ? ? 請(qǐng)求參數(shù)轉(zhuǎn)換操作由authenticationConverter對(duì)象來(lái)處理,該對(duì)象在實(shí)例化過(guò)濾器時(shí)傳入。

? ? ? ? 認(rèn)證操作由authenticationManager對(duì)象處理,該對(duì)象在實(shí)例化過(guò)濾器時(shí)傳入。

? ? ? ? 具體實(shí)現(xiàn)邏輯見后續(xù)章節(jié)。

???????自定義配置

配置示例

@Configuration

@EnableWebSecurity

public class AuthSecurityConfig{

??@Bean

??@Order(1)

??public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http, ??????????????????????????????????????????????????????????????????OAuth2AuthorizationService authorizationService, ??????????????????????????????????????????????????????????????OAuth2TokenGenerator<?> tokenGenerator) throws Exception {

??????OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =

????????????????new OAuth2AuthorizationServerConfigurer<>();

??????authorizationServerConfigurer.tokenEndpoint(tokenEndpoint ->

????????tokenEndpoint

.accessTokenRequestConverter(new CustomDelegatingAuthenticationConverter())

.authenticationProvider(new PasswordGrantAuthenticationProvider())

??????????.authenticationProvider(new PasswordRefreshTokenAuthenticationProvider()));

RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();

??????http.requestMatcher(endpointsMatcher)

??????????.apply(authorizationServerConfigurer);

??????return http.build();

??}

? ? ? ?在這里,通過(guò)調(diào)用tokenEndpoint()方法實(shí)現(xiàn)對(duì)請(qǐng)求參數(shù)轉(zhuǎn)換器和認(rèn)證提供者的自定義。傳入的匿名內(nèi)部類,采用鏈?zhǔn)秸{(diào)用的方式先自定義請(qǐng)求參數(shù)轉(zhuǎn)換器CustomDelegatingAuthenticationConverter,再自定義認(rèn)證提供者PasswordGrantAuthenticationProvider和PasswordRefreshTokenAuthenticationProvider。

???????tokenEndpoint

public OAuth2AuthorizationServerConfigurer<B> tokenEndpoint(Customizer<OAuth2TokenEndpointConfigurer> tokenEndpoint(Customizer) {

tokenEndpointCustomizer.customize(getConfigurer(OAuth2TokenEndpointConfigurer.class));

return this;

}

? ? ? ?在這里,通過(guò)調(diào)用getConfigurer()方法獲取配置器實(shí)例OAuth2TokenEndpointConfigurer,然后將該實(shí)例傳入給匿名內(nèi)部類的接口方法。

???????自定義請(qǐng)求參數(shù)轉(zhuǎn)換器

CustomDelegatingAuthenticationConverter

? ? ? ? 這是一個(gè)轉(zhuǎn)換器的代表類,代表如下兩個(gè)轉(zhuǎn)換器類:PasswordGrantAuthenticationConverter、PasswordRefreshTokenAuthenticationConverter。

@Slf4j

public class CustomDelegatingAuthenticationConverter implements AuthenticationConverter {

????private final List<AuthenticationConverter> converters;

????public CustomDelegatingAuthenticationConverter() {

????????this.converters = Arrays.asList(

????????????new PasswordGrantAuthenticationConverter(),

????????????new PasswordRefreshTokenAuthenticationConverter());

????}

????@Nullable

????@Override

????public Authentication convert(HttpServletRequest request) {

????????for (AuthenticationConverter converter : this.converters) {

????????????Authentication authentication = converter.convert(request);

????????????if (authentication != null) {

????????????????return authentication;

????????????}

????????}

????????log.error("沒有匹配到合適的Converter");

????????throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode());

????}

}

? ? ? ?在這里,按順序調(diào)用每個(gè)轉(zhuǎn)換器實(shí)例的convert()方法,只要調(diào)用結(jié)果返回不為null則表示調(diào)用成功,成功則結(jié)束convert操作;如果遍歷所有的轉(zhuǎn)換器都沒有調(diào)用成功,則拋出異常。

???????PasswordGrantAuthenticationConverter

public class PasswordGrantAuthenticationConverter implements AuthenticationConverter {

????public PasswordGrantAuthenticationConverter() { }

????@Override

????public Authentication convert(HttpServletRequest request) {

????????// 判斷登錄授權(quán)模式是否是支持的類型

????????String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);

????????String username = request.getParameter(OAuth2ParameterNames.USERNAME);

????????if (!”password”.equals(grantType) || StringUtils.isEmpty(username)) {

????????????return null;

????????}

????????... ...

????????Authentication clientPrincipal=securityContextHolder.getContext().getAuthentication();

????????// 獲取用戶名與密碼,并校驗(yàn)密碼的合法性

????????String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);

????????// 獲取用戶信息

????????CustomUserDetails userDetails = customUserDetailsService.loadUser(username, password);

????????// 返回自定義的PasswordGrantAuthenticationToken對(duì)象

????????return new PasswordGrantAuthenticationToken(clientPrincipal, additionalParameters,

????????????????, userDetails);

????}

}

? ? ? ? 在這里,將用戶輸入的username 和password 轉(zhuǎn)換為Authentication對(duì)象。

???????PasswordRefreshTokenAuthenticationConverter

public class PasswordRefreshTokenAuthenticationConverter implements AuthenticationConverter {

@Override

public Authentication convert(HttpServletRequest request) {

String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);

if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) {

return null;

}

????Authentication clientPrincipal=SecurityContextHolder.getContext().getAuthentication();

String refreshToken = parameters.getFirst(OAuth2ParameterNames.REFRESH_TOKEN);

String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);

Set<String> requestedScopes = Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));

????... ...

return new OAuth2RefreshTokenAuthenticationToken(

refreshToken, clientPrincipal, requestedScopes, additionalParameters);

}

}

? ? ? ? 在這里,將用戶輸入的refreshToken 轉(zhuǎn)換為Authentication對(duì)象。

自定義認(rèn)證提供者

PasswordGrantAuthenticationProvider

public class PasswordGrantAuthenticationProvider implements AuthenticationProvider {

????public PasswordGrantAuthenticationProvider() {}

????@Override

????public Authentication authenticate(Authentication authentication) throws AuthenticationException {

????????//獲取自定義token信息

????????PasswordGrantAuthenticationToken passwordGrantAuthenticationToken =

????????????????(PasswordGrantAuthenticationToken) authentication;

????????... ...

????????//授權(quán)類型

????????AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();

????????//密碼

????????String password = (String)additionalParameters.get(OAuth2ParameterNames.PASSWORD);

????????//用戶信息

????????CustomUserDetails userDetails = passwordGrantAuthenticationToken.getUserDetails();

????????// Ensure the client is authenticated

????????OAuth2ClientAuthenticationToken clientPrincipal =

??AuthUtils.getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);

????????... ...

????????// 由于在上面已驗(yàn)證過(guò)用戶名、密碼,現(xiàn)在構(gòu)建一個(gè)已認(rèn)證的對(duì)象

????????UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =

????????????????new UsernamePasswordAuthenticationToken(userDetails, password);

????????DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()

????????????????.registeredClient(registeredClient)

????????????????.principal(usernamePasswordAuthenticationToken)

????????????????.providerContext(ProviderContextHolder.getProviderContext())

????????????????.tokenType(OAuth2TokenType.ACCESS_TOKEN)

????????????????.authorizationGrantType(authorizationGrantType)

????????????????.authorizedScopes(registeredClient.getScopes())

????????????????.authorizationGrant(passwordGrantAuthenticationToken);

????????// Initialize the OAuth2Authorization

????????OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)

????????????????.principalName(clientPrincipal.getName())

????????????????.attribute(Principal.class.getName(), usernamePasswordAuthenticationToken)

????????????????.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, registeredClient.getScopes()).authorizationGrantType(authorizationGrantType);

????????// ----- Access token -----

????????OAuth2AccessToken accessToken = getAccessToken(tokenContextBuilder, authorizationBuilder);

????????// ----- Refresh token -----

????????OAuth2RefreshToken refreshToken =

????????????getRefreshToken(registeredClient, clientPrincipal, tokenContextBuilder, authorizationBuilder);

????????... ...

????????//存儲(chǔ)token信息

????????authorizationService.save(authorization);

????????return new PasswordTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);

????}

????@Override

????public boolean supports(Class<?> authentication) {

????????return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);

????}

}

? ? ? ? 在這里,驗(yàn)證用戶輸入的username 和password的合法性。

? ? ? ? 如果合法,則生成accessToken 和refreshToken ,然后返回給用戶。

???????PasswordRefreshTokenAuthenticationProvider

public class PasswordRefreshTokenAuthenticationProvider implements AuthenticationProvider {

public PasswordRefreshTokenAuthenticationProvider() {}

@Override

public Authentication authenticate(Authentication authentication) {

OAuth2RefreshTokenAuthenticationToken refreshTokenAuthentication =

(OAuth2RefreshTokenAuthenticationToken) authentication;

OAuth2ClientAuthenticationToken clientPrincipal =

AuthUtils.getAuthenticatedClientElseThrowInvalidClient(refreshTokenAuthentication);

RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();

OAuth2Authorization authorization = this.authorizationService.findByToken(

refreshTokenAuthentication.getRefreshToken(), OAuth2TokenType.REFRESH_TOKEN);

if (authorization == null || registeredClient == null ) {

log.error("Authentication或registeredClient的信息為空!");

throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode());

}

if (!registeredClient.getId().equals(authorization.getRegisteredClientId()) ||

!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {

log.error("registeredClient信息不符合要求!");

throw new BusinessException(ResponseCode.BUSINESS_AUTH_ERROR.getCode());

}

OAuth2Authorization.Token<OAuth2RefreshToken> refreshToken = authorization.getRefreshToken();

... ...

//獲取token構(gòu)造時(shí)存儲(chǔ)的用戶密碼認(rèn)證對(duì)象

UsernamePasswordAuthenticationToken principal = authorization.getAttribute(Principal.class.getName());

DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()

.registeredClient(registeredClient)

.principal(principal)

.providerContext(ProviderContextHolder.getProviderContext())

.authorization(authorization)

.authorizedScopes(scopes)

.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)

.authorizationGrant(refreshTokenAuthentication);

OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization);

// ----- Access token -----

OAuth2AccessToken accessToken = getOAuth2AccessToken(tokenContextBuilder, authorizationBuilder);

// ----- Refresh token -----

OAuth2RefreshToken currentRefreshToken = getOAuth2RefreshToken(refreshToken, registeredClient,tokenContextBuilder, authorizationBuilder);

????????... ...

????return new OAuth2AccessTokenAuthenticationToken(

registeredClient, clientPrincipal, accessToken, currentRefreshToken, additionalParameters);

}

@Override

public boolean supports(Class<?> authentication) {

??return OAuth2RefreshTokenAuthenticationToken.class.isAssignableFrom(authentication);

}

}

? ? ? ? 在這里,驗(yàn)證用戶輸入的refreshToken 的合法性。

? ? ? ? 如果合法,則生成新的accessToken 和refreshToken返回給用戶。

時(shí)序圖

  1. 類對(duì)象主要包括:配置器對(duì)象OAuth2TokenEndpointConfigurer、過(guò)濾器對(duì)象OAuth2TokenEndpointFilter、請(qǐng)求參數(shù)轉(zhuǎn)換器對(duì)象CustomDelegatingAuthenticationConverter、認(rèn)證管理器對(duì)象ProviderManager;
  2. 配置器對(duì)象:使用模板方法設(shè)計(jì)模式,提供了init、beforeConfigure、configure等幾個(gè)主要的過(guò)程來(lái)對(duì)過(guò)濾器進(jìn)行配置;
  3. 過(guò)濾器對(duì)象:過(guò)濾器的過(guò)濾邏輯不僅簡(jiǎn)單也很清晰,即只包含了convert和authenticate兩個(gè)過(guò)程;
  4. 請(qǐng)求參數(shù)轉(zhuǎn)換器對(duì)象:框架提供了由開發(fā)人員自定義請(qǐng)求參數(shù)轉(zhuǎn)換器的功能,請(qǐng)求參數(shù)轉(zhuǎn)換器的主要功能是把HTTP請(qǐng)求參數(shù)封裝為框架需要的請(qǐng)求參數(shù)類;
  5. 認(rèn)證管理器對(duì)象:認(rèn)證管理器管理著多個(gè)認(rèn)證提供者,框架提供了由開發(fā)人員自定義認(rèn)證提供者的功能。
http://m.risenshineclean.com/news/64630.html

相關(guān)文章:

  • 網(wǎng)站建設(shè)費(fèi) 什么科目品牌推廣宣傳詞
  • 獨(dú)立個(gè)人博客網(wǎng)站制作微信公眾號(hào)怎么開通
  • 優(yōu)秀網(wǎng)站制作深圳網(wǎng)站開發(fā)制作
  • 網(wǎng)站建設(shè)教程互聯(lián)網(wǎng)電商平臺(tái)有哪些
  • 做詐騙網(wǎng)站以及維護(hù)cpa推廣接單平臺(tái)
  • 商城類網(wǎng)站用什么做seo線下培訓(xùn)班
  • 網(wǎng)站值多少錢推薦一個(gè)seo優(yōu)化軟件
  • 網(wǎng)站建設(shè)app網(wǎng)站關(guān)鍵詞優(yōu)化培訓(xùn)
  • 微信管理中心seo人員的職責(zé)
  • aspcms網(wǎng)站模板網(wǎng)絡(luò)推廣公司有多少家
  • 中英西班牙網(wǎng)站建設(shè)一鍵優(yōu)化是什么意思
  • 浙江臺(tái)州做網(wǎng)站的公司有哪些網(wǎng)絡(luò)推廣網(wǎng)絡(luò)營(yíng)銷外包
  • 廈門網(wǎng)站建設(shè)哪家強(qiáng)農(nóng)產(chǎn)品網(wǎng)絡(luò)營(yíng)銷
  • 做網(wǎng)站軟件frontpage百度排名點(diǎn)擊軟件
  • 織夢(mèng)移動(dòng)網(wǎng)站百度站長(zhǎng)社區(qū)
  • 遼陽(yáng)好的網(wǎng)站建設(shè)公司百度競(jìng)價(jià)推廣流程
  • 梧州網(wǎng)站設(shè)計(jì)理念網(wǎng)絡(luò)seo外包
  • 贛州網(wǎng)站建設(shè)費(fèi)用百度seo培訓(xùn)要多少錢
  • 提供網(wǎng)站建設(shè)費(fèi)用企業(yè)網(wǎng)站優(yōu)化技巧
  • 做軟件常用的網(wǎng)站有哪些軟件有哪些事件營(yíng)銷案例
  • 移動(dòng)網(wǎng)站建站視頻網(wǎng)絡(luò)推廣的基本方法有哪些
  • 做平面什么網(wǎng)站好用今日熱搜前十名
  • 個(gè)人做旅游網(wǎng)站的意義百度首頁(yè)排名優(yōu)化服務(wù)
  • 做網(wǎng)站用什么語(yǔ)言好廣告聯(lián)盟app下載
  • 南昌新建網(wǎng)站建設(shè)如何讓百度快速收錄網(wǎng)站文章
  • 做網(wǎng)絡(luò)推廣的網(wǎng)站有哪些如何做電商 個(gè)人
  • 包頭正規(guī)旅游網(wǎng)站開發(fā)哪家好aso關(guān)鍵詞排名優(yōu)化是什么
  • 做網(wǎng)站界面尺寸是多少網(wǎng)上營(yíng)銷的平臺(tái)有哪些
  • wordpress sparklingseo視頻教程百度云
  • 南京房產(chǎn)網(wǎng)站建設(shè)手機(jī)如何做網(wǎng)站