vue3整合SpringSecurity加JWT實現(xiàn)權(quán)限校驗
我本來是想著登錄認(rèn)證和權(quán)限校驗放在一篇文章里的,但是上次寫登錄認(rèn)證就寫了非常多了,實在是有些寫不動了,所以才分為了兩篇文章。
本文適合有一定基礎(chǔ)的人來看,如果你對springsecurity安全框架還不是很了解,建議你先去看一下我之前寫過的spring security框架的快速入門:
springboot3整合SpringSecurity實現(xiàn)登錄校驗與權(quán)限認(rèn)證(萬字超詳細(xì)講解)
技術(shù)棧版本:vue3.3.11、springboot3.1.5、spring security6.x
之前的登錄認(rèn)證文章:
前后端分離,使用vue3整合SpringSecurity加JWT實現(xiàn)登錄認(rèn)證
在上次的文章中,只寫到登錄成功和退出之后就不寫了,這次會加上權(quán)限校驗。
首先,在原來數(shù)據(jù)庫的基礎(chǔ)上再新建:角色表、權(quán)限表、用戶角色表、角色權(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、用戶角色表
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ù)庫中共有5張表,分別創(chuàng)建相應(yīng)的server、mapper和controller層。
接下來,再原來的登錄認(rèn)證的代碼的基礎(chǔ)上就可以來實現(xiàn)我們的權(quán)限校驗了;
權(quán)限校驗這方面主要體現(xiàn)在后端代碼上,所以前端我只是進行一些簡單的演示即可;
1、在我們的MyTUserDetail類中定義角色和權(quán)限的屬性集合,并添加到UserDetails類的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 實現(xiàn)如何存儲 GrantedAuthority 對象的列表。這些對象代表已經(jīng)授予委托人(principal)的權(quán)限。GrantedAuthority 對象由 AuthenticationManager 插入到 Authentication 對象中,隨后由 AccessDecisionManager 實例在做出授權(quán)決定時讀取。
GrantedAuthority 接口只有一個方法。
String getAuthority();
這個方法被 AuthorizationManager 實例用來獲取 GrantedAuthority 的一個精確的 String 表示。通過返回一個 String 表示,一個 GrantedAuthority 可以被大多數(shù) AuthorizationManager 實現(xiàn)輕松 "讀取"。如果 GrantedAuthority 不能被精確地表示為一個 String,那么該 GrantedAuthority 被認(rèn)為是 "復(fù)雜的",getAuthority() 必須返回 null。
一個復(fù)雜的 GrantedAuthority 的例子是一個實現(xiàn),它存儲了一個適用于不同客戶賬號的操作和權(quán)限閾值的列表。將這種復(fù)雜的 GrantedAuthority 表示為一個 String 將是相當(dāng)困難的。因此,getAuthority() 方法應(yīng)該返回 null。這向任何 AuthorizationManager 表明,它需要支持特定的 GrantedAuthority 實現(xiàn)來理解其內(nèi)容。
Spring Security 包括一個具體的 GrantedAuthority 實現(xiàn)。SimpleGrantedAuthority。這個實現(xiàn)允許任何用戶指定的字符串被轉(zhuǎn)換為 GrantedAuthority。安全架構(gòu)中包含的所有 AuthenticationProvider 實例都使用 SimpleGrantedAuthority 來填充 Authentication 對象。
默認(rèn)情況下,基于角色的授權(quán)規(guī)則包括 ROLE_ 作為前綴。這意味著,如果有一個授權(quán)規(guī)則要求 security context 的角色是 "USER",Spring Security 將默認(rèn)尋找返回 "ROLE_USER" 的 GrantedAuthority#getAuthority。
你可以用 GrantedAuthorityDefaults 來定制這個。GrantedAuthorityDefaults 的存在是為了允許自定義基于角色的授權(quán)規(guī)則所使用的前綴。
你可以通過暴露一個 GrantedAuthorityDefaults Bean 來配置授權(quán)規(guī)則以使用不同的前綴,像這樣:
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("ZML_");
}我們需要特別注意的一點是,在spring security中。我們的角色和權(quán)限是存儲在一起的,沒有分開存儲 如:

