当前位置: 首页 > news >正文

建设个人网站流程网站建设工作小组推进表

建设个人网站流程,网站建设工作小组推进表,网站权重如何速度增加,线下推广渠道原本的OAuth2登录支持用户名密码登录#xff0c;现在还想支持另外用id号码和密码登录。但是OAuth2默认提供的UserDetailsService只允许传入一个参数#xff1a;想要实现多种用户登录#xff0c;是不是可以考虑loadUserByUsername方法携带多个参数呢#xff1f;接下来记录一…原本的OAuth2登录支持用户名密码登录现在还想支持另外用id号码和密码登录。但是OAuth2默认提供的UserDetailsService只允许传入一个参数想要实现多种用户登录是不是可以考虑loadUserByUsername方法携带多个参数呢接下来记录一下实现步骤新增interface CustomUserDetailsServiceInter 继承原来的UserDetailsService,新增自定义方法loadUserByUsernamepublic interface CustomUserDetailsServiceInter extends UserDetailsService {CustomUser loadUserByUsername(String var1, String var2) throws UsernameNotFoundException;}然后根据自己需要实现CustomUserDetailsServiceInter接口的方法这里就不放出来了Slf4j Component public class CustomUserDetailsService implements CustomUserDetailsServiceInter {Overridepublic CustomUser loadUserByUsername(String username, String idCard) throws UsernameNotFoundException {// 根据自己需要进行实现// 1.获取用户// 2.获取用户可访问权限信息// 3.构造UserDetails信息并返回return userDetails;} }从现在开始所有需要用到userDetailsService的全部都要替换成自定义CustomUserDetailsService。比如WebSecurityConfigurer、AuthServerConfigurer复制org.springframework.security.authentication.dao.DaoAuthenticationProvider的代码自定义 CustomerDaoAuthenticationProvider然后进行修改retrieveUser()方法其他不需要动 public class CustomerDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {private static final String USER_NOT_FOUND_PASSWORD userNotFoundPassword;private PasswordEncoder passwordEncoder;private volatile String userNotFoundEncodedPassword;private CustomUserDetailsService userDetailsService;private UserDetailsPasswordService userDetailsPasswordService;public CustomerDaoAuthenticationProvider() {setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());}OverrideSuppressWarnings(deprecation)protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() null) {this.logger.debug(Failed to authenticate since no credentials provided);throw new BadCredentialsException(this.messages.getMessage(AbstractUserDetailsAuthenticationProvider.badCredentials, Bad credentials));}String presentedPassword authentication.getCredentials().toString();String password userDetails.getPassword();boolean matches this.passwordEncoder.matches(presentedPassword, password);if (!matches) {this.logger.debug(Failed to authenticate since password does not match stored value);throw new BadCredentialsException(this.messages.getMessage(AbstractUserDetailsAuthenticationProvider.badCredentials, Bad credentials));}}Overrideprotected void doAfterPropertiesSet() {Assert.notNull(this.userDetailsService, A UserDetailsService must be set);}/*Overrideprotected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {UserDetails loadedUser this.getUserDetailsService().loadUserByUsername(username, authentication.getDetails());if (loadedUser null) {throw new InternalAuthenticationServiceException(UserDetailsService returned null, which is an interface contract violation);}return loadedUser;} catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;} catch (InternalAuthenticationServiceException ex) {throw ex;} catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}*/Overrideprotected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {boolean upgradeEncoding this.userDetailsPasswordService ! null this.passwordEncoder.upgradeEncoding(user.getPassword());if (upgradeEncoding) {String presentedPassword authentication.getCredentials().toString();String newPassword this.passwordEncoder.encode(presentedPassword);user this.userDetailsPasswordService.updatePassword(user, newPassword);}return super.createSuccessAuthentication(principal, authentication, user);}private void prepareTimingAttackProtection() {if (this.userNotFoundEncodedPassword null) {this.userNotFoundEncodedPassword this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);}}private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {if (authentication.getCredentials() ! null) {String presentedPassword authentication.getCredentials().toString();this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);}}/*** Sets the PasswordEncoder instance to be used to encode and validate passwords. If* not set, the password will be compared using* {link PasswordEncoderFactories#createDelegatingPasswordEncoder()}** param passwordEncoder must be an instance of one of the {code PasswordEncoder}* types.*/public void setPasswordEncoder(PasswordEncoder passwordEncoder) {Assert.notNull(passwordEncoder, passwordEncoder cannot be null);this.passwordEncoder passwordEncoder;this.userNotFoundEncodedPassword null;}protected PasswordEncoder getPasswordEncoder() {return this.passwordEncoder;}public void setUserDetailsService(CustomUserDetailsService userDetailsService) {this.userDetailsService userDetailsService;}protected CustomUserDetailsService getUserDetailsService() {return this.userDetailsService;}public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {this.userDetailsPasswordService userDetailsPasswordService;}protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {this.prepareTimingAttackProtection();MapString, String map (MapString, String) authentication.getDetails(); // 自定义添加try {String userIdCard map.get(idCard); // 自定义添加UserDetails loadedUser this.getUserDetailsService().loadUserByUsername(username, userIdCard);if (loadedUser null) {throw new InternalAuthenticationServiceException(UserDetailsService returned null, which is an interface contract violation);} else {return loadedUser;}} catch (UsernameNotFoundException var4) {this.mitigateAgainstTimingAttack(authentication);throw var4;} catch (InternalAuthenticationServiceException var5) {throw var5;} catch (Exception var6) {throw new InternalAuthenticationServiceException(var6.getMessage(), var6);}} }记得将自定义的CustomerDaoAuthenticationProvider中的userDetailsService替换成自定义的CustomUserDetailsService到WebSecurityConfig配置上面的CustomAuthenticationProvider Bean(namecustomAuthenticationProvider)public AuthenticationProvider customAuthenticationProvider() {CustomerDaoAuthenticationProvider customAuthenticationProvider new CustomerDaoAuthenticationProvider();customAuthenticationProvider.setUserDetailsService(userDetailsService);customAuthenticationProvider.setHideUserNotFoundExceptions(false);customAuthenticationProvider.setPasswordEncoder(passwordEncoder);return customAuthenticationProvider;}Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(customAuthenticationProvider());}可以去获取token试试了获取access_token请求/oauth/token请求所需参数client_id、client_secret、grant_type、username、password 这些是默认的可以用需要的idCard替换username现在去请求刷新Token刷新token请求/oauth/token请求所需参数grant_type、refresh_token、client_id、client_secret其中grant_type为固定值grant_typerefresh_token会发现怎样也刷新不了因为刷新Token时用的还是原版的UserDetailsService执行的还是单个参数的loadUserByUsername所以下面要重新自定义相关类来替换掉复制org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper 自定义 CustomUserDetailsByNameServiceWrapper 在 loadUserDetails() 方法添加自定义的idCard public class CustomUserDetailsByNameServiceWrapperT extends Authentication implementsAuthenticationUserDetailsServiceT, InitializingBean {private CustomUserDetailsService userDetailsService null;/*** Constructs an empty wrapper for compatibility with Spring Security 2.0.xs method* of using a setter.*/public CustomUserDetailsByNameServiceWrapper() {// constructor for backwards compatibility with 2.0}/*** Constructs a new wrapper using the supplied* {link org.springframework.security.core.userdetails.UserDetailsService} as the* service to delegate to.** param userDetailsService the UserDetailsService to delegate to.*/public CustomUserDetailsByNameServiceWrapper(final CustomUserDetailsService userDetailsService) {Assert.notNull(userDetailsService, userDetailsService cannot be null.);this.userDetailsService userDetailsService;}/*** Check whether all required properties have been set.** see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()*/public void afterPropertiesSet() {Assert.notNull(this.userDetailsService, UserDetailsService must be set);}/*** Get the UserDetails object from the wrapped UserDetailsService implementation*/public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {// ----------添加自定义的内容----------AbstractAuthenticationToken principal (AbstractAuthenticationToken) authentication.getPrincipal();MapString,String map (MapString, String) principal.getDetails();String userIdCard map.get(idCard);// ----------添加自定义的内容----------return this.userDetailsService.loadUserByUsername(authentication.getName(), userIdCard); // 使用自定义的userDetailsService}/*** Set the wrapped UserDetailsService implementation** param aUserDetailsService The wrapped UserDetailsService to set*/public void setUserDetailsService(CustomUserDetailsService aUserDetailsService) {this.userDetailsService aUserDetailsService;} }复制 org.springframework.security.oauth2.provider.token.DefaultTokenServices 到自定义的 CustomTokenServices 然后修改refreshAccessToken() 方法的if (this.authenticationManager ! null !authentication.isClientOnly()) 这里面的内容 public class CustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,ConsumerTokenServices, InitializingBean {private int refreshTokenValiditySeconds 60 * 60 * 24 * 30; // default 30 days.private int accessTokenValiditySeconds 60 * 60 * 12; // default 12 hours.private boolean supportRefreshToken false;private boolean reuseRefreshToken true;private TokenStore tokenStore;private ClientDetailsService clientDetailsService;private TokenEnhancer accessTokenEnhancer;private AuthenticationManager authenticationManager;/*** Initialize these token services. If no random generator is set, one will be created.*/public void afterPropertiesSet() throws Exception {Assert.notNull(tokenStore, tokenStore must be set);}Transactionalpublic OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {OAuth2AccessToken existingAccessToken tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken null;if (existingAccessToken ! null) {if (existingAccessToken.isExpired()) {if (existingAccessToken.getRefreshToken() ! null) {refreshToken existingAccessToken.getRefreshToken();// The token store could remove the refresh token when the// access token is removed, but we want to// be sure...tokenStore.removeRefreshToken(refreshToken);}tokenStore.removeAccessToken(existingAccessToken);}else {// Re-store the access token in case the authentication has changedtokenStore.storeAccessToken(existingAccessToken, authentication);return existingAccessToken;}}// Only create a new refresh token if there wasnt an existing one// associated with an expired access token.// Clients might be holding existing refresh tokens, so we re-use it in// the case that the old access token// expired.if (refreshToken null) {refreshToken createRefreshToken(authentication);}// But the refresh token itself might need to be re-issued if it has// expired.else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring (ExpiringOAuth2RefreshToken) refreshToken;if (System.currentTimeMillis() expiring.getExpiration().getTime()) {refreshToken createRefreshToken(authentication);}}OAuth2AccessToken accessToken createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);// In case it was modifiedrefreshToken accessToken.getRefreshToken();if (refreshToken ! null) {tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}Transactional(noRollbackFor{InvalidTokenException.class, InvalidGrantException.class})public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)throws AuthenticationException {if (!supportRefreshToken) {throw new InvalidGrantException(Invalid refresh token: refreshTokenValue);}OAuth2RefreshToken refreshToken tokenStore.readRefreshToken(refreshTokenValue);if (refreshToken null) {throw new InvalidGrantException(Invalid refresh token: refreshTokenValue);}OAuth2Authentication authentication tokenStore.readAuthenticationForRefreshToken(refreshToken);if (this.authenticationManager ! null !authentication.isClientOnly()) {/*// The client has already been authenticated, but the user authentication might be old now, so give it a// chance to re-authenticate.Authentication user new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), , authentication.getAuthorities());user authenticationManager.authenticate(user);Object details authentication.getDetails();authentication new OAuth2Authentication(authentication.getOAuth2Request(), user);authentication.setDetails(details);*/// OAuth2Authentication 中的 Authentication userAuthentication 丢失了 Detail的信息,需要补上// 1.从tokenRequest中获取请求的信息,并重新构造成 UsernamePasswordAuthenticationToken// 2.设置好了Detail的信息再传入构造 PreAuthenticatedAuthenticationToken 交由后面的验证//tokenRequest.getRequestParameters();Object details tokenRequest.getRequestParameters();UsernamePasswordAuthenticationToken userAuthentication (UsernamePasswordAuthenticationToken) authentication.getUserAuthentication();userAuthentication.setDetails(details);// 去掉原来的,使用自己重新构造的 userAuthentication // Authentication user new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), , authentication.getAuthorities());Authentication user new PreAuthenticatedAuthenticationToken(userAuthentication, , authentication.getAuthorities());user this.authenticationManager.authenticate(user);authentication new OAuth2Authentication(authentication.getOAuth2Request(), user);authentication.setDetails(details);}String clientId authentication.getOAuth2Request().getClientId();if (clientId null || !clientId.equals(tokenRequest.getClientId())) {throw new InvalidGrantException(Wrong client for this refresh token: refreshTokenValue);}// clear out any access tokens already associated with the refresh// token.tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);if (isExpired(refreshToken)) {tokenStore.removeRefreshToken(refreshToken);throw new InvalidTokenException(Invalid refresh token (expired): refreshToken);}authentication createRefreshedAuthentication(authentication, tokenRequest);if (!reuseRefreshToken) {tokenStore.removeRefreshToken(refreshToken);refreshToken createRefreshToken(authentication);}OAuth2AccessToken accessToken createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);if (!reuseRefreshToken) {tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);}return accessToken;}public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {return tokenStore.getAccessToken(authentication);}/*** Create a refreshed authentication.** param authentication The authentication.* param request The scope for the refreshed token.* return The refreshed authentication.* throws InvalidScopeException If the scope requested is invalid or wider than the original scope.*/private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {OAuth2Authentication narrowed authentication;SetString scope request.getScope();OAuth2Request clientAuth authentication.getOAuth2Request().refresh(request);if (scope ! null !scope.isEmpty()) {SetString originalScope clientAuth.getScope();if (originalScope null || !originalScope.containsAll(scope)) {throw new InvalidScopeException(Unable to narrow the scope of the client authentication to scope ., originalScope);}else {clientAuth clientAuth.narrowScope(scope);}}narrowed new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());return narrowed;}protected boolean isExpired(OAuth2RefreshToken refreshToken) {if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiringToken (ExpiringOAuth2RefreshToken) refreshToken;return expiringToken.getExpiration() null|| System.currentTimeMillis() expiringToken.getExpiration().getTime();}return false;}public OAuth2AccessToken readAccessToken(String accessToken) {return tokenStore.readAccessToken(accessToken);}public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,InvalidTokenException {OAuth2AccessToken accessToken tokenStore.readAccessToken(accessTokenValue);if (accessToken null) {throw new InvalidTokenException(Invalid access token: accessTokenValue);}else if (accessToken.isExpired()) {tokenStore.removeAccessToken(accessToken);throw new InvalidTokenException(Access token expired: accessTokenValue);}OAuth2Authentication result tokenStore.readAuthentication(accessToken);if (result null) {// in case of race conditionthrow new InvalidTokenException(Invalid access token: accessTokenValue);}if (clientDetailsService ! null) {String clientId result.getOAuth2Request().getClientId();try {clientDetailsService.loadClientByClientId(clientId);}catch (ClientRegistrationException e) {throw new InvalidTokenException(Client not valid: clientId, e);}}return result;}public String getClientId(String tokenValue) {OAuth2Authentication authentication tokenStore.readAuthentication(tokenValue);if (authentication null) {throw new InvalidTokenException(Invalid access token: tokenValue);}OAuth2Request clientAuth authentication.getOAuth2Request();if (clientAuth null) {throw new InvalidTokenException(Invalid access token (no client id): tokenValue);}return clientAuth.getClientId();}public boolean revokeToken(String tokenValue) {OAuth2AccessToken accessToken tokenStore.readAccessToken(tokenValue);if (accessToken null) {return false;}if (accessToken.getRefreshToken() ! null) {tokenStore.removeRefreshToken(accessToken.getRefreshToken());}tokenStore.removeAccessToken(accessToken);return true;}private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {if (!isSupportRefreshToken(authentication.getOAuth2Request())) {return null;}int validitySeconds getRefreshTokenValiditySeconds(authentication.getOAuth2Request());String value UUID.randomUUID().toString();if (validitySeconds 0) {return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis() (validitySeconds * 1000L)));}return new DefaultOAuth2RefreshToken(value);}private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {DefaultOAuth2AccessToken token new DefaultOAuth2AccessToken(UUID.randomUUID().toString());int validitySeconds getAccessTokenValiditySeconds(authentication.getOAuth2Request());if (validitySeconds 0) {token.setExpiration(new Date(System.currentTimeMillis() (validitySeconds * 1000L)));}token.setRefreshToken(refreshToken);token.setScope(authentication.getOAuth2Request().getScope());return accessTokenEnhancer ! null ? accessTokenEnhancer.enhance(token, authentication) : token;}/*** The access token validity period in seconds** param clientAuth the current authorization request* return the access token validity period in seconds*/protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {if (clientDetailsService ! null) {ClientDetails client clientDetailsService.loadClientByClientId(clientAuth.getClientId());Integer validity client.getAccessTokenValiditySeconds();if (validity ! null) {return validity;}}return accessTokenValiditySeconds;}/*** The refresh token validity period in seconds** param clientAuth the current authorization request* return the refresh token validity period in seconds*/protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {if (clientDetailsService ! null) {ClientDetails client clientDetailsService.loadClientByClientId(clientAuth.getClientId());Integer validity client.getRefreshTokenValiditySeconds();if (validity ! null) {return validity;}}return refreshTokenValiditySeconds;}/*** Is a refresh token supported for this client (or the global setting if* {link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set.** param clientAuth the current authorization request* return boolean to indicate if refresh token is supported*/protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {if (clientDetailsService ! null) {ClientDetails client clientDetailsService.loadClientByClientId(clientAuth.getClientId());return client.getAuthorizedGrantTypes().contains(refresh_token);}return this.supportRefreshToken;}/*** An access token enhancer that will be applied to a new token before it is saved in the token store.** param accessTokenEnhancer the access token enhancer to set*/public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {this.accessTokenEnhancer accessTokenEnhancer;}/*** The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be* non-expiring.** param refreshTokenValiditySeconds The validity (in seconds) of the refresh token.*/public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {this.refreshTokenValiditySeconds refreshTokenValiditySeconds;}/*** The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client* details service is set the validity period will be read from the client, defaulting to this value if not defined* by the client.** param accessTokenValiditySeconds The validity (in seconds) of the access token.*/public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {this.accessTokenValiditySeconds accessTokenValiditySeconds;}/*** Whether to support the refresh token.** param supportRefreshToken Whether to support the refresh token.*/public void setSupportRefreshToken(boolean supportRefreshToken) {this.supportRefreshToken supportRefreshToken;}/*** Whether to reuse refresh tokens (until expired).** param reuseRefreshToken Whether to reuse refresh tokens (until expired).*/public void setReuseRefreshToken(boolean reuseRefreshToken) {this.reuseRefreshToken reuseRefreshToken;}/*** The persistence strategy for token storage.** param tokenStore the store for access and refresh tokens.*/public void setTokenStore(TokenStore tokenStore) {this.tokenStore tokenStore;}/*** An authentication manager that will be used (if provided) to check the user authentication when a token is* refreshed.** param authenticationManager the authenticationManager to set*/public void setAuthenticationManager(AuthenticationManager authenticationManager) {this.authenticationManager authenticationManager;}/*** The client details service to use for looking up clients (if necessary). Optional if the access token expiry is* set globally via {link #setAccessTokenValiditySeconds(int)}.** param clientDetailsService the client details service*/public void setClientDetailsService(ClientDetailsService clientDetailsService) {this.clientDetailsService clientDetailsService;}}到认证服务器配置类 AuthorizationServerConfig 配置刚刚自定义的两个类 CustomUserDetailsByNameServiceWrapper 和 CustomTokenServicesOverridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.tokenStore(jwtTokenStore()) // 根据自己需要.tokenEnhancer(jwtAccessTokenConverter()).reuseRefreshTokens(true).authenticationManager(authenticationManager).userDetailsService(userDetailsService).tokenServices(customTokenServices(endpoints)); // 自定义TokenServices}public CustomTokenServices customTokenServices(AuthorizationServerEndpointsConfigurer endpoints){CustomTokenServices tokenServices new CustomTokenServices();tokenServices.setTokenStore(endpoints.getTokenStore());tokenServices.setSupportRefreshToken(true); tokenServices.setReuseRefreshToken(true);tokenServices.setClientDetailsService(clientDetails());tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());// 设置自定义的CustomUserDetailsByNameServiceWrapperif (userDetailsService ! null) {PreAuthenticatedAuthenticationProvider provider new PreAuthenticatedAuthenticationProvider();provider.setPreAuthenticatedUserDetailsService(new CustomUserDetailsByNameServiceWrapper(userDetailsService));tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));}return tokenServices;}至此可以刷新token试试了。在获取Token的时候像client_id,client_secret等默认的请求参数框架会自动放到details中但是在刷新token的时候不知什么原因details中并没有放入请求的参数所以需要自己重新构造还好这些信息都保存在tokenRequest中所以new一个UsernamePasswordAuthenticationToken把它们都放进details中再传入 PreAuthenticatedAuthenticationToken后续就交由框架去验证就可以了 。这一步操作就是CustomTokenServices类refreshAccessToken方法
http://www.hkea.cn/news/14496558/

