欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3整合SpringSecurity加JWT實(shí)現(xiàn)權(quán)限校驗(yàn)

 更新時(shí)間:2025年04月13日 11:25:58   作者:張喬24  
本文主要介紹了vue3整合SpringSecurity加JWT實(shí)現(xiàn)權(quán)限校驗(yàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

我本來(lái)是想著登錄認(rèn)證和權(quán)限校驗(yàn)放在一篇文章里的,但是上次寫(xiě)登錄認(rèn)證就寫(xiě)了非常多了,實(shí)在是有些寫(xiě)不動(dòng)了,所以才分為了兩篇文章。

本文適合有一定基礎(chǔ)的人來(lái)看,如果你對(duì)springsecurity安全框架還不是很了解,建議你先去看一下我之前寫(xiě)過(guò)的spring security框架的快速入門(mén):

springboot3整合SpringSecurity實(shí)現(xiàn)登錄校驗(yàn)與權(quán)限認(rèn)證(萬(wàn)字超詳細(xì)講解)

技術(shù)棧版本:vue3.3.11、springboot3.1.5、spring security6.x

之前的登錄認(rèn)證文章:
前后端分離,使用vue3整合SpringSecurity加JWT實(shí)現(xiàn)登錄認(rèn)證

在上次的文章中,只寫(xiě)到登錄成功和退出之后就不寫(xiě)了,這次會(huì)加上權(quán)限校驗(yàn)。

首先,在原來(lái)數(shù)據(jù)庫(kù)的基礎(chǔ)上再新建:角色表、權(quán)限表、用戶(hù)角色表、角色權(quán)限表四張表:

2、角色表

CREATE TABLE roles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3、權(quán)限表

CREATE TABLE permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE,
  description VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

4、用戶(hù)角色表

CREATE TABLE user_roles (
  id INT AUTO_INCREMENT PRIMARY KEY,
  user_id INT NOT NULL,
  role_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id),
  FOREIGN KEY (role_id) REFERENCES roles(id)
);

5、角色權(quán)限表

CREATE TABLE role_permissions (
  id INT AUTO_INCREMENT PRIMARY KEY,
  role_id INT NOT NULL,
  permission_id INT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (role_id) REFERENCES roles(id),
  FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

現(xiàn)在,我們的數(shù)據(jù)庫(kù)中共有5張表,分別創(chuàng)建相應(yīng)的server、mapper和controller層。

接下來(lái),再原來(lái)的登錄認(rèn)證的代碼的基礎(chǔ)上就可以來(lái)實(shí)現(xiàn)我們的權(quán)限校驗(yàn)了;

權(quán)限校驗(yàn)這方面主要體現(xiàn)在后端代碼上,所以前端我只是進(jìn)行一些簡(jiǎn)單的演示即可;

1、在我們的MyTUserDetail類(lèi)中定義角色和權(quán)限的屬性集合,并添加到UserDetails類(lèi)的getAuthorities方法中(角色和權(quán)限我都使用Set定義,這樣能夠去重)

代碼如下:

@Data
public class MyUserDetail implements Serializable, UserDetails {

    private static final long serialVersionUID = 1L;

    private Users Users;

//    角色
    private Set<String> roles;

//    權(quán)限
    private  Set<String> permissions;

    @JsonIgnore  //json忽略
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        List<GrantedAuthority> list =  new ArrayList<>();

//        如果角色不用空,則將角色添加到list中
        if (!ObjectUtils.isEmpty(roles)){
            roles.forEach(role->list.add(new SimpleGrantedAuthority("ZML_"+role)));
        }

//                如果權(quán)限不用空,則將權(quán)限添加到list中
        if (!ObjectUtils.isEmpty(permissions)){
            permissions.forEach(permission->list.add(new SimpleGrantedAuthority(permission)));
        }
        return list;
        
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return this.getUsers().getPassword();
    }
    @JsonIgnore
    @Override
    public String getUsername() {
        return this.getUsers().getUsername();
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return this.getUsers().getStatus()==0;
    }
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return this.getUsers().getStatus()==0;
    }
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return this.getUsers().getStatus()==0;
    }
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return this.getUsers().getStatus()==0;
    }
}