參考來源:授權(quán)架構(gòu) :: Spring Security Reference
2、在MyUserDetailServerImpl類的loadUserByUsername方法中查出登錄用戶的權(quán)限集合:
代碼如下:
@Service
@Slf4j
public class MyUserDetailServerImpl implements MyUserDetailServer {
@Autowired
UsersMapper userService;
/**
* 返回一個賬號所擁有的權(quán)限碼集合
*/
// 角色權(quán)限表
@Autowired
IRolePermissionsService rolePermissionsService;
// 用戶角色表
@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("用戶名不存在");
}
log.info("UserDetailServer中的user:=========>"+users);
MyUserDetail myTUserDetail=new MyUserDetail();
myTUserDetail.setUsers(users);
// 查詢用戶權(quán)限
// 根據(jù)用戶id從用戶角色表中獲取角色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)){
// 用戶沒有分配角色
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)限名稱
rolePermissions.forEach(permissionsId->{
Permissions permissions = permissionsService.getById(permissionsId.getPermissionId());
listPermission.add(permissions.getName());
});
});
myTUserDetail.setPermissions( listPermission);
// 查詢角色角色
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;
}
}我所實現(xiàn)的是標(biāo)準(zhǔn)的RBAC(基于用戶、角色、權(quán)限的訪問控制模型)。所以,在得到用戶id的情況下、先根據(jù)用戶角色表查出角色id(如果角色id的集合為空,說明用戶沒有分配任何角色,直接返回用戶信息)、在根據(jù)角色權(quán)限表查詢權(quán)限id,在根據(jù)權(quán)限表查出具體權(quán)限名稱。
上面使用了Mybatis-plus的條件構(gòu)造器和stream流的形式進行查詢。
3、在JwtAuthenticationTokenFilter攔截器中,在查詢到用戶信息時,將用戶的標(biāo)識和用戶擁有的權(quán)限一起放到SecurityContextHolder中,這樣后面的過濾器在獲取到用戶信息的同時也能獲取到用戶所擁有的權(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 {
//獲取請求頭中的token
String token = request.getHeader("token");
System.out.println("前端的token信息=======>"+token);
//如果token為空直接放行,由于用戶信息沒有存放在SecurityContextHolder.getContext()中所以后面的過濾器依舊認(rèn)證失敗符合要求
if(!StringUtils.hasText(token)){
filterChain.doFilter(request,response);
return;
}
// 解析Jwt中的用戶id
Integer userId = jwtUtil.getUsernameFromToken(token);
//從redis中獲取用戶信息
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過濾器中MyUserDetail的值============>"+myTUserDetail.toString());
//將用戶信息存放在SecurityContextHolder.getContext(),后面的過濾器就可以獲得用戶信息了。這表明當(dāng)前這個用戶是登錄過的,后續(xù)的攔截器就不用再攔截了
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myTUserDetail,null,myTUserDetail.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
filterChain.doFilter(request,response);
}
}在這里解釋一下UsernamePasswordAuthenticationToken類:
UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用戶名和密碼的身份驗證令牌的類。它主要有以下兩個構(gòu)造方法:
UsernamePasswordAuthenticationToken(Object principal, Object credentials)
- principal參數(shù)表示認(rèn)證主體,通常是用戶名或用戶對象。在身份驗證過程中,這通常是用來標(biāo)識用戶的信息,可以是用戶名、郵箱等。
- credentials參數(shù)表示憑據(jù),通常是用戶的密碼或其他憑證信息。在身份驗證過程中,這用于驗證用戶的身份。
UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
- 除了上述兩個參數(shù)外,這個構(gòu)造方法還接受一個授權(quán)權(quán)限集合(authorities參數(shù))。這個集合表示用戶所擁有的權(quán)限,通常是一個包含用戶權(quán)限信息的集合。
- GrantedAuthority接口代表了用戶的權(quán)限信息,可以通過該接口的實現(xiàn)類來表示用戶具體的權(quán)限。
這兩個構(gòu)造方法的作用是創(chuàng)建一個包含用戶身份信息、憑據(jù)信息和權(quán)限信息的身份驗證令牌,以便在Spring Security中進行身份驗證和授權(quán)操作。通過這些構(gòu)造方法,可以將用戶的相關(guān)信息封裝成一個完整的身份驗證對象,方便在安全框架中進行處理和驗證。
總之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用戶名密碼身份驗證信息的重要類,通過不同的構(gòu)造方法可以滿足不同場景下的需求
所以我們通過myTUserDetail.getAuthorities()方法完全可以將用戶擁有的權(quán)限方法Security容器中,并供后續(xù)的攔截器獲取用戶信息和權(quán)限;
4、運行測試:
接下來我編寫一個基于方法的權(quán)限校驗,看我們編寫的代碼是否生效;
(基于方法的權(quán)限認(rèn)證要在SecurityConfig類上加上@EnableMethodSecurity注解,表示開啟了方法權(quán)限的使用;)
新建一個TestController,并在這個類中定義一個方法,用來測試:
@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頁面中新增一個按鈕,并綁定指定的方法用來測試;
代碼如圖:
const testHello = async() => {
let data:any= await api.get("/test/hello")
if(data.code===200){
ElMessage('有權(quán)限')
}
else{
ElMessage.error('沒有權(quán)限')
}
}現(xiàn)在,我們來測試看看這個方法能不能被調(diào)用到:

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

