最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot和Redis實現(xiàn)Token權(quán)限認(rèn)證的實例講解

 更新時間:2021年02月18日 15:28:18   作者:止步前行  
這篇文章主要介紹了SpringBoot和Redis實現(xiàn)Token權(quán)限認(rèn)證的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧

一、引言

登陸權(quán)限控制是每個系統(tǒng)都應(yīng)必備的功能,實現(xiàn)方法也有好多種。下面使用Token認(rèn)證來實現(xiàn)系統(tǒng)的權(quán)限訪問。

功能描述:

用戶登錄成功后,后臺返回一個token給調(diào)用者,同時自定義一個@AuthToken注解,被該注解標(biāo)注的API請求都需要進(jìn)行token效驗,效驗通過才可以正常訪問,實現(xiàn)接口級的鑒權(quán)控制。

同時token具有生命周期,在用戶持續(xù)一段時間不進(jìn)行操作的話,token則會過期,用戶一直操作的話,則不會過期。

二、環(huán)境

SpringBoot

Redis(Docke中鏡像)

MySQL(Docker中鏡像)

三、流程分析

1、流程分析

(1)、客戶端登錄,輸入用戶名和密碼,后臺進(jìn)行驗證,如果驗證失敗則返回登錄失敗的提示。

如果驗證成功,則生成 token 然后將 username 和 token 雙向綁定 (可以根據(jù) username 取出 token 也可以根據(jù) token 取出username)存入redis,同時使用 token+username 作為key把當(dāng)前時間戳也存入redis。并且給它們都設(shè)置過期時間。

(2)、每次請求接口都會走攔截器,如果該接口標(biāo)注了@AuthToken注解,則要檢查客戶端傳過來的Authorization字段,獲取 token。

由于 token 與 username 雙向綁定,可以通過獲取的 token 來嘗試從 redis 中獲取 username,如果可以獲取則說明 token 正確,反之,說明錯誤,返回鑒權(quán)失敗。

(3)、token可以根據(jù)用戶使用的情況來動態(tài)的調(diào)整自己過期時間。

在生成 token 的同時也往 redis 里面存入了創(chuàng)建 token 時的時間戳,每次請求被攔截器攔截 token 驗證成功之后,將當(dāng)前時間與存在 redis 里面的 token 生成時刻的時間戳進(jìn)行比較,當(dāng)當(dāng)前時間的距離創(chuàng)建時間快要到達(dá)設(shè)置的redis過期時間的話,就重新設(shè)置token過期時間,將過期時間延長。

如果用戶在設(shè)置的 redis 過期時間的時間長度內(nèi)沒有進(jìn)行任何操作(沒有發(fā)請求),則token會在redis中過期。

四、具體代碼實現(xiàn)

1、自定義注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthToken {
}

2、登陸控制器

@RestController
public class welcome {
 Logger logger = LoggerFactory.getLogger(welcome.class);
 @Autowired
 Md5TokenGenerator tokenGenerator;
 @Autowired
 UserMapper userMapper;
 @GetMapping("/welcome")
 public String welcome(){
  return "welcome token authentication";
 }
 @RequestMapping(value = "/login", method = RequestMethod.GET)
 public ResponseTemplate login(String username, String password) {
  logger.info("username:"+username+"  password:"+password);
  User user = userMapper.getUser(username,password);
  logger.info("user:"+user);
  JSONObject result = new JSONObject();
  if (user != null) {
   Jedis jedis = new Jedis("192.168.1.106", 6379);
   String token = tokenGenerator.generate(username, password);
   jedis.set(username, token);
   //設(shè)置key生存時間,當(dāng)key過期時,它會被自動刪除,時間是秒
   jedis.expire(username, ConstantKit.TOKEN_EXPIRE_TIME);
   jedis.set(token, username);
   jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);
   Long currentTime = System.currentTimeMillis();
   jedis.set(token + username, currentTime.toString());
   //用完關(guān)閉
   jedis.close();
   result.put("status", "登錄成功");
   result.put("token", token);
  } else {
   result.put("status", "登錄失敗");
  }
  return ResponseTemplate.builder()
    .code(200)
    .message("登錄成功")
    .data(result)
    .build();
 }
 //測試權(quán)限訪問
 @RequestMapping(value = "test", method = RequestMethod.GET)
 @AuthToken
 public ResponseTemplate test() {
  logger.info("已進(jìn)入test路徑");
  return ResponseTemplate.builder()
    .code(200)
    .message("Success")
    .data("test url")
    .build();
 }
}

