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

SpringSecurity角色權限控制(SpringBoot+SpringSecurity+JWT)

 更新時間:2024年05月07日 11:32:38   作者:lans_g  
本文主要介紹了SpringSecurity角色權限控制(SpringBoot+SpringSecurity+JWT),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

一、項目介紹

通過springboot整合jwt和security,以用戶名/密碼的方式進行認證和授權。認證通過jwt+數據庫的,授權這里使用了兩種方式,分別是SpringSecurity自帶的hasRole方法+SecurityConfig 和 我們自定義的permission+@PreAuthorize注解。

二、SpringSecurity簡介

SpringSecurity中的幾個重要組件:

1.SecurityContextHolder(class)

用來存儲和獲取當前線程關聯(lián)的 SecurityContext 對象的類。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ku2An95n-1683728698707)(E:/Blog/lansg/source/img/1683186758072-ccd6a67a-c2c5-4ec7-aca3-b42404ce8277.png)]

其中有兩種 SecurityContext 模式:

  • MODE_THREADLOCAL:將 SecurityContext 對象存儲到當前線程中,只在當前線程中可見。多線程時,每個線程的 SecurityContext 對象都是獨立的。
  • MODE_INHERITABLETHREADLOCAL:將 SecurityContext 對象存儲到當前線程中,對當前線程和子線程都可見。也就是說,在當前線程中存儲的 SecurityContext 對象可以被傳遞給子線程使用。

表示用戶已通過身份驗證的最簡單方法就是設置 SecurityContextHolder!

2.SecurityContext(Interface)

用來存儲當前已經被認證的用戶,包含了當前執(zhí)行操作的線程上下文信息以及用戶認證和授權信息。包含Authentication。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-KFfG9auG-1683728698708)(E:/Blog/lansg/source/img/1683186616655-7d1408b0-f5aa-4a06-ae7e-417d63c3fe94.png)]

3.Authentication(Interface)

存儲了當前正在執(zhí)行操作的用戶的身份驗證信息,包括用戶名、密碼、權限,可以作為AuthenticationManager的輸入,它包含principal、credentials、authorities。

  • principal:用戶的標識。通常情況下是UserDetails接口的一個實例。
  • credentials:用戶的密碼。多數情況下,為確保密碼不被泄露,會在用戶身份驗證之后被清除。
  • authorities:用戶擁有的權限。

Authentication 接口的常用實現類有以下幾種:

  • AnonymousAuthenticationToken:匿名用戶的身份驗證信息。
  • UsernamePasswordAuthenticationToken:用戶名/密碼的身份驗證信息。
  • RememberMeAuthenticationToken:用于“記住我”功能的身份驗證信息。
  • PreAuthenticatedAuthenticationToken:基于預先認證信息的身份驗證。

通常情況下,用戶在進行登錄時需要通過身份驗證,當身份驗證成功時,就會通過 Authentication 接口封裝用戶的身份信息。在后續(xù)的操作中,認證后的用戶可以通過 SecurityContextHolder 獲取 Authentication 對象,并根據其中的信息獲得用戶的身份信息及相應的權限等。

對比SecurityContext和Authentication

Authentication 是一個封裝了用戶身份認證信息的對象,表示用戶已經通過了驗證。

SecurityContext 則是一個上下文類對象,用于保存和獲取當前線程關聯(lián)的上下文信息,包括了 Authentication 對象。

4.AuthenticationManager(Interface)

定義了用戶身份驗證的api接口(例如將用戶名和密碼和數據庫進行比對)??梢越邮找粋€Authentication對象作為入參,驗證成功后會返回已被驗證的Authentication對象。

5.GrantedAuthority(Interface)

授權信息以GrantedAuthority的形式存儲在Authentication對象中,GrantedAuthority接口表示一個授權(權限)對象,包含一個字符串類型的授權名字(authority name)。授權名字通常是一個表示權限的字符串,例如"ROLE_ADMIN"、"ROLE_USER"等

二、整理思路

  • 搭建springboot項目,導入相關依賴
  • 在數據庫導入sql創(chuàng)建用戶表
  • 創(chuàng)建幾個關于User的對象便于數據傳輸
  • dao層開發(fā)(對用戶信息增刪改查)
  • 實現UserDetails接口和UserDetailService接口
  • 自定義實現校驗token的攔截器JwtAuthenticationTokenFilter
  • 自定義實現用戶登錄校驗的攔截器JWTAuthenticationFilter
  • service層開發(fā)(包括PermissionService和UserService)
  • 創(chuàng)建測試Controller
  • 實現SpringSecurity的配置類SecurityConfig
  • 通過postman進行測試

三、具體實現步驟

1.項目主要相關依賴

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.3.12.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.3.12.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
  </dependency>
  <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.0</version>
  </dependency>