Authentication 討論了所有 Authentication 實(shí)現(xiàn)如何存儲(chǔ) GrantedAuthority 對(duì)象的列表。這些對(duì)象代表已經(jīng)授予委托人(principal)的權(quán)限。GrantedAuthority 對(duì)象由 AuthenticationManager 插入到 Authentication 對(duì)象中,隨后由 AccessDecisionManager 實(shí)例在做出授權(quán)決定時(shí)讀取。

GrantedAuthority 接口只有一個(gè)方法。

String getAuthority();

這個(gè)方法被 AuthorizationManager 實(shí)例用來(lái)獲取 GrantedAuthority 的一個(gè)精確的 String 表示。通過(guò)返回一個(gè) String 表示,一個(gè) GrantedAuthority 可以被大多數(shù) AuthorizationManager 實(shí)現(xiàn)輕松 "讀取"。如果 GrantedAuthority 不能被精確地表示為一個(gè) String,那么該 GrantedAuthority 被認(rèn)為是 "復(fù)雜的",getAuthority() 必須返回 null。

一個(gè)復(fù)雜的 GrantedAuthority 的例子是一個(gè)實(shí)現(xiàn),它存儲(chǔ)了一個(gè)適用于不同客戶(hù)賬號(hào)的操作和權(quán)限閾值的列表。將這種復(fù)雜的 GrantedAuthority 表示為一個(gè) String 將是相當(dāng)困難的。因此,getAuthority() 方法應(yīng)該返回 null。這向任何 AuthorizationManager 表明,它需要支持特定的 GrantedAuthority 實(shí)現(xiàn)來(lái)理解其內(nèi)容。

Spring Security 包括一個(gè)具體的 GrantedAuthority 實(shí)現(xiàn)。SimpleGrantedAuthority。這個(gè)實(shí)現(xiàn)允許任何用戶(hù)指定的字符串被轉(zhuǎn)換為 GrantedAuthority。安全架構(gòu)中包含的所有 AuthenticationProvider 實(shí)例都使用 SimpleGrantedAuthority 來(lái)填充 Authentication 對(duì)象。

默認(rèn)情況下,基于角色的授權(quán)規(guī)則包括 ROLE_ 作為前綴。這意味著,如果有一個(gè)授權(quán)規(guī)則要求 security context 的角色是 "USER",Spring Security 將默認(rèn)尋找返回 "ROLE_USER" 的 GrantedAuthority#getAuthority。

你可以用 GrantedAuthorityDefaults 來(lái)定制這個(gè)。GrantedAuthorityDefaults 的存在是為了允許自定義基于角色的授權(quán)規(guī)則所使用的前綴。

你可以通過(guò)暴露一個(gè) GrantedAuthorityDefaults Bean 來(lái)配置授權(quán)規(guī)則以使用不同的前綴,像這樣:

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("ZML_");
}

我們需要特別注意的一點(diǎn)是,在spring security中。我們的角色和權(quán)限是存儲(chǔ)在一起的,沒(méi)有分開(kāi)存儲(chǔ) 如:

 參考來(lái)源:授權(quán)架構(gòu) :: Spring Security Reference

2、在MyUserDetailServerImpl類(lèi)的loadUserByUsername方法中查出登錄用戶(hù)的權(quán)限集合:

代碼如下:

@Service
@Slf4j
public class MyUserDetailServerImpl implements MyUserDetailServer {
    @Autowired
    UsersMapper userService;

    /**
     * 返回一個(gè)賬號(hào)所擁有的權(quán)限碼集合
     */

//    角色權(quán)限表
    @Autowired
    IRolePermissionsService rolePermissionsService;
    //    用戶(hù)角色表
    @Autowired
    IUserRolesService userRolesService;
    //權(quán)限表
    @Autowired
    IPermissionsService permissionsService;

    //    角色表
    @Autowired
    IRolesService rolesService;



    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Users users = userService.selectOne(new LambdaQueryWrapper<Users>().
                eq(username != null, Users::getUsername, username));
        if (users == null) {
            throw new UsernameNotFoundException("用戶(hù)名不存在");
        }
log.info("UserDetailServer中的user:=========>"+users);
        MyUserDetail myTUserDetail=new MyUserDetail();
myTUserDetail.setUsers(users);


        // 查詢(xún)用戶(hù)權(quán)限


//        根據(jù)用戶(hù)id從用戶(hù)角色表中獲取角色id
        List<UserRoles> roleIds = userRolesService.list(new LambdaQueryWrapper<UserRoles>()
                .eq(UserRoles::getUserId,users.getId()));
        List<Integer> rolesList = roleIds.stream().map(UserRoles::getRoleId).toList();