3、攔截器

@Slf4j
public class AuthorizationInterceptor implements HandlerInterceptor {
 //存放鑒權(quán)信息的Header名稱,默認(rèn)是Authorization
 private String httpHeaderName = "Authorization";
 //鑒權(quán)失敗后返回的錯誤信息,默認(rèn)為401 unauthorized
 private String unauthorizedErrorMessage = "401 unauthorized";
 //鑒權(quán)失敗后返回的HTTP錯誤碼,默認(rèn)為401
 private int unauthorizedErrorCode = HttpServletResponse.SC_UNAUTHORIZED;
 //存放登錄用戶模型Key的Request Key
 public static final String REQUEST_CURRENT_KEY = "REQUEST_CURRENT_KEY";
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  if (!(handler instanceof HandlerMethod)) {
   return true;
  }
  HandlerMethod handlerMethod = (HandlerMethod) handler;
  Method method = handlerMethod.getMethod();
  // 如果打上了AuthToken注解則需要驗證token
  if (method.getAnnotation(AuthToken.class) != null || handlerMethod.getBeanType().getAnnotation(AuthToken.class) != null) {
   String token = request.getParameter(httpHeaderName);
   log.info("Get token from request is {} ", token);
   String username = "";
   Jedis jedis = new Jedis("192.168.1.106", 6379);
   if (token != null && token.length() != 0) {
    username = jedis.get(token);
    log.info("Get username from Redis is {}", username);
   }
   if (username != null && !username.trim().equals("")) {
    Long tokeBirthTime = Long.valueOf(jedis.get(token + username));
    log.info("token Birth time is: {}", tokeBirthTime);
    Long diff = System.currentTimeMillis() - tokeBirthTime;
    log.info("token is exist : {} ms", diff);
    if (diff > ConstantKit.TOKEN_RESET_TIME) {
     jedis.expire(username, ConstantKit.TOKEN_EXPIRE_TIME);
     jedis.expire(token, ConstantKit.TOKEN_EXPIRE_TIME);
     log.info("Reset expire time success!");
     Long newBirthTime = System.currentTimeMillis();
     jedis.set(token + username, newBirthTime.toString());
    }
    //用完關(guān)閉
    jedis.close();
    request.setAttribute(REQUEST_CURRENT_KEY, username);
    return true;
   } else {
    JSONObject jsonObject = new JSONObject();
    PrintWriter out = null;
    try {
     response.setStatus(unauthorizedErrorCode);
     response.setContentType(MediaType.APPLICATION_JSON_VALUE);
     jsonObject.put("code", ((HttpServletResponse) response).getStatus());
     jsonObject.put("message", HttpStatus.UNAUTHORIZED);
     out = response.getWriter();
     out.println(jsonObject);
     return false;
    } catch (Exception e) {
     e.printStackTrace();
    } finally {
     if (null != out) {
      out.flush();
      out.close();
     }
    }
   }
  }
  request.setAttribute(REQUEST_CURRENT_KEY, null);
  return true;
 }
 @Override
 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
 }
 @Override
 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
 }
}

4、測試結(jié)果

五、小結(jié)

登陸權(quán)限控制,實際上利用的就是攔截器的攔截功能。因為每一次請求都要通過攔截器,只有攔截器驗證通過了,才能訪問想要的請求路徑,所以在攔截器中做校驗Token校驗。

想要代碼,可以去GitHub上查看。

https://github.com/Hofanking/token-authentication.git

攔截器介紹,可以參考 這篇文章

補(bǔ)充:springboot+spring security+redis實現(xiàn)登錄權(quán)限管理

筆者負(fù)責(zé)的電商項目的技術(shù)體系是基于SpringBoot,為了實現(xiàn)一套后端能夠承載ToB和ToC的業(yè)務(wù),需要完善現(xiàn)有的權(quán)限管理體系。

在查看Shiro和Spring Security對比后,筆者認(rèn)為Spring Security更加適合本項目使用,可以總結(jié)為以下2點:

1、基于攔截器的權(quán)限校驗邏輯,可以針對ToB的業(yè)務(wù)接口來做相關(guān)的權(quán)限校驗,以筆者的項目為例,ToB的接口請求路徑以/openshop/api/開頭,可以根據(jù)接口請求路徑配置全局的ToB的攔截器;

2、Spring Security的權(quán)限管理模型更簡單直觀,對權(quán)限、角色和用戶做了很好的解耦。

以下介紹本項目的實現(xiàn)步驟