相关文章:

  • 郑州做设计公司网站安阳网站建设安阳
  • 湖北建设工程信息网站做视频哪个网站收入高
  • 网站后台点击添加图片没有反应成熟短视频源码大全
  • 视频网站开发 价格佛山网络推广哪里好
  • 网站建设策划书怎么写特效比漂亮的网站
  • 变白网站制作源码我要自学网做网站
  • 服装网站建设项目实施报告wordpress学校主题
  • 自学做甜品师的网站公司网站怎么更新
  • 自己可以自己做公司的网站吗WordPress自动采集翻译插件
  • 国外文件传输网站网站建设 今晟网络
  • wordpress怎样下载医院网站优化
  • 济南哪家做网站外贸多语言网站免费源码
  • 不动产登记门户网站建设东莞建设网站公司简介
  • 品牌网站是什么专业的企业智能建站制造厂家
  • 湖南网站营销seo哪家好最新手机资讯
  • 点样用外网访问自己做的网站国外优秀企业网站设计
  • 淄博网站建设方案鲜花外贸网站建设
  • 无法进入网站后台最新新闻热点事件2022年1月
  • 自己做的网站如何兼容维护公司网站建设
  • 高端品牌网站建设明细报价报P2 wordpress
  • 网站建设检查整改情况报告百度网盘下载官网
  • 敦煌网的网站推广方式宽带开户多少钱
  • 南通高端网站设计网站开发有哪些方式
  • 湖北网站设计制作价格桦甸市城乡建设局网站
  • 京东网站建设框架图wordpress 搬瓦工
  • 十堰网站优化价格怎么开店
  • 基于python的网站开发项目悟空crm永久免费了
  • 怎么用自己电脑做网站服务器做网站域名费一般多少钱
  • asp与php做网站哪个好类似源码之家的网站
  • 用云做网站网站制作费用及后期运营