</dependencies>

2.用戶表

創(chuàng)建一個名為security_jwt_demo的數據庫,導入項目根目錄下/db/db.sql文件即可。

3.項目中用到的幾個user相關對象

(1)User實體

對應數據庫中的user表

@Data
public class User {

    private Long id;
    private String username;
    private String password;
    private String permission;
    private String role;

    @Override
    public String toString() {
        return "User{" +
        "id=" + id +
        ", username='" + username + '\'' +
        ", password='" + password + '\'' +
        ", permission='" + permission + '\'' +
        ", role='" + role + '\'' +
        '}';
    }
}

(2)LoginUser

UserDetails的實現類,使用用戶名/密碼驗證時需要用到其作為返回值。這個類中包含了用戶名、密碼和當前登錄用戶所具備的權限。

@Data
public class LoginUser implements UserDetails {

    private Long id;
    private String username;
    private String password;
    //通過自定義方式進行授權
    private Set<String> permissions = new HashSet<String>();

    //通過springSecurity進行授權
    private Collection<? extends GrantedAuthority> authorities;

    public LoginUser(){}

    public LoginUser(User user,Collection<? extends GrantedAuthority> authorities) {
        id = user.getId();
        username = user.getUsername();
        password = user.getPassword();
        permissions.add(user.getPermission());
        this.authorities = authorities;

    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

(3)UserVO

主要用來登錄時從輸入流中獲取登錄用戶的信息,對應了前端傳遞的參數,包括用戶名、密碼、記住我等屬性。

@Data
public class UserVO {
    private String username;
    private String password;
    private Integer rememberMe;
}

4.dao層開發(fā)

創(chuàng)建mapper接口并配置對應的mapper.xml

@Mapper
public interface UserMapper {

    //根據用戶名獲取用戶
    User getByName(String username);

    //根據用戶id獲取用戶權限
    List<String> getPermissionById(Long id);

    //新增一個用戶
    int insertUser(User user);
}

5.實現UserDetail接口和UserDetailService接口

首先說一下為什么要實現這兩個接口。

SpringSecurity提供了多種身份驗證的方式,在這里我們使用的是用戶名/密碼的方式進行驗證,而如果要使用這種方式進行驗證的話,我們需要實現UserDetailService接口中的loadUserByUsername方法,這個方法用來從數據庫中進行查詢用戶,然后和傳入的用戶密碼進行比對。這個方法會返回一個UserDetails對象。

@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getByName(username);
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        return new LoginUser(user,authorities);
    }
}

對于UserDetailService接口,SpringSecurity提供了基于內存和JDBC的兩種驗證方式,默認是JDBC的方式。我們也可以通過自定義實現UserDetailService接口,來達到自定義身份驗證的結果。這里我們使用的是自定義身份驗證的方式。

對于UserDetails,在Spring Security中,UserDetails接口是表示用戶信息的規(guī)范。該接口表示應用程序中的用戶,并提供有關用戶的基本信息,如用戶名、密碼、角色、權限等,因此我們需要有一個類似用戶的對象來實現該接口(LoginUser)。

6.JwtAuthenticationTokenFilter

檢驗用戶token的過濾器。對于客戶端發(fā)出的請求,首先對用戶的token進行校驗,如果token不合法表示當前用戶未登錄,繼續(xù)執(zhí)行其他過濾器的邏輯;如果token合法則設置SecurityContextHolder表示用戶已被認證。