點擊前端按鈕:

可以看到確實不能訪問到了,這說明我們的代碼是正確的;
我們權(quán)限校驗的邏輯是:直接在登錄時查詢用戶的權(quán)限,并放在我們自定義的實現(xiàn)了UserDetail的接口類中(MyUserDetail),用來表示登錄用戶的全部信息;
至此:我們前后端分離,使用vue3整合SpringSecurity實現(xiàn)登錄認(rèn)證和權(quán)限校驗就已經(jīng)全部的講解完畢了,我還是會將前后端的源碼放在碼云上,有需要的童靴可以自行的下載:
碼云地址:
Vue-Security: 前后端分離的Security
到此這篇關(guān)于vue3整合SpringSecurity加JWT實現(xiàn)權(quán)限校驗的文章就介紹到這了,更多相關(guān)vue3 SpringSecurity 權(quán)限校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中進入詳情頁記住滾動位置的方法(keep-alive)
今天小編就為大家分享一篇vue中進入詳情頁記住滾動位置的方法(keep-alive),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue實現(xiàn)的微信機器人聊天功能案例【附源碼下載】
這篇文章主要介紹了vue實現(xiàn)的微信機器人聊天功能,結(jié)合實例形式分析了基于vue.js的微信機器人聊天相關(guān)界面布局、ajax交互等操作技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2019-02-02
lottie實現(xiàn)vue自定義loading指令及常用指令封裝詳解
這篇文章主要為大家介紹了lottie實現(xiàn)vue自定義loading指令及常用指令封裝,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
Vue Element-UI中el-table實現(xiàn)單選的示例代碼
在element-ui中是為我們準(zhǔn)備好了可直接使用的單選與多選屬性的,本文主要介紹了Vue Element-UI中el-table實現(xiàn)單選的示例代碼,具有一定的參考價值,感興趣的可以了解一下2023-12-12
vue3 + vite + ts 中使用less文件全局變量的操作方法
這篇文章主要介紹了vue3 + vite + ts 中使用less文件全局變量的操作方法,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-03-03
vue3在單個組件中實現(xiàn)類似mixin的事件調(diào)用
這篇文章主要為大家詳細(xì)介紹了vue3如何在單個組件中實現(xiàn)類似mixin的事件調(diào)用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01