一、在項目中添加Spring相關(guān)依賴

 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
   <version>1.5.3.RELEASE</version>
  </dependency>
  <dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>4.3.8.RELEASE</version>
  </dependency>

二、使用模板模式定義權(quán)限管理攔截器抽象類

public abstract class AbstractAuthenticationInterceptor extends HandlerInterceptorAdapter implements InitializingBean {
 @Resource
 private AccessDecisionManager accessDecisionManager;
 @Override
 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  //檢查是否登錄
  String userId = null;
  try {
   userId = getUserId();
  }catch (Exception e){
   JsonUtil.renderJson(response,403,"{}");
   return false;
  }
  if(StringUtils.isEmpty(userId)){
   JsonUtil.renderJson(response,403,"{}");
   return false;
  }
  //檢查權(quán)限
  Collection<? extends GrantedAuthority> authorities = getAttributes(userId);
  Collection<ConfigAttribute> configAttributes = getAttributes(request);
  return accessDecisionManager.decide(authorities,configAttributes);
 }
 //獲取用戶id
 public abstract String getUserId();
 //根據(jù)用戶id獲取用戶的角色集合
 public abstract Collection<? extends GrantedAuthority> getAttributes(String userId);
 //查詢請求需要的權(quán)限
 public abstract Collection<ConfigAttribute> getAttributes(HttpServletRequest request);
}

三、權(quán)限管理攔截器實現(xiàn)類 AuthenticationInterceptor

@Component
public class AuthenticationInterceptor extends AbstractAuthenticationInterceptor {
 @Resource
 private SessionManager sessionManager;
 @Resource
 private UserPermissionService customUserService;
 @Override
 public String getUserId() {
  return sessionManager.obtainUserId();
 }
 @Override
 public Collection<? extends GrantedAuthority> getAttributes(String s) {
  return customUserService.getAuthoritiesById(s);
 }
 @Override
 public Collection<ConfigAttribute> getAttributes(HttpServletRequest request) {
  return customUserService.getAttributes(request);
 }
 @Override
 public void afterPropertiesSet() throws Exception {
 }
}

四、用戶Session信息管理類

集成redis維護(hù)用戶session信息

@Component
public class SessionManager {
 private static final Logger logger = LoggerFactory.getLogger(SessionManager.class);
 @Autowired
 private RedisUtils redisUtils;
 public SessionManager() {
 }
 public UserInfoDTO obtainUserInfo() {
  UserInfoDTO userInfoDTO = null;
  try {
   String token = this.obtainToken();
   logger.info("=======token=========", token);
   if (StringUtils.isEmpty(token)) {
    LemonException.throwLemonException(AccessAuthCode.sessionExpired.getCode(), AccessAuthCode.sessionExpired.getDesc());
   }
   userInfoDTO = (UserInfoDTO)this.redisUtils.obtain(this.obtainToken(), UserInfoDTO.class);
  } catch (Exception var3) {
   logger.error("obtainUserInfo ex:", var3);
  }
  if (null == userInfoDTO) {
   LemonException.throwLemonException(AccessAuthCode.sessionExpired.getCode(), AccessAuthCode.sessionExpired.getDesc());
  }
  return userInfoDTO;
 }
 public String obtainUserId() {
  return this.obtainUserInfo().getUserId();
 }
 public String obtainToken() {
  HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
  String token = request.getHeader("token");
  return token;
 }
 public UserInfoDTO createSession(UserInfoDTO userInfoDTO, long expired) {
  String token = UUIDUtil.obtainUUID("token.");
  userInfoDTO.setToken(token);
  if (expired == 0L) {
   this.redisUtils.put(token, userInfoDTO);
  } else {
   this.redisUtils.put(token, userInfoDTO, expired);
  }
  return userInfoDTO;
 }
 public void destroySession() {
  String token = this.obtainToken();
  if (StringUtils.isNotBlank(token)) {
   this.redisUtils.remove(token);
  }
 }
}

五、用戶權(quán)限管理service