/**
 * token過濾器 驗證token有效性
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String token = request.getHeader(JwtTokenUtil.TOKEN_HEADER);
        if (StringUtils.isBlank(token) || !token.startsWith(JwtTokenUtil.TOKEN_PREFIX)){
            chain.doFilter(request,response);
            return;
        }
        try {
            //如果能獲取到token則Authentication進行設置,表示已認證
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(token));
        } catch (Exception e) {
            e.printStackTrace();
        }
        //繼續(xù)執(zhí)行其他過濾器的邏輯
        chain.doFilter(request,response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws Exception {
        String token = tokenHeader.replace(JwtTokenUtil.TOKEN_PREFIX,"");
        //判斷token是否過期
        boolean expiration = JwtTokenUtil.isExpiration(token);
        if (expiration){
            throw new Exception("過期了");
        }else{
            String username = JwtTokenUtil.getUsername(token);
            User user = userMapper.getByName(username);
            List<String> permissions = userMapper.getPermissionById(user.getId());
            LoginUser loginUser = new LoginUser(user, Collections.singleton(new SimpleGrantedAuthority(user.getRole())));
            loginUser.setPermissions(new HashSet<>(permissions));
            //新建一個UsernamePasswordAuthenticationToken用來設置Authentication
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            return authenticationToken;
        }
    }
}

7.JWTAuthenticationFilter

用戶登錄時對用戶名密碼進行校驗的過濾器,在token校驗過濾器之后執(zhí)行。在該過濾器中會對用戶名密碼進行比對,校驗成功后返回一個token給客戶端,下次客戶端訪問時在請求頭帶上此token代表該用戶已經被認證。

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private ThreadLocal<Integer> rememberMe = new ThreadLocal<>();
    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {

        // 從輸入流中獲取到登錄的信息
        try {
            UserVO vo = new ObjectMapper().readValue(request.getInputStream(), UserVO.class);
            rememberMe.set(vo.getRememberMe() == null ? 0 : vo.getRememberMe());
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(vo.getUsername(), vo.getPassword(), new ArrayList<>())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 成功驗證后調用的方法
    // 如果驗證成功,就生成token并返回
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {

        LoginUser loginUser = (LoginUser) authResult.getPrincipal();
        System.out.println("loginUser:" + loginUser.toString());
        boolean isRemember = rememberMe.get() == 1;

        String role = "";
        String token = JwtTokenUtil.createToken(loginUser.getUsername(), role, isRemember);
        /* 返回創(chuàng)建成功的token
         但是這里創(chuàng)建的token只是單純的token
         按照jwt的規(guī)定,最后請求的時候應該是 `Bearer token`*/
        response.setHeader("token", JwtTokenUtil.TOKEN_PREFIX + token);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write("authentication failed, reason: " + failed.getMessage());
    }
}

8.service層開發(fā)

由于業(yè)務邏輯比較簡單,我們在service層中主要實現自定義授權的邏輯,用戶相關的Service不做實現。

自定義授權的實現如下,首先在數據庫的user表中有個string類型的permission字段,代表用戶所擁有的權限。在進行授權時檢查用戶權限屬性是否包含該權限,如果包含則表示當前用戶具有訪問權限。

/**
 * 自定義權限實現,ss取自SpringSecurity首字母
 */
@Service("ss")
public class PermissionService {
    public boolean hasPer(String permission) throws Exception {
        if (StringUtils.isBlank(permission)){
            return false;
        }
        LoginUser loginUser = SecurityUtil.getLoginUser();
        if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        return loginUser.getPermissions().contains(StringUtils.trim(permission));
    }
}

9.Controller層開發(fā)

我們用到的Controller主要有兩個,一個是用戶相關的,一個是進行測試的接口。

對于我們進行授權測試的接口,在使用自定義授權邏輯時(PermissionService),要配合@PreAuthorize注解實現(createJob方法),對應的JobController如下:

@RestController
@RequestMapping("/jobs")
public class JobController {

    @GetMapping("/list")
    public String listJobs(){
        System.out.println("接收到請求...");
        return "展示所有任務";
    }

    //通過PermissionService自定義授權實現
    @PostMapping("/create")
    @PreAuthorize("@ss.hasPer('job:add')")
    public String createJob(){
        return "創(chuàng)建一個新任務";
    }

    //通過SpringSecurity配合用戶角色(role字段)實現權限管理
    @DeleteMapping("/delete")
    public String deleteJob(){
        return "刪除一個任務";
    }
}

對于用戶相關的Controller,我們只需要寫一個注冊方法就行了,如下所示:

@RestController
@RequestMapping("/")
public class UserController {

    @Autowired
    UserMapper userMapper;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @PostMapping("/register")
    public String register(@RequestBody Map<String,String> registerUser){
        User user = new User();
        user.setUsername(registerUser.get("username"));
        //對密碼進行一下加密
        user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password")));
        user.setPermission(registerUser.get("permission"));
        user.setRole(registerUser.get("role"));
        userMapper.insertUser(user);
        return user.toString();
    }
}

為什么不需要注冊接口呢?是因為UsernamePasswordAuthenticationFilter已經幫我們實現了,默認是"/login"

 public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
 }

這里我們也可以對這個路徑進行修改,如下所示:

public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/auth/login");
    }