        if (!(roleIds.size() >0)){
//            用戶(hù)沒(méi)有分配角色
            return myTUserDetail;
        }
        
        Set<String> listPermission = new HashSet<>();

        rolesList.forEach(roleId ->{
            // 根據(jù)角色id從角色權(quán)限表中獲取權(quán)限id
            List<RolePermissions> rolePermissions = rolePermissionsService.list(new LambdaQueryWrapper<RolePermissions>().
                    eq(RolePermissions::getRoleId, roleId));
            // 根據(jù)權(quán)限id從權(quán)限表中獲取權(quán)限名稱(chēng)
            rolePermissions.forEach(permissionsId->{
                Permissions permissions = permissionsService.getById(permissionsId.getPermissionId());
                listPermission.add(permissions.getName());
            });
        });

myTUserDetail.setPermissions( listPermission);

        // 查詢(xún)角色角色

        Set<String> listRole = new HashSet<>();

     
        
        roleIds.forEach(roleId ->{
            Roles byId = rolesService.getById(roleId.getRoleId());
            listRole.add(byId.getName());
        });
        myTUserDetail.setRoles(listRole);
log.info("UserDetailServer中的查完權(quán)限的myTUserDetail:=========>"+myTUserDetail);
        return myTUserDetail;
    }

}

我所實(shí)現(xiàn)的是標(biāo)準(zhǔn)的RBAC(基于用戶(hù)、角色、權(quán)限的訪問(wèn)控制模型)。所以,在得到用戶(hù)id的情況下、先根據(jù)用戶(hù)角色表查出角色id(如果角色id的集合為空,說(shuō)明用戶(hù)沒(méi)有分配任何角色,直接返回用戶(hù)信息)、在根據(jù)角色權(quán)限表查詢(xún)權(quán)限id,在根據(jù)權(quán)限表查出具體權(quán)限名稱(chēng)。

上面使用了Mybatis-plus的條件構(gòu)造器和stream流的形式進(jìn)行查詢(xún)。

3、在JwtAuthenticationTokenFilter攔截器中,在查詢(xún)到用戶(hù)信息時(shí),將用戶(hù)的標(biāo)識(shí)和用戶(hù)擁有的權(quán)限一起放到SecurityContextHolder中,這樣后面的過(guò)濾器在獲取到用戶(hù)信息的同時(shí)也能獲取到用戶(hù)所擁有的權(quán)限;

代碼如下:

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //獲取請(qǐng)求頭中的token
        String token = request.getHeader("token");
        System.out.println("前端的token信息=======>"+token);
        //如果token為空直接放行,由于用戶(hù)信息沒(méi)有存放在SecurityContextHolder.getContext()中所以后面的過(guò)濾器依舊認(rèn)證失敗符合要求
        if(!StringUtils.hasText(token)){
            filterChain.doFilter(request,response);
            return;
        }

//        解析Jwt中的用戶(hù)id
        Integer userId = jwtUtil.getUsernameFromToken(token);
        //從redis中獲取用戶(hù)信息
        String redisUser = redisTemplate.opsForValue().get(String.valueOf(userId));
        if(!StringUtils.hasText(redisUser)){
            filterChain.doFilter(request,response);
            return;
        }

        MyUserDetail myTUserDetail= JSON.parseObject(redisUser, MyUserDetail.class);
log.info("Jwt過(guò)濾器中MyUserDetail的值============>"+myTUserDetail.toString());

        //將用戶(hù)信息存放在SecurityContextHolder.getContext(),后面的過(guò)濾器就可以獲得用戶(hù)信息了。這表明當(dāng)前這個(gè)用戶(hù)是登錄過(guò)的,后續(xù)的攔截器就不用再攔截了
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,myTUserDetail.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(request,response);
    }
}

在這里解釋一下UsernamePasswordAuthenticationToken類(lèi):

UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用戶(hù)名和密碼的身份驗(yàn)證令牌的類(lèi)。它主要有以下兩個(gè)構(gòu)造方法:

  • UsernamePasswordAuthenticationToken(Object principal, Object credentials)

    • principal參數(shù)表示認(rèn)證主體,通常是用戶(hù)名或用戶(hù)對(duì)象。在身份驗(yàn)證過(guò)程中,這通常是用來(lái)標(biāo)識(shí)用戶(hù)的信息,可以是用戶(hù)名、郵箱等。
    • credentials參數(shù)表示憑據(jù),通常是用戶(hù)的密碼或其他憑證信息。在身份驗(yàn)證過(guò)程中,這用于驗(yàn)證用戶(hù)的身份。
  • UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

    • 除了上述兩個(gè)參數(shù)外,這個(gè)構(gòu)造方法還接受一個(gè)授權(quán)權(quán)限集合(authorities參數(shù))。這個(gè)集合表示用戶(hù)所擁有的權(quán)限,通常是一個(gè)包含用戶(hù)權(quán)限信息的集合。
    • GrantedAuthority接口代表了用戶(hù)的權(quán)限信息,可以通過(guò)該接口的實(shí)現(xiàn)類(lèi)來(lái)表示用戶(hù)具體的權(quán)限。

這兩個(gè)構(gòu)造方法的作用是創(chuàng)建一個(gè)包含用戶(hù)身份信息、憑據(jù)信息和權(quán)限信息的身份驗(yàn)證令牌,以便在Spring Security中進(jìn)行身份驗(yàn)證和授權(quán)操作。通過(guò)這些構(gòu)造方法,可以將用戶(hù)的相關(guān)信息封裝成一個(gè)完整的身份驗(yàn)證對(duì)象,方便在安全框架中進(jìn)行處理和驗(yàn)證。

總之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用戶(hù)名密碼身份驗(yàn)證信息的重要類(lèi),通過(guò)不同的構(gòu)造方法可以滿(mǎn)足不同場(chǎng)景下的需求

所以我們通過(guò)myTUserDetail.getAuthorities()方法完全可以將用戶(hù)擁有的權(quán)限方法Security容器中,并供后續(xù)的攔截器獲取用戶(hù)信息和權(quán)限;

4、運(yùn)行測(cè)試:
接下來(lái)我編寫(xiě)一個(gè)基于方法的權(quán)限校驗(yàn),看我們編寫(xiě)的代碼是否生效;

(基于方法的權(quán)限認(rèn)證要在SecurityConfig類(lèi)上加上@EnableMethodSecurity注解,表示開(kāi)啟了方法權(quán)限的使用;)

新建一個(gè)TestController,并在這個(gè)類(lèi)中定義一個(gè)方法,用來(lái)測(cè)試:

@RestController
@RequestMapping("/test")
public class TestController {
    @PreAuthorize("hasAnyAuthority('所有權(quán)限')")
 @GetMapping("/hello")
    public Result hello(){
        System.out.println("test接口中的hello方法調(diào)用========================");
        return Result.successData("hello");
    }

}

在前端的Layout.vue頁(yè)面中新增一個(gè)按鈕,并綁定指定的方法用來(lái)測(cè)試;

代碼如圖:

const testHello = async() => {
  let data:any= await api.get("/test/hello")
if(data.code===200){
  ElMessage('有權(quán)限')
}
else{
  ElMessage.error('沒(méi)有權(quán)限')
}
}

現(xiàn)在,我們來(lái)測(cè)試看看這個(gè)方法能不能被調(diào)用到:

可以看到這個(gè)方法被正確的訪問(wèn)到了,這是必須的因?yàn)檫@個(gè)”張喬“用戶(hù)有這個(gè)權(quán)限,那么我們改一下所需的權(quán)限看還能不能訪問(wèn)到;

點(diǎn)擊前端按鈕:

可以看到確實(shí)不能訪問(wèn)到了,這說(shuō)明我們的代碼是正確的;

我們權(quán)限校驗(yàn)的邏輯是:直接在登錄時(shí)查詢(xún)用戶(hù)的權(quán)限,并放在我們自定義的實(shí)現(xiàn)了UserDetail的接口類(lèi)中(MyUserDetail),用來(lái)表示登錄用戶(hù)的全部信息;

至此:我們前后端分離,使用vue3整合SpringSecurity實(shí)現(xiàn)登錄認(rèn)證和權(quán)限校驗(yàn)就已經(jīng)全部的講解完畢了,我還是會(huì)將前后端的源碼放在碼云上,有需要的童靴可以自行的下載:

碼云地址:
Vue-Security: 前后端分離的Security

到此這篇關(guān)于vue3整合SpringSecurity加JWT實(shí)現(xiàn)權(quán)限校驗(yàn)的文章就介紹到這了,更多相關(guān)vue3 SpringSecurity 權(quán)限校驗(yàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論