@Service
public class UserPermissionService {
 @Resource
 private SysUserDao userDao;
 @Resource
 private SysPermissionDao permissionDao;
 private HashMap<String, Collection<ConfigAttribute>> map =null;
 /**
  * 加載資源,初始化資源變量
  */
 public void loadResourceDefine(){
  map = new HashMap<>();
  Collection<ConfigAttribute> array;
  ConfigAttribute cfg;
  List<SysPermission> permissions = permissionDao.findAll();
  for(SysPermission permission : permissions) {
   array = new ArrayList<>();
   cfg = new SecurityConfig(permission.getName());
   array.add(cfg);
   map.put(permission.getUrl(), array);
  }
 }
/*
*
 * @Author zhangs
 * @Description 獲取用戶權(quán)限列表
 * @Date 18:56 2019/11/11
 **/
 public List<GrantedAuthority> getAuthoritiesById(String userId) {
  SysUserRspDTO user = userDao.findById(userId);
  if (user != null) {
   List<SysPermission> permissions = permissionDao.findByAdminUserId(user.getUserId());
   List<GrantedAuthority> grantedAuthorities = new ArrayList <>();
   for (SysPermission permission : permissions) {
    if (permission != null && permission.getName()!=null) {
     GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
     grantedAuthorities.add(grantedAuthority);
    }
   }
   return grantedAuthorities;
  }
  return null;
 }
 /*
 *
  * @Author zhangs
  * @Description 獲取當(dāng)前請求所需權(quán)限 
  * @Date 18:57 2019/11/11
  **/
 public Collection<ConfigAttribute> getAttributes(HttpServletRequest request) throws IllegalArgumentException {
  if(map !=null) map.clear();
  loadResourceDefine();
  AntPathRequestMatcher matcher;
  String resUrl;
  for(Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
   resUrl = iter.next();
   matcher = new AntPathRequestMatcher(resUrl);
   if(matcher.matches(request)) {
    return map.get(resUrl);
   }
  }
  return null;
 }
}

六、權(quán)限校驗類 AccessDecisionManager

通過查看authorities中的權(quán)限列表是否含有configAttributes中所需的權(quán)限,判斷用戶是否具有請求當(dāng)前資源或者執(zhí)行當(dāng)前操作的權(quán)限。

@Service
public class AccessDecisionManager {
 public boolean decide(Collection<? extends GrantedAuthority> authorities, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
  if(null== configAttributes || configAttributes.size() <=0) {
   return true;
  }
  ConfigAttribute c;
  String needRole;
  for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
   c = iter.next();
   needRole = c.getAttribute();
   for(GrantedAuthority ga : authorities) {
    if(needRole.trim().equals(ga.getAuthority())) {
     return true;
    }
   }
  }
  return false;
 }
}

七、配置攔截規(guī)則

@Configuration
public class WebAppConfigurer extends WebMvcConfigurerAdapter {
 @Resource
 private AbstractAuthenticationInterceptor authenticationInterceptor;
 @Override
 public void addInterceptors(InterceptorRegistry registry) {
  // 多個攔截器組成一個攔截器鏈
  // addPathPatterns 用于添加攔截規(guī)則
  // excludePathPatterns 用戶排除攔截
  //對來自/openshop/api/** 這個鏈接來的請求進(jìn)行攔截
  registry.addInterceptor(authenticationInterceptor).addPathPatterns("/openshop/api/**");
  super.addInterceptors(registry);
 }
}

八 相關(guān)表說明

用戶表 sys_user

