上海网站维护,个人版的wordpress怎么加关键词,哪个全球购网站做的好处,免费论坛网站建设文章目录 前言技术积累Oauth2简介Oauth2的四种模式授权码模式简化模式密码模式客户端模式自定义模式 实战演示1、mavan依赖引入2、自定义手机用户3、自定义手机用户信息获取服务4、自定义认证令牌5、自定义授权模式6、自定义实际认证提供者7、认证服务配置8、Oauth2配置9、资源… 文章目录 前言技术积累Oauth2简介Oauth2的四种模式授权码模式简化模式密码模式客户端模式自定义模式 实战演示1、mavan依赖引入2、自定义手机用户3、自定义手机用户信息获取服务4、自定义认证令牌5、自定义授权模式6、自定义实际认证提供者7、认证服务配置8、Oauth2配置9、资源服务配置 测试用例1、创建测试请求2、测试用例预演3、测试结果 写在最后 前言
最近在修改一个之前使用Oauth2的旧项目该项目使用了原始的账号密码登录。现在有一个需求是在当前的基础上增加手机验证码登录让系统可以同时支持账密、手机验证码登录两种方式。哈哈看到这个需求心里就想着可以参照Oauth2的账密验证逻辑进行改造比如自定义认证对象、自定义授权模式、自定义实际授权者最后将上面几个添加到Oauth2里面让其能够识别即可。 技术积累
Oauth2简介
OAuth 是一个开放标准该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源 如头像、照片、视频等并且在这个过程中无须将用户名和密码提供给第三方应用。通过令牌 token 可以实现这一功能。每一个令牌授权一个特定的网站在特定的时间段内允许可访问特定的资源。
OAuth 让用户可以授权第三方网站灵活访问它们存储在另外一些资源服务器上的特定信息而非所有的内容。对于用户而言我们在互联网应用中最常见的 OAuth 应用就是各种第三方登录例如 QQ授权登录、微信授权登录、微博授权登录、GitHub 授权登录等。
Oauth2的四种模式
授权码模式
大概就是客户端需要拿到一个预授权码然后通过预授权码换取真实的token,最后使用token进行访问。比如我们的微信授权登录、飞书授权登录等等。
简化模式
简化模式是授权码模式简化版本客户端不用先申请预授权码直接通过具体的参数拿到token最后访问资源。比如我们微信appId、appSecrt拿到access_token。
密码模式
这个就是非常原始的账户、密码登录客户端携带账户和密码直接拿到token最后使用token进行资源访问。比如我们现在常规系统的账密登录。
客户端模式
这种直接客户端向授权服务注册直接拿到token比如会员客户端等等。 自定义模式
自定义模式就是我们今天的重点我们不用Oauth2的四种模式直接用自己的方式实现一个手机验证码登录功能。
大概得流程就是 1、自定义认证令牌 extends AbstractAuthenticationToken 自由实现未认证和已认证的构造令牌的方法
2、自定义授权模式 extends AbstractTokenGranter 在授权模式中我们会获取到认证路径中的手机号和验证码并产生一个未认证的认证令牌交给Oauth2
3、自定义实际认证提供者 implements AuthenticationProvider 在实际的认证者中我们会拿到在授权模式中提供的手机号和验证码然后进行业务比对比如是否存在、是否过期等等。如果全部验证通过会产生一个完成认证的认证令牌交给Oauth2
4、授权配置 extends AuthorizationServerConfigurerAdapter 这个里面需要配置 客户端 ClientDetailsServiceConfigurer 以及将自定义和默认授权模式交给Oauth2 AuthorizationServerEndpointsConfigurer
5、Oauth2配置 extends WebSecurityConfigurerAdapter 配置哪些请求需要进行拦截和免登录 configure(HttpSecurity http) 配置密码模式的用户名和密码并将自定义认证提供者加入 configure(AuthenticationManagerBuilder auth)
6、资源服务器配置 extends ResourceServerConfigurerAdapter 定义哪些资源需要被拦截和放行 configure(HttpSecurity http) 如果是生成环境需要配置token策略和服务端保持一致。
实战演示
本次演示是将资源服务和客户服务放置在同一个项目上所有采用的内存报错token。如果使用在生产环境请修改为从数据库拿去用户和客户信息以及token持久化。
1、mavan依赖引入
parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.3.12.RELEASE/versionrelativePath/ !-- lookup parent from repository --
/parent
propertiesjava.version8/java.versionspring-cloud.versionHoxton.SR12/spring-cloud.version
/properties
dependencyManagementdependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-dependencies/artifactIdversion${spring-cloud.version}/versiontypepom/typescopeimport/scope/dependency/dependencies
/dependencyManagement!--安全模块--
dependencygroupIdorg.springframework.security.oauth.boot/groupIdartifactIdspring-security-oauth2-autoconfigure/artifactId
/dependency2、自定义手机用户
PhoneUser
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;/*** PhoneUser* author senfel* version 1.0* date 2024/8/7 18:34*/
public class PhoneUser extends User {Getterprivate Integer id;Getterprivate String name;Getterprivate String phone;public PhoneUser(Integer id,String name,String phone,String username, String password, Collection? extends GrantedAuthority authorities) {super(username, password, authorities);this.id id;this.name name;this.phone phone;}
}3、自定义手机用户信息获取服务
PhoneUserDetailsService
import cn.hutool.core.util.ArrayUtil;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;/*** PhoneUserDetailsService* 手机用户信息获取服务* author senfel* version 1.0* date 2024/8/7 18:28*/
Component
public class PhoneUserDetailsService implements UserDetailsService {Overridepublic UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {System.err.println(PhoneUserDetailsService获取到的phone:phone);//TODO 从数据库获取手机用户数据,默认admin_role权限集String[] permissionArr new String[]{admin_role};return new PhoneUser(1,senfel-test-phone,18788888888,18788888888,, initAuthority(permissionArr));}private Collection? extends GrantedAuthority initAuthority(String[] permissionArr) {SetString dbAuthsSet new HashSet();if (ArrayUtil.isNotEmpty(permissionArr)) {dbAuthsSet.addAll(Arrays.asList(permissionArr));}return AuthorityUtils.createAuthorityList(dbAuthsSet.toArray(new String[0]));}
}4、自定义认证令牌
PhoneAuthenticationToken
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;/*** PhoneAuthenticationToken* 手机验证码认证令牌* author senfel* version 1.0* date 2024/8/7 17:33*/
public class PhoneAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID 530L;//表示认证主体通常是用户对象UserDetails这里传入手机号private final Object principal;//存储了与主体关联的认证信息例如密码这里传入验证码private Object credentials;/*** 自定义已未证对象* param principal* param credentials* author senfel* date 2024/8/7 17:39* return*/public PhoneAuthenticationToken(Object principal, Object credentials) {super(null);this.principal principal;this.credentials credentials;setAuthenticated(false);}/*** 自定义已认证对象* param authorities 表示主体所拥有的权限集合* param principal* param credentials* author senfel* date 2024/8/7 17:39* return*/public PhoneAuthenticationToken(Collection? extends GrantedAuthority authorities, Object principal, Object credentials) {super(authorities);this.principal principal;this.credentials credentials;//是否认证标识setAuthenticated(true);}Overridepublic Object getCredentials() {return this.credentials;}Overridepublic Object getPrincipal() {return this.principal;}
}5、自定义授权模式
PhoneCodeTokenGranter
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;/*** PhoneCodeTokenGranter* 手机验证码授权模式* author senfel* version 1.0* date 2024/8/7 17:43*/
public class PhoneCodeTokenGranter extends AbstractTokenGranter {//授权类型名称private static final String GRANT_TYPE phonecode;private final AuthenticationManager authenticationManager;/*** 手机验证码授权模式构造函数* param tokenServices* param clientDetailsService* param requestFactory* param authenticationManager* author senfel* date 2024/8/7 18:06* return*/public PhoneCodeTokenGranter( AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {this(authenticationManager,tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);}public PhoneCodeTokenGranter(AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {super(tokenServices, clientDetailsService, requestFactory, grantType);this.authenticationManager authenticationManager;}Overrideprotected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {MapString, String parameters new LinkedHashMapString, String(tokenRequest.getRequestParameters());//获取参数String phone parameters.get(phone);String phonecode parameters.get(phonecode);//创建未认证对象Authentication userAuth new PhoneAuthenticationToken(phone, phonecode);((AbstractAuthenticationToken) userAuth).setDetails(parameters);try {//进行身份认证userAuth authenticationManager.authenticate(userAuth);} catch (AccountStatusException ase) {//将过期、锁定、禁用的异常统一转换throw new InvalidGrantException(ase.getMessage());} catch (BadCredentialsException e) {// 验证码错误我们应该发送400/invalid grantthrow new InvalidGrantException(e.getMessage());}if (userAuth null || !userAuth.isAuthenticated()) {throw new InvalidGrantException(用户认证失败: phone);}OAuth2Request storedOAuth2Request getRequestFactory().createOAuth2Request(client, tokenRequest);return new OAuth2Authentication(storedOAuth2Request, userAuth);}}6、自定义实际认证提供者
PhoneAuthenticationProvider
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;/*** PhoneAuthenticationProvider* 手机验证码实际认证供应者* author senfel* version 1.0* date 2024/8/7 18:12*/
Setter
public class PhoneAuthenticationProvider implements AuthenticationProvider {private StringRedisTemplate redisTemplate;private PhoneUserDetailsService phoneUserDetailsService;public static final String PHONE_CODE_SUFFIX phone:code:;Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {//先将authentication转为我们自定义的Authentication对象PhoneAuthenticationToken authenticationToken (PhoneAuthenticationToken) authentication;//校验参数Object principal authentication.getPrincipal();Object credentials authentication.getCredentials();if (principal null || .equals(principal.toString()) || credentials null || .equals(credentials.toString())){throw new InternalAuthenticationServiceException(手机/手机验证码为空);}//获取手机号和验证码String phone (String) authenticationToken.getPrincipal();String code (String) authenticationToken.getCredentials();//查找手机用户信息验证用户是否存在UserDetails userDetails phoneUserDetailsService.loadUserByUsername(phone);if (userDetails null){throw new InternalAuthenticationServiceException(用户手机不存在);}String codeKey PHONE_CODE_SUFFIXphone;//手机用户存在验证手机验证码是否正确if (!redisTemplate.hasKey(codeKey)){throw new InternalAuthenticationServiceException(验证码不存在或已失效);}String realCode redisTemplate.opsForValue().get(codeKey);if (StringUtils.isBlank(realCode) || !realCode.equals(code)){throw new InternalAuthenticationServiceException(验证码错误);}//返回认证成功的对象PhoneAuthenticationToken phoneAuthenticationToken new PhoneAuthenticationToken(userDetails.getAuthorities(),phone,code);//details是一个泛型属性用于存储关于认证令牌的额外信息。其类型是 Object所以你可以存储任何类型的数据。这个属性通常用于存储与认证相关的详细信息比如用户的角色、IP地址、时间戳等。phoneAuthenticationToken.setDetails(userDetails);return phoneAuthenticationToken;}/*** ProviderManager 选择具体Provider时根据此方法判断* 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口*/Overridepublic boolean supports(Class? authentication) {//isAssignableFrom方法如果比较类和被比较类类型相同或者是其子类、实现类返回truereturn PhoneAuthenticationToken.class.isAssignableFrom(authentication);}}7、认证服务配置
AuthorizationServerConfig
import com.example.ccedemo.security.PhoneCodeTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.ArrayList;
import java.util.List;/*** AuthorizationServerConfig* 认证服务配置* author senfel* version 1.0* date 2024/8/7 19:22*/
Configuration
EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {Autowiredprivate AuthenticationManager authenticationManager;/*** 配置允许访问系统的客户端* param clients* author senfel* date 2024/8/8 14:17* return void*/Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//clients.jdbc()生产环境从数据库加载//模拟内置一个系统客户端clients.inMemory().withClient(admin).secret({bcrypt}new BCryptPasswordEncoder().encode(12315)).scopes(server).authorizedGrantTypes(authorization_code, password, implicit,client_credentials,refresh_token,phonecode);}/*** 断点配置* param endpoints* author senfel* date 2024/8/8 14:17* return void*/Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {//tokenGranterendpoints.tokenGranter(new CompositeTokenGranter(initGranters(endpoints)));}/*** 加入所有自定义的和原有的认证模式配置* param endpoints* author senfel* date 2024/8/9 9:33* return java.util.Listorg.springframework.security.oauth2.provider.TokenGranter*/private ListTokenGranter initGranters(AuthorizationServerEndpointsConfigurer endpoints) {//TODO 如果资源服务与授权服务分开则需要token持久化这里默认使用内存存储AuthorizationServerTokenServices tokenServices endpoints.getTokenServices();ClientDetailsService clientDetailsService endpoints.getClientDetailsService();OAuth2RequestFactory oAuth2RequestFactory endpoints.getOAuth2RequestFactory();AuthorizationCodeServices authorizationCodeServices endpoints.getAuthorizationCodeServices();//自定义GranterListTokenGranter customTokenGranters new ArrayList();customTokenGranters.add(new PhoneCodeTokenGranter(authenticationManager, tokenServices, clientDetailsService, oAuth2RequestFactory));//添加密码模式customTokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices, clientDetailsService, oAuth2RequestFactory));//刷新模式customTokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailsService, oAuth2RequestFactory));//简易模式customTokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailsService, oAuth2RequestFactory));//客户端模式customTokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailsService, oAuth2RequestFactory));//授权码模式customTokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailsService, oAuth2RequestFactory));return customTokenGranters;}}8、Oauth2配置
OAuth2SecurityConfig
import com.example.ccedemo.security.PhoneAuthenticationProvider;
import com.example.ccedemo.security.PhoneUserDetailsService;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;/*** OAuth2SecurityConfig* author senfel* version 1.0* date 2024/8/7 19:08*/
Configuration
EnableWebSecurity
public class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {Autowiredprivate StringRedisTemplate redisTemplate;Autowiredprivate PhoneUserDetailsService phoneUserDetailsService;OverrideSneakyThrowsprotected void configure(HttpSecurity http) {http.csrf().disable()// 关闭csrf.authorizeRequests().anyRequest().authenticated();}BeanOverridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}Beanpublic PasswordEncoder passwordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//auth.jdbcAuthentication()生产环境从数据库加载//模拟验证用户名密码登录auth.inMemoryAuthentication().withUser(admin).password({bcrypt}new BCryptPasswordEncoder().encode(123456))//设置权限.authorities(admin_role).and().withUser(user).password({bcrypt}new BCryptPasswordEncoder().encode(123456))//设置权限.authorities(user_role);//放入自定义的认证提供者auth.authenticationProvider(phoneAuthenticationProvider());}/*** 手机验证码登录的认证提供者* author senfel* date 2024/8/9 9:34* return com.example.ccedemo.security.PhoneAuthenticationProvider*/Beanpublic PhoneAuthenticationProvider phoneAuthenticationProvider(){//实例化provider把需要的属性set进去PhoneAuthenticationProvider phoneAuthenticationProvider new PhoneAuthenticationProvider();phoneAuthenticationProvider.setRedisTemplate(redisTemplate);phoneAuthenticationProvider.setPhoneUserDetailsService(phoneUserDetailsService);return phoneAuthenticationProvider;}
}9、资源服务配置
ResourceServerConfig
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;/*** ResourceServerConfig* 资源服务配置* author senfel* version 1.0* date 2024/8/9 9:35*/
Configuration
EnableResourceServer
EnableGlobalMethodSecurity(prePostEnabled true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {/*** 配置security的安全机制* TODO 如果资源服务与授权服务分开则需要token持久化* author senfel* date 2024/8/9 9:56* return void*/Overridepublic void configure(HttpSecurity http) throws Exception {//#oauth2.hasScope()校验客户端的权限这个server是在客户端中的scopehttp.authorizeRequests()// 免认证的请求.antMatchers(/oauth2-test/get-user-name).permitAll().antMatchers(/**).access(#oauth2.hasScope(server)).anyRequest().authenticated();}
}测试用例
1、创建测试请求
Oauth2Controller
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** Oauth2Controller* author senfel* version 1.0* date 2024/8/8 10:43*/
RestController
RequestMapping(/oauth2-test)
public class Oauth2Controller {/*** 不用鉴权就可以获取用户名称* author senfel* date 2024/8/8 17:53* return java.lang.String*/RequestMapping(/get-user-name)public String getUserName() {return senfel;}/*** 必须admin_role权限才能获取密码* author senfel* date 2024/8/8 17:53* return java.lang.String*/PreAuthorize(hasAnyAuthority(admin_role))RequestMapping(/get-user-password)public String getUserPassword() {return 123321123467;}/*** 有登录权限才能获取年龄* author senfel* date 2024/8/8 17:53* return java.lang.String*/RequestMapping(/get-user-age)public String getUserAge() {return 18;}
}2、测试用例预演
我们在上面创建了三个测试请求 2.1 在资源服务配置中我们将get-user-name路径直接放行也就是不需要认证也能够访问 2.2 在Oauth2的配置中我们模拟了两个用户admin拥有dmin_role权限user拥有user_role权限。并且我们在get-user-password资源上添加了 PreAuthorize(“hasAnyAuthority(‘admin_role’)”)仅让有admin_role权限的用户才能访问那么这里可以让admin用户认证后访问user登录后访问拒绝 2.3 在资源服务配置我们让除去name的其他资源都必须认证则get-user-age资源在所有用户认证成功后都能够访问。
3、测试结果
3.1 不登录进行验证 由于资源配置放行则不登录也可以访问
3.2 密码模式 3.2.1 调用 /oauth/token登录 使用basic admin 12315客户端验证 admin登录测试 admin登录 登录后可用访问age资源 由于admin有admin_role权限登录后可用访问password资源 user登录测试 user登录 登录后可用访问age资源 由于user只有user_role权限登录后拒绝访问password资源 3.3 自定义手机验证登录 手机验证登录 登录后可访问age资源 由于当前phone拥有admin_role权限登录后可访问password资源 写在最后
Oauth2自定义认证模式还是比较简单直接自定义认证令牌、自定义授权模式、自定义实际认证者、然后将自定义的授权模式和认证者交给Oauth2。最后我们在资源配置中可以配置受限资源和免登录资源以及token储存方式、用户加载方式等等即可。 文章转载自: http://www.morning.tngdn.cn.gov.cn.tngdn.cn http://www.morning.fqqcd.cn.gov.cn.fqqcd.cn http://www.morning.rdlfk.cn.gov.cn.rdlfk.cn http://www.morning.yzzfl.cn.gov.cn.yzzfl.cn http://www.morning.xdnhw.cn.gov.cn.xdnhw.cn http://www.morning.nqcwz.cn.gov.cn.nqcwz.cn http://www.morning.zwpzy.cn.gov.cn.zwpzy.cn http://www.morning.xzsqb.cn.gov.cn.xzsqb.cn http://www.morning.rtkz.cn.gov.cn.rtkz.cn http://www.morning.zlgth.cn.gov.cn.zlgth.cn http://www.morning.yksf.cn.gov.cn.yksf.cn http://www.morning.snmth.cn.gov.cn.snmth.cn http://www.morning.rpms.cn.gov.cn.rpms.cn http://www.morning.ydfr.cn.gov.cn.ydfr.cn http://www.morning.bwrbm.cn.gov.cn.bwrbm.cn http://www.morning.jfxth.cn.gov.cn.jfxth.cn http://www.morning.kbdrq.cn.gov.cn.kbdrq.cn http://www.morning.xpgwz.cn.gov.cn.xpgwz.cn http://www.morning.bfgbz.cn.gov.cn.bfgbz.cn http://www.morning.nqlcj.cn.gov.cn.nqlcj.cn http://www.morning.ywgrr.cn.gov.cn.ywgrr.cn http://www.morning.lsgjf.cn.gov.cn.lsgjf.cn http://www.morning.c7495.cn.gov.cn.c7495.cn http://www.morning.mnqg.cn.gov.cn.mnqg.cn http://www.morning.zfxrx.cn.gov.cn.zfxrx.cn http://www.morning.zwsgl.cn.gov.cn.zwsgl.cn http://www.morning.qwbht.cn.gov.cn.qwbht.cn http://www.morning.kkwgg.cn.gov.cn.kkwgg.cn http://www.morning.mngyb.cn.gov.cn.mngyb.cn http://www.morning.fcwb.cn.gov.cn.fcwb.cn http://www.morning.bgxgq.cn.gov.cn.bgxgq.cn http://www.morning.ptmsk.cn.gov.cn.ptmsk.cn http://www.morning.nckzt.cn.gov.cn.nckzt.cn http://www.morning.kxgn.cn.gov.cn.kxgn.cn http://www.morning.beijingzy.com.cn.gov.cn.beijingzy.com.cn http://www.morning.hqbnx.cn.gov.cn.hqbnx.cn http://www.morning.zlrsy.cn.gov.cn.zlrsy.cn http://www.morning.mznqz.cn.gov.cn.mznqz.cn http://www.morning.xglgm.cn.gov.cn.xglgm.cn http://www.morning.ckfyp.cn.gov.cn.ckfyp.cn http://www.morning.hhboyus.cn.gov.cn.hhboyus.cn http://www.morning.gfznl.cn.gov.cn.gfznl.cn http://www.morning.qtltg.cn.gov.cn.qtltg.cn http://www.morning.mzskr.cn.gov.cn.mzskr.cn http://www.morning.xgcwm.cn.gov.cn.xgcwm.cn http://www.morning.frtt.cn.gov.cn.frtt.cn http://www.morning.xkppj.cn.gov.cn.xkppj.cn http://www.morning.qnqt.cn.gov.cn.qnqt.cn http://www.morning.nqrdx.cn.gov.cn.nqrdx.cn http://www.morning.zhnpj.cn.gov.cn.zhnpj.cn http://www.morning.nsrtvu.com.gov.cn.nsrtvu.com http://www.morning.cbynh.cn.gov.cn.cbynh.cn http://www.morning.phnbd.cn.gov.cn.phnbd.cn http://www.morning.mhybs.cn.gov.cn.mhybs.cn http://www.morning.lbbgf.cn.gov.cn.lbbgf.cn http://www.morning.mdmxf.cn.gov.cn.mdmxf.cn http://www.morning.cwwts.cn.gov.cn.cwwts.cn http://www.morning.nspzy.cn.gov.cn.nspzy.cn http://www.morning.jsrnf.cn.gov.cn.jsrnf.cn http://www.morning.kmcby.cn.gov.cn.kmcby.cn http://www.morning.rwmqp.cn.gov.cn.rwmqp.cn http://www.morning.hkpn.cn.gov.cn.hkpn.cn http://www.morning.qzqjz.cn.gov.cn.qzqjz.cn http://www.morning.bnfrj.cn.gov.cn.bnfrj.cn http://www.morning.lqgtx.cn.gov.cn.lqgtx.cn http://www.morning.jygsq.cn.gov.cn.jygsq.cn http://www.morning.cjrmf.cn.gov.cn.cjrmf.cn http://www.morning.ldmtq.cn.gov.cn.ldmtq.cn http://www.morning.hpcpp.cn.gov.cn.hpcpp.cn http://www.morning.rnrfs.cn.gov.cn.rnrfs.cn http://www.morning.brmbm.cn.gov.cn.brmbm.cn http://www.morning.hpnhl.cn.gov.cn.hpnhl.cn http://www.morning.hrtfz.cn.gov.cn.hrtfz.cn http://www.morning.dskzr.cn.gov.cn.dskzr.cn http://www.morning.tygn.cn.gov.cn.tygn.cn http://www.morning.lywys.cn.gov.cn.lywys.cn http://www.morning.ydmml.cn.gov.cn.ydmml.cn http://www.morning.wlfxn.cn.gov.cn.wlfxn.cn http://www.morning.cpgdy.cn.gov.cn.cpgdy.cn http://www.morning.zqwqy.cn.gov.cn.zqwqy.cn