10.通過SecurityConfig類對SpringSecurity進行配置

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    @Qualifier("userDetailServiceImpl")
    UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 測試用資源,需要驗證了的用戶才能訪問
                .antMatchers("/jobs/create").authenticated()
                //只有角色為admin的用戶才能進行刪除
                .antMatchers(HttpMethod.DELETE,"/jobs/delete").hasRole("ADMIN")
                // 其他請求都放行了
                .anyRequest().permitAll()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                // 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //將驗證token的過濾器添加在驗證用戶名/密碼的過濾器之前
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource(){
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

到這里就全部完成了!讓我們測試一下是否生效。

11.postman測試

這是數據庫表中原有的角色和權限:

(1)測試注冊接口

可以看到是可以注冊成功的,但是我們這里使用原有user1和user2進行權限測試(偷個懶)

(2)測試登錄接口

由于我們之前將原有的登錄接口從"/login"改為了"/auth/login",這里需要注意一下。

在這里插入圖片描述

登錄成功后的ResponseBody是空的,響應頭中有token代表已經登錄成功了。我們需要從header中獲取該token,后續(xù)請求需要用到。這里展示了user2,user1也是一樣的。

在這里插入圖片描述

(3)測試創(chuàng)建任務接口

由于創(chuàng)建任務需要"job:add"權限,查看數據庫user2是有該權限的,user1沒有。

將剛才登錄獲得的token添加到參數中:

然后發(fā)現就可以創(chuàng)建成功了

(4)測試刪除任務接口

刪除任務需要用戶角色是admin,故user2是無法進行刪除的。

(5)我們按照剛才的流程對user1進行測試,來達到對比的效果。

登錄:

創(chuàng)建任務:

刪除任務:

對于查詢接口不需要進行認證和授權,也就是說不需要登錄就能訪問:

源碼地址:https://github.com/Rancho-7/SpringSecurity-JWT

到此這篇關于SpringSecurity角色權限控制(SpringBoot+SpringSecurity+JWT)的文章就介紹到這了,更多相關SpringSecurity角色權限內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家! 

相關文章

  • 淺談java中類名.class, class.forName(), getClass()的區(qū)別

    淺談java中類名.class, class.forName(), getClass()的區(qū)別

    下面小編就為大家?guī)硪黄獪\談java中類名.class, class.forName(), getClass()的區(qū)別。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • 使用mybatis-plus想要修改某字段為null問題

    使用mybatis-plus想要修改某字段為null問題

    這篇文章主要介紹了使用mybatis-plus想要修改某字段為null問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解

    java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解

    這篇文章主要介紹了java JUC原子類基本類型詳解的相關資料,文中示例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • java之assert關鍵字用法案例詳解

    java之assert關鍵字用法案例詳解

    這篇文章主要介紹了java之assert關鍵字用法案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-08-08
  • Spring Boot 基于注解的 Redis 緩存使用詳解

    Spring Boot 基于注解的 Redis 緩存使用詳解

    本篇文章主要介紹了Spring Boot 基于注解的 Redis 緩存使用詳解,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • Java實現遞歸計算n的階乘

    Java實現遞歸計算n的階乘

    這篇文章主要為大家詳細介紹了Java實現遞歸計算n的階乘,利用遞歸的思想實現階乘的計算,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • IntelliJ IDEA AI Assistant 攜帶OpenCode保姆級安裝教程

    IntelliJ IDEA AI Assistant 攜帶OpenCode保姆級

    本文介紹了JetBrains官方AI插件AIAssistant的安裝、使用及接入本地客戶端的過程,通過安裝、測試、接入本地客戶端等步驟,詳細描述了使用過程中的注意事項和操作方法,感興趣的朋友跟隨小編一起看看吧
    2026-04-04
  • Java創(chuàng)建可執(zhí)行JAR文件的多種方式

    Java創(chuàng)建可執(zhí)行JAR文件的多種方式

    本文主要介紹了Java創(chuàng)建可執(zhí)行JAR文件的多種方式,使用JDK的jar工具、IDE、Maven和Gradle來創(chuàng)建和配置可執(zhí)行JAR文件,具有一定的參考價值,感興趣的可以了解一下
    2024-07-07
  • Java設置Excel頁面的配置指南

    Java設置Excel頁面的配置指南

    本指南將深入探討如何在Java代碼中精細化控制Excel的頁面設置,幫助你告別打印噩夢,輕松打造出媲美手動操作的專業(yè)級報表,快跟隨小編一起學習一下吧
    2025-10-10
  • Java抽象類Abstract Class示例代碼詳解

    Java抽象類Abstract Class示例代碼詳解

    Java中的抽象類(Abstract Class)是面向對象編程中的重要概念,它通過abstract關鍵字聲明,用于定義一組相關類的公共行為和屬性,下面詳細介紹Java抽象類Abstract Class,感興趣的朋友一起看看吧
    2025-09-09

最新評論

昌宁县| 湾仔区| 溧水县| 剑河县| 湟源县| 措勤县| 平江县| 林芝县| 鹤庆县| 台东县| 郯城县| 海门市| 柳州市| 衡东县| 彰化市| 临漳县| 三原县| 开江县| 汉源县| 哈巴河县| 大足县| 镇宁| 通辽市| 泸溪县| 通河县| 方正县| 榆中县| 阿拉善盟| 德江县| 名山县| 泽普县| 申扎县| 隆化县| 漳平市| 水城县| 双峰县| 桐梓县| 民权县| 灯塔市| 临武县| 云林县|