CREATE TABLE `sys_user` (
 `user_id` varchar(64) NOT NULL COMMENT '用戶ID',
 `username` varchar(255) DEFAULT NULL COMMENT '登錄賬號',
 `first_login` datetime(6) NOT NULL COMMENT '首次登錄時間',
 `last_login` datetime(6) NOT NULL COMMENT '上次登錄時間',
 `pay_pwd` varchar(100) DEFAULT NULL COMMENT '支付密碼',
 `chant_id` varchar(64) NOT NULL DEFAULT '-1' COMMENT '關(guān)聯(lián)商戶id',
 `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
 `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
 PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

角色表 sys_role

CREATE TABLE `sys_role` (
 `role_id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) DEFAULT NULL,
 `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
 `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
 PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

用戶角色關(guān)聯(lián)表 sys_role_user

CREATE TABLE `sys_role_user` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `sys_user_id` varchar(64) DEFAULT NULL,
 `sys_role_id` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

權(quán)限表 sys_premission

CREATE TABLE `sys_permission` (
 `permission_id` int(11) NOT NULL,
 `name` varchar(255) DEFAULT NULL COMMENT '權(quán)限名稱',
 `description` varchar(255) DEFAULT NULL COMMENT '權(quán)限描述',
 `url` varchar(255) DEFAULT NULL COMMENT '資源url',
 `check_pwd` int(2) NOT NULL DEFAULT '1' COMMENT '是否檢查支付密碼:0不需要 1 需要',
 `check_sms` int(2) NOT NULL DEFAULT '1' COMMENT '是否校驗短信驗證碼:0不需要 1 需要',
 `create_time` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
 `modify_time` datetime DEFAULT NULL COMMENT '修改時間',
 PRIMARY KEY (`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

角色權(quán)限關(guān)聯(lián)表 sys_permission_role

CREATE TABLE `sys_permission_role` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `role_id` int(11) DEFAULT NULL,
 `permission_id` int(11) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。

相關(guān)文章

  • SpringBoot統(tǒng)一處理功能實現(xiàn)的全過程

    SpringBoot統(tǒng)一處理功能實現(xiàn)的全過程

    最近在做項目時需要對異常進(jìn)行全局統(tǒng)一處理,主要是一些分類入庫以及記錄日志等,下面這篇文章主要給大家介紹了關(guān)于SpringBoot統(tǒng)一功能處理實現(xiàn)的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • 一文深入分析java.lang.ClassNotFoundException異常

    一文深入分析java.lang.ClassNotFoundException異常

    這篇文章主要給大家介紹了關(guān)于java.lang.ClassNotFoundException異常的相關(guān)資料,java.lang.ClassNotFoundException是Java編程時經(jīng)常會遇到的一個異常,它表示JVM在嘗試加載某個類時未能找到該類,需要的朋友可以參考下
    2023-10-10
  • 如何解決Mybatis--java.lang.IllegalArgumentException: Result Maps collection already contains value for X

    如何解決Mybatis--java.lang.IllegalArgumentException: Result Maps

    這兩天因為項目需要整合spring、struts2、mybatis三大框架,但啟動的時候總出現(xiàn)這個錯誤,困擾我好久,折騰了好久終于找到問題根源,下面小編給大家分享下問題所在及解決辦法,一起看看吧
    2016-12-12
  • Java操作xls替換文本或圖片的功能實現(xiàn)

    Java操作xls替換文本或圖片的功能實現(xiàn)

    這篇文章主要給大家介紹了關(guān)于Java操作xls替換文本或圖片功能實現(xiàn)的相關(guān)資料,文中通過示例代碼講解了文件上傳、文件處理和Excel文件生成,需要的朋友可以參考下
    2024-12-12
  • Java連接postgresql數(shù)據(jù)庫的示例代碼

    Java連接postgresql數(shù)據(jù)庫的示例代碼

    本篇文章主要介紹了Java連接postgresql數(shù)據(jù)庫的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08
  • Spring基于注解配置事務(wù)的屬性

    Spring基于注解配置事務(wù)的屬性

    這篇文章主要為大家詳細(xì)介紹了Spring基于注解配置事務(wù)的屬性,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • Java中的單向鏈表詳解

    Java中的單向鏈表詳解

    這篇文章主要介紹了Java中的單向鏈表詳解,單向鏈表又叫單鏈表,是鏈表的一種,由節(jié)點構(gòu)成,head指針指向第一個稱為表頭節(jié)點,而終止指向最后一個null指針,需要的朋友可以參考下
    2024-01-01
  • MyBatis Plus實現(xiàn)一對多的查詢場景的三種方法

    MyBatis Plus實現(xiàn)一對多的查詢場景的三種方法

    MyBatis Plus提供了多種簡便的方式來進(jìn)行一對多子查詢,本文主要介紹了MyBatis Plus實現(xiàn)一對多的查詢場景的三種方法,具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • jvm添加自定義dns實現(xiàn)過程示例

    jvm添加自定義dns實現(xiàn)過程示例

    這篇文章主要為大家介紹了jvm添加自定義dns實現(xiàn)過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • Java基礎(chǔ)之內(nèi)存泄漏與溢出詳解

    Java基礎(chǔ)之內(nèi)存泄漏與溢出詳解

    今天帶大家來了解一下Java內(nèi)存泄漏與溢出的知識,文中有非常詳細(xì)的介紹,對正在學(xué)習(xí)Java基礎(chǔ)的各位小伙伴呢很有幫助喲,需要的朋友可以參考下
    2021-05-05

最新評論

永城市| 东乡县| 浦城县| 清镇市| 西畴县| 赫章县| 眉山市| 靖安县| 兴业县| 那坡县| 晋宁县| 钦州市| 土默特右旗| 中山市| 辽宁省| 绥滨县| 安岳县| 德州市| 太仓市| 沂水县| 中牟县| 凤山市| 灵川县| 兴仁县| 望奎县| 灵台县| 上饶市| 沐川县| 孝义市| 望江县| 荆门市| 萨迦县| 吉水县| 定陶县| 汽车| 永丰县| 维西| 曲阜市| 阳东县| 上林县| 梨树县|