SaaS-HRM--第4章_ 權限管理與jwt鑑權

第4章 權限管理與jwt鑑權

學習目標:
理解權限管理的需求以及設計思路實現角色分配和權限分配
理解常見的認證機制
能夠使用JWT完成微服務Token簽發與驗證

1權限管理

1.1需求分析

完成權限(菜單,按鈕(權限點),API接口)的基本操作
在這裏插入圖片描述
權限與菜單,菜單與按鈕,菜單與API接口都是一對一關係。爲了方便操作,在SAAS-HRM系統的表設計中,採用基於共享主鍵的形式實現一對一關係維護,並且數據庫約束,一切的關係維護需要程序員在代碼中實現。

1.2後端實現

1.2.1實體類
在系統微服務中創建權限,菜單,按鈕(權限點),API對象的實體類
(1)權限實體類Permission


@Entity
@Table(name = "pe_permission") @Getter
@Setter @NoArgsConstructor @DynamicInsert(true) @DynamicUpdate(true)
public class Permission implements Serializable {
private static final long serialVersionUID = -4990810027542971546L;
/**
* 主 鍵
*/ @Id
private String id;
/**
* 權限名稱
*/
private String name;
/**
* 權限類型 1爲菜單 2爲功能 3爲API
*/
private Integer type;


/**
* 權限編碼
*/
private String code;


/**
* 權限描述
*/
private String description;


private String pid;

//可見狀態
private String enVisible;


public Permission(String name, Integer type, String code, String description) { this.name = name;
this.type = type; this.code = code;
this.description = description;
}



}


(2)權限菜單實體類PermissionMenu

@Entity
@Table(name = "pe_permission_menu") @Getter
@Setter
public class PermissionMenu implements Serializable {
private static final long serialVersionUID = -1002411490113957485L;
@Id
private String id;
private String menuIcon; //展示圖標private String menuOrder; //排序號
}

(3)權限菜單(權限點)實體類PermissionPoint


@Entity
@Table(name = "pe_permission_point") @Getter
@Setter
public class PermissionPoint implements Serializable {
private static final long serialVersionUID = -1002411490113957485L; @Id
private String id; private String pointClass; private String pointIcon;
private String pointStatus;
}

(4)權限API實體類PermissionApi


@Entity
@Table(name = "pe_permission_api") @Getter
@Setter
public class PermissionApi implements Serializable {
private static final long serialVersionUID = -1803315043290784820L;

@Id
private String id; private String apiUrl; private String apiMethod;
private String apiLevel;//權限等級,1爲通用接口權限,2爲需校驗接口權限
}

1.2.2持久層
(1)權限持久化類


/**
* 權限數據訪問接口
*/
public interface PermissionDao extends JpaRepository<Permission, String>, JpaSpecificationExecutor<Permission> {
List<Permission> findByTypeAndPid(int type,String pid);
}

(2)權限菜單持久化類


public interface PermissionMenuDao extends JpaRepository<PermissionMenu, String>, JpaSpecificationExecutor<PermissionMenu> {
}

(3)權限按鈕(點)持久化類


public interface PermissionPointDao extends JpaRepository<PermissionPoint, String>, JpaSpecificationExecutor<PermissionPoint> {
}

(4)權限API持久化類


public interface PermissionApiDao extends JpaRepository<PermissionApi, String>, JpaSpecificationExecutor<PermissionApi> {
}

1.2.3業務邏輯

package com.ihrm.system.service;


import com.ihrm.common.entity.ResultCode;
import com.ihrm.common.exception.CommonException; import com.ihrm.common.utils.BeanMapUtils; import com.ihrm.common.utils.IdWorker;
import com.ihrm.common.utils.PermissionConstants; import com.ihrm.domain.system.*;
import com.ihrm.system.dao.*;
import com.ihrm.system.dao.PermissionDao;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils;
import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root;
import java.util.ArrayList;


import java.util.List; import java.util.Map;

@Service @Transactional
public class PermissionService {


@Autowired
private PermissionDao permissionDao;


@Autowired
private PermissionMenuDao permissionMenuDao;


@Autowired
private PermissionPointDao permissionPointDao;


@Autowired
private PermissionApiDao permissionApiDao;


@Autowired
private IdWorker idWorker;


/**
* 1.保存權限
*/
public void save(Map<String,Object> map) throws Exception {
//設置主鍵的值
String id = idWorker.nextId()+"";
//1.通過map構造permission對象
Permission perm = BeanMapUtils.mapToBean(map,Permission.class);
perm.setId(id);
//2.根據類型構造不同的資源對象(菜單,按鈕,api) int type = perm.getType();
switch (type) {
case PermissionConstants.PERMISSION_MENU:
PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class); menu.setId(id);
permissionMenuDao.save(menu); break;
case PermissionConstants.PERMISSION_POINT: PermissionPoint point =
BeanMapUtils.mapToBean(map,PermissionPoint.class);
point.setId(id); permissionPointDao.save(point); break;
case PermissionConstants.PERMISSION_API:
PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class); api.setId(id);
permissionApiDao.save(api); break;
default:
throw new CommonException(ResultCode.FAIL);
}
//3.保存permissionDao.save(perm);
}

/**
* 2.更新權限
*/
public void update(Map<String,Object> map) throws Exception { Permission perm = BeanMapUtils.mapToBean(map,Permission.class);
//1.通過傳遞的權限id查詢權限
Permission permission = permissionDao.findById(perm.getId()).get(); permission.setName(perm.getName()); permission.setCode(perm.getCode()); permission.setDescription(perm.getDescription());
permission.setEnVisible(perm.getEnVisible());
//2.根據類型構造不同的資源
int type = perm.getType(); switch (type) {
case PermissionConstants.PERMISSION_MENU:
PermissionMenu menu = BeanMapUtils.mapToBean(map,PermissionMenu.class); menu.setId(perm.getId());
permissionMenuDao.save(menu); break;
case PermissionConstants.PERMISSION_POINT: PermissionPoint point =
BeanMapUtils.mapToBean(map,PermissionPoint.class);
point.setId(perm.getId()); permissionPointDao.save(point); break;
case PermissionConstants.PERMISSION_API:
PermissionApi api = BeanMapUtils.mapToBean(map,PermissionApi.class); api.setId(perm.getId());
permissionApiDao.save(api); break;
default:
throw new CommonException(ResultCode.FAIL);
}
//3.保存permissionDao.save(permission);
}

/**
*3.根據id查詢
*//1.查詢權限
*//2.根據權限的類型查詢資源
*//3.構造map集合
*/
public Map<String, Object> findById(String id) throws Exception { Permission perm = permissionDao.findById(id).get();
int type = perm.getType();


Object object = null;

if(type == PermissionConstants.PERMISSION_MENU) { object = permissionMenuDao.findById(id).get();
}else if (type == PermissionConstants.PERMISSION_POINT) { object = permissionPointDao.findById(id).get();
}else if (type == PermissionConstants.PERMISSION_API) { object = permissionApiDao.findById(id).get();
}else {
throw new CommonException(ResultCode.FAIL);
}


Map<String, Object> map = BeanMapUtils.beanToMap(object);


map.put("name",perm.getName());
map.put("type",perm.getType());
map.put("code",perm.getCode()); map.put("description",perm.getDescription()); map.put("pid",perm.getPid()); map.put("enVisible",perm.getEnVisible());


return map;
}

/**
*4.查詢全部
*type	: 查詢全部權限列表type:0:菜單 + 按鈕(權限點) 1:菜單2:按鈕(權限點)3:API接
口
*enVisible : 0:查詢所有saas平臺的最高權限,1:查詢企業的權限
*pid :父id
*/
public List<Permission> findAll(Map<String, Object> map) {
//1.需要查詢條件
Specification<Permission> spec = new Specification<Permission>() {
/**
*動態拼接查詢條件
*@return
*/
public Predicate toPredicate(Root<Permission> root, CriteriaQuery<?>
criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> list = new ArrayList<>();
//根據父id查詢if(!StringUtils.isEmpty(map.get("pid"))) {
list.add(criteriaBuilder.equal(root.get("pid").as(String.class), (String)map.get("pid")));
}
//根據enVisible查詢if(!StringUtils.isEmpty(map.get("enVisible"))) {


list.add(criteriaBuilder.equal(root.get("enVisible").as(String.class), (String)map.get("enVisible")));
}
// 根 據 類 型 type if(!StringUtils.isEmpty(map.get("type"))) {
String ty = (String) map.get("type"); CriteriaBuilder.In<Object> in =
criteriaBuilder.in(root.get("type"));
if("0".equals(ty)) {
in.value(1).value(2);
}else{
in.value(Integer.parseInt(ty));
}
list.add(in);
}
return criteriaBuilder.and(list.toArray(new Predicate[list.size()]));
}
};
return permissionDao.findAll(spec);
}

/**
*5.根據id刪除
*//1.刪除權限
*//2.刪除權限對應的資源
*
*/
public void deleteById(String id) throws Exception {
//1.通過傳遞的權限id查詢權限
Permission permission = permissionDao.findById(id).get();
permissionDao.delete(permission);
//2.根據類型構造不同的資源
int type = permission.getType(); switch (type) {
case PermissionConstants.PERMISSION_MENU: permissionMenuDao.deleteById(id); break;
case PermissionConstants.PERMISSION_POINT: permissionPointDao.deleteById(id); break;
case PermissionConstants.PERMISSION_API: permissionApiDao.deleteById(id); break;
default:
throw new CommonException(ResultCode.FAIL);
}
}
}



1.2.4控制器


package com.ihrm.system.controller;

import com.ihrm.common.entity.PageResult; import com.ihrm.common.entity.Result; import com.ihrm.common.entity.ResultCode;

import com.ihrm.domain.system.Permission; import com.ihrm.domain.system.User;
import com.ihrm.system.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.*;


import java.util.List; import java.util.Map;

//1.解決跨域@CrossOrigin
//2.聲明restContoller
@RestController
//3.設置父路徑@RequestMapping(value="/sys") public class PermissionController {
@Autowired
private PermissionService permissionService;
/**
* 保 存
*/
@RequestMapping(value = "/permission", method = RequestMethod.POST)
public Result save(@RequestBody Map<String,Object> map) throws Exception { permissionService.save(map);
return new Result(ResultCode.SUCCESS);
}


/**
* 修 改
*/
@RequestMapping(value = "/permission/{id}", method = RequestMethod.PUT) public Result update(@PathVariable(value = "id") String id, @RequestBody
Map<String,Object> map) throws Exception {
// 構 造 id map.put("id",id);
permissionService.update(map);
return new Result(ResultCode.SUCCESS);
}


/**
* 查詢列表
*/
@RequestMapping(value = "/permission", method = RequestMethod.GET) public Result findAll(@RequestParam Map map) {
List<Permission> list =	permissionService.findAll(map); return new Result(ResultCode.SUCCESS,list);
}


/**
* 根據ID查詢
*/
@RequestMapping(value = "/permission/{id}", method = RequestMethod.GET)
public Result findById(@PathVariable(value = "id") String id) throws Exception { Map map = permissionService.findById(id);
return new Result(ResultCode.SUCCESS,map);
}
/**
* 根據id刪除
*/
@RequestMapping(value = "/permission/{id}", method = RequestMethod.DELETE) public Result delete(@PathVariable(value = "id") String id) throws Exception {
permissionService.deleteById(id); return new Result(ResultCode.SUCCESS);
}
}

1.3前端實現

1.3.1引入權限管理模塊
將今日提供的資料module-permissions引入到工程

import permissions from '@/module-permissions/' // 權限管理
Vue.use(permissions, store)

1.3.2配置API
在/src/api/base/目錄下創建permissions.js


import {createAPI} from '@/utils/request'

const api = "/sys/permission"
export const list = data => createAPI(`${api}`, 'get', data) export const add = data => createAPI(`${api}`, 'post', data)
export const update = data => createAPI(`${api}/${data.id}`, 'put', data) export const remove = data => createAPI(`${api}/${data.id}`, 'delete', data) export const detail = data => createAPI(`${api}/${data.id}`, 'get', data) export const saveOrUpdate = data => {return data.id?update(data):add(data)}

1.3.3實現權限頁面


<template>
<div class="dashboard-container">
<div class="app-container">
<el-card shadow="never">
<el-button class="filter-item fr" size="small" style="margin-left: 10px;" @click="handleCreate(null,1);setPid(1,'0')" type="primary" icon="el-icon-edit">添加菜單
</el-button>

<el-table :data="dataList" fit style="width: 100%;" highlight-current-row>
<el-table-column fixed prop="name" label="菜單名稱" width="200px">
<template slot-scope="scope">
<i :class="scope.row.type==1?'ivu-icon fa fa-folder-open-o fa- fw':'ivu-icon	el-icon-view'"
:style="scope.row.type==1?'margin-left: 0px':'margin-left:
20px'"></i>
<span @click="show(scope.$index,scope.row.id)">
{{scope.row.name}}</span>
</template>
</el-table-column>
<el-table-column fixed prop="code" label="權限標識" width="200"></el-

table-column>


table-column>


<el-table-column fixed prop="description" label="描述" width="200"></el-

<el-table-column fixed="right" label="操作">
<template slot-scope="scope">
<el-button v-if="scope.row.type==1"

@click="handleCreate(null,2);setPid(2,scope.row.id)" type="text" size="small">添加權限點
</el-button>
<el-button @click="handlerApiList(scope.row.id)" type="text" size="small">查看api權限</el-button>
<el-button
@click="handleCreate(scope.row.id,scope.row.type);setPid(scope.row.type,scope.row.pid)" type="text" size="small">查看</el-button>
<el-button @click="handleDelete(scope.row.id)" type="text" size="small">刪除</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</div>

<el-dialog title="編輯權限" :visible.sync="dialogFormVisible" style="hight:100px;line-height:1px">
<el-form :model="formData" label-width="90px" style="margin-top:20px">
<el-form-item label="權限名稱">
<el-input v-model="formData.name" autocomplete="off" style="width:90%">

</el-input>





</el-input>



</el-form-item>
<el-form-item label="權限標識">
<el-input v-model="formData.code" autocomplete="off" style="width:90%">


</el-form-item>
<el-form-item label="權限描述">
<el-input v-model="formData.description" autocomplete="off"

style="width:90%"></el-input>
</el-form-item>
<div v-if="type==1">
<el-form-item label="菜單順序">
<el-input v-model="formData.menuOrder" autocomplete="off" style="width:90%"></el-input>
</el-form-item>

<el-form-item label="菜單icon">
<el-input v-model="formData.menuIcon" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
</div>
<div v-else-if="type==2">
<el-form-item label="按鈕樣式">
<el-input v-model="formData.pointClass" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
<el-form-item label="按鈕icon">
<el-input v-model="formData.pointIcon" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
<el-form-item label="按鈕狀態">
<el-input v-model="formData.pointStatus" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
</div>
<div v-else-if="type==3">
<el-form-item label="api請求地址">
<el-input v-model="formData.apiUrl" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
<el-form-item label="api請求方式">
<el-input v-model="formData.apiMethod" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
<el-form-item label="api類型">
<el-input v-model="formData.apiLevel" autocomplete="off" style="width:90%"></el-input>
</el-form-item>
</div>


</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="saveOrUpdate">確 定</el-button>
</div>
</el-dialog>

<el-dialog	title="API權限列表" :visible.sync="apiDialogVisible" style="hight:400px;line-height:1px">
<el-button class="filter-item fr" size="small" style="margin-left: 10px;" @click="handleCreate(null,1);setPid(3,pid)" type="primary"  icon="el-icon-edit">添加api權限</el-button>
<el-table :data="apiList" fit style="width: 100%;" max-height="250" >
<el-table-column fixed prop="name" label="菜單名稱" width="120px"></el-

table-column>


table-column>


table-column>


<el-table-column fixed prop="code" label="權限標識" width="200"></el-
<el-table-column fixed="right" label="操作" width="200">
<template slot-scope="scope">
<el-button @click="handleCreate(scope.row.id,scope.row.type);setPid(scope.row.type,scope.row.pid)" type="text" size="small">查看</el-button>
<el-button @click="handleDelete(scope.row.id);handlerApiList(pid)" type="text" size="small"> 刪 除
</el-button>
</template>
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>


<script>
import {saveOrUpdate,list,detail,remove} from "@/api/base/permissions" export default {
name: 'permissions-table-index', data() {
return {
MenuList: 'menuList', type:0,
pid:"", dialogFormVisible:false, apiDialogVisible:false, formData:{},
dataList:[],
apiList:[], pointEnable:{}
}
},
methods: { setPid(type,pid){
this.pid = pid; this.type = type
},
handleCreate(id) {
if(id && id !=undefined) { detail({id}).then(res => {
this.formData = res.data.data this.dialogFormVisible=true
})
}else{
this.formData = {} this.dialogFormVisible=true
}
},
saveOrUpdate() { this.formData.type = this.type this.formData.pid = this.pid
saveOrUpdate(this.formData).then(res => {
this.$message({message:res.data.message,type:res.data.success?"success":"error"}); if(res.data.success){
this.formData={}; this.dialogFormVisible=false;
}
if(this.type ==3){ this.handlerApiList(this.pid);
}else{
this.getList(); this.pointEnable = {}
}
})
},
handleDelete(id) { remove({id}).then(res=> {

this.$message({message:res.data.message,type:res.data.success?"success":"error"});
})
},
getList() { list({type:1,pid:0}).then(res=> {
this.dataList = res.data.data
})
},
show(index,id) {
if(!this.pointEnable[id] == null || this.pointEnable[id]==undefined){ list({type:2,pid:id}).then(res=> {
for(var i = 0 ; i <res.data.data.length;i++) { this.dataList.splice(index+1,0,res.data.data[i]);
}
this.pointEnable[id] = res.data.data.length; console.log(this.dataList)
})
}else{
this.dataList.splice(index+1,this.pointEnable[id]) this.pointEnable[id] = null;
}
},
handlerApiList(id) { this.pid = id;
list({type:3,pid:id}).then(res=> { this.apiList = res.data.data this.apiDialogVisible = true;
})
}
},
created () { this.getList();
}
}



2 分配角色

2.1需求分析

由於使用了RBAC模型對權限進行統一管理,所以每個SAAS-HRM平臺的用戶都應該具有角色的信息。進而通過角色完成對權限的識別。衆所周知,一個用戶可以具有很多的角色,一個角色可以被分配給不同的用戶。所以用戶和 角色之間是多對多關係。
在這裏插入圖片描述

2.2服務端代碼實現

(1)改造用戶實體類,添加角色的id集合屬性,表明一個用戶具有的多個角色id
用戶實體類中添加與角色的多對多關係並進行JPA的配置


@ManyToMany @JsonIgnore
@JoinTable(name="pe_user_role",joinColumns=
{@JoinColumn(name="user_id",referencedColumnName="id")}, inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}
)
private Set<Role> roles = new HashSet<Role>();//用戶與角色	多對多

在com.ihrm.system.domain.Role角色實體類中配置角色與用戶的多對多關係並進行JPA配置

@JsonIgnore @ManyToMany(mappedBy="roles")
private Set<User> users = new HashSet<User>(0);//角色與用戶	多對多

(2)添加分配角色的控制器方法實現

/**
* 分配角色
*/
@RequestMapping(value = "/user/assignRoles", method = RequestMethod.PUT) public Result assignRoles(@RequestBody Map<String,Object> map) {
//1.獲取被分配的用戶id
String userId = (String) map.get("id");
//2.獲取到角色的id列表
List<String> roleIds = (List<String>) map.get("roleIds");
//3.調用service完成角色分配userService.assignRoles(userId,roleIds); return new Result(ResultCode.SUCCESS);
}

(3)業務邏輯層添加分配角色的業務方法


/**
* 分配角色
*/
public void assignRoles(String userId,List<String> roleIds) {
//1.根據id查詢用戶
User user = userDao.findById(userId).get();
//2.設置用戶的角色集合
Set<Role> roles = new HashSet<>(); for (String roleId : roleIds) {
Role role = roleDao.findById(roleId).get(); roles.add(role);
}
//設置用戶和角色集合的關係user.setRoles(roles);
//3.更新用戶
userDao.save(user);
}

2.3前端代碼實現
(1)添加分配角色的組件


<template>
<div class="add-form">
<el-dialog title="分配角色" :visible.sync="roleFormVisible" style="height:300px">
<el-form	:model="formBase"	label-position="left" label-width="120px" style='margin-left:120px; width:500px;'>
<el-checkbox-group
v-model="checkedCities1">
<el-checkbox v-for="(item,index) in cities" :label="item.id" :key="index">
{{item.name}}</el-checkbox>
</el-checkbox-group>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="createData">提交</el-button>
<el-button @click="roleFormVisible=false">取消</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {findAll} from "@/api/base/role" import {assignRoles} from "@/api/base/users" export default {
data () {
return {
roleFormVisible:false, formBase:{}, checkedCities1:[], data:[],
cities:[], id:null
}
},
methods: {
toAssignPrem(id) { findAll().then(res => {
this.id = id;
this.cities = res.data.data this.roleFormVisible=true
})
},
createData() { assignRoles({id:this.id,ids:this.checkedCities1}).then(res => {

this.$message({message:res.data.message,type:res.data.success?"success":"error"}); this.roleFormVisible=false
})
}
}
}
</script>

(2)引入組件

<!--分配角色組件 -->
<component v-bind:is="addRole" ref="addRole"></component>

3分配權限

3.1需求分析

完成對角色權限的分配。
在這裏插入圖片描述

3.2服務端代碼實現

(1)角色實體類中添加與權限的多對多關係並進行JPA配置

@JsonIgnore //忽略json轉化@ManyToMany
@JoinTable(name="pe_role_permission", joinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}, inverseJoinColumns=
{@JoinColumn(name="permission_id",referencedColumnName="id")})
private Set<Permission> permissions = new HashSet<Permission>(0);//角色與模塊	多對多

(2)控制器類添加權限分配


/**
* 分配權限
*/
@RequestMapping(value = "/role/assignPrem", method = RequestMethod.PUT) public Result assignPrem(@RequestBody Map<String,Object> map) {
//1.獲取被分配的角色的id
String roleId = (String) map.get("id");
//2.獲取到權限的id列表
List<String> permIds = (List<String>) map.get("permIds");
//3.調用service完成權限分配roleService.assignPerms(roleId,permIds); return new Result(ResultCode.SUCCESS);
}

(3)持久化類中添加分配權限方法





*/
public void assignPerms(String roleId,List<String> permIds) {
//1.獲取分配的角色對象
Role role = roleDao.findById(roleId).get();
//2.構造角色的權限集合
Set<Permission> perms = new HashSet<>(); for (String permId : permIds) {
Permission permission = permissionDao.findById(permId).get();
//需要根據父id和類型查詢API權限列表List<Permission> apiList =
permissionDao.findByTypeAndPid(PermissionConstants.PERMISSION_API, permission.getId()); perms.addAll(apiList);//自定賦予API權限
perms.add(permission);//當前菜單或按鈕的權限
}
System.out.println(perms.size());
//3.設置角色和權限的關係role.setPermissions(perms);
//4.更新角色
roleDao.save(role);
}

3.3前端代碼實現

(1)綁定權限按鈕

<el-table-column fixed="right" label="操作" align="center" width="250">
<template slot-scope="scope">
<el-button @click="handlerPerm(scope.row)" type="text" size="small">分配權限</el- button>
<el-button @click="handleUpdate(scope.row)" type="text" size="small">修改</el-
button>
<el-button @click="handleDelete(scope.row)" type="text" size="small">刪除</el- button>
</template>
</el-table-column>

(2)添加分配權限的API方法


export const assignPrem = data => createAPI(`/sys/role/assignPrem`, 'put', data)

(3)使用Element-UI構造權限樹

<el-dialog :title="'爲【'+formData.name+'】分配權限'" :visible.sync="permFormVisible" style="hight:100px;line-height:1px">
<el-tree
:data="treeData" default-expand-all show-checkbox
node-key="id" ref="tree"
:default-checked-keys="checkNodes"
:props="{label:'name'}">
</el-tree>
<div slot="footer" class="dialog-footer">
<el-button @click="permFormVisible = false">取 消</el-button>
<el-button type="primary" @click="assignPrem">確 定</el-button>
</div>
</el-dialog>

(4)完成添加權限

import {list,add,update,remove,detail,assignPrem} from "@/api/base/role" import * as permApi from "@/api/base/permissions"
import commonApi from "@/utils/common"
import PageTool from './../../components/page/page-tool' var _this = null
export default { name: 'roleList',
components: {PageTool}, props: ['objId'], data() {
return { formData:{},
treeData:[], checkNodes:[], dialogFormVisible: false, permFormVisible:false, dataList:[],
counts:0, requestParameters:{
page: 1,
pagesize: 10
}
}
},
methods: { assignPrem() {

assignPrem({roleId:this.formData.id,ids:this.$refs.tree.getCheckedKeys()}).then(res =>
{


this.$message({message:res.data.message,type:res.data.success?"success":"error"}); this.permFormVisible=false
})
},
handlerPerm(obj) { detail({id:obj.id}).then(res=>{
this.formData = res.data.data; if(this.formData.menusIds != null) {
this.checkNodes = this.formData.menusIds.split(",")
}
if(this.formData.pointIds != null) { this.checkNodes.push(this.formData.pointIds.split(","))
}
permApi.list({type:0,pid:null}).then(res => {
this.treeData = commonApi.transformTozTreeFormat(res.data.data) this.permFormVisible=true
})
})
},
handlerAdd() { this.formData={}
this.dialogFormVisible = true
},
handleDelete(obj) { this.$confirm(
`本次操作將刪除${obj.name},刪除后角色將不可恢復,您確認刪除嗎?`
).then(() => {
remove({id: obj.id}).then(res => {


this.$message({message:res.data.message,type:res.data.success?"success":"error"}); this.doQuery()
})
})
},
handleUpdate(obj) { detail({id:obj.id}).then(res=>{
this.formData = res.data.data; this.formData.id = obj.id; this.dialogFormVisible = true
})
},
saveOrUpdate() {
if(this.formData.id == null || this.formData.id == undefined) { this.save()
}else{
this.update();
}
},
update(){ update(this.formData).then(res=>{

this.$message({message:res.data.message,type:res.data.success?"success":"error"}); if(res.data.success){
this.formData={}; this.dialogFormVisible=false; this.doQuery();
}
})
},
save() { add(this.formData).then(res=>{

this.$message({message:res.data.message,type:res.data.success?"success":"error"}); if(res.data.success){
this.formData={}; this.dialogFormVisible=false;
this.doQuery();
}
})
},
// 獲取詳情
doQuery() { list(this.requestParameters).then(res => {
this.dataList = res.data.data.rows this.counts = res.data.data.total
})
},
// 每頁顯示信息條數
handleSizeChange(pageSize) { this.requestParameters.pagesize = pageSize if (this.requestParameters.page === 1) {
_this.doQuery(this.requestParameters)
}
},
// 進入某一頁
handleCurrentChange(val) { this.requestParameters.page = val
_this.doQuery()
},
},
// 掛載結束
mounted: function() {},
// 創建完畢狀態
created: function() {
_this = this this.doQuery()
},
// 組件更新
updated: function() {}
}
</script>

4常見的認證機制

4.1HTTP Basic Auth

HTTP Basic Auth簡單點說明就是每次請求API時都提供用戶的username和password,簡言之,Basic Auth是配合RESTful API 使用的最簡單的認證方式,只需提供用戶名密碼即可,但由於有把用戶名密碼暴露給第三方客戶端的風險,在生產環境下被使用的越來越少。因此,在開發對外開放的RESTful API時,儘量避免採用HTTP Basic Auth

4.2Cookie Auth

Cookie認證機制就是爲一次請求認證在服務端創建一個Session對象,同時在客戶端的瀏覽器端創建了一個Cookie 對象;通過客戶端帶上來Cookie對象來與服務器端的session對象匹配來實現狀態管理的。默認的,當我們關閉瀏覽器的時候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時間內有效

4.3OAuth

OAuth(開放授權)是一個開放的授權標準,允許用戶讓第三方應用訪問該用戶在某一web服務上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。 OAuth允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每一個令牌授權一個特定的第三方系統(例如,視頻 編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣, OAuth讓用戶可以授權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非所有內容
在這裏插入圖片描述
這種基於OAuth的認證機制適用於個人消費者類的互聯網產品,如社交類APP等應用,但是不太適合擁有自有認證權限管理的企業應用。

4.4Token Auth

使用基於 Token 的身份驗證方法,在服務端不需要存儲用戶的登錄記錄。大概的流程是這樣的:
1.客戶端使用用戶名跟密碼請求登錄
2.服務端收到請求,去驗證用戶名與密碼
3.驗證成功後,服務端會簽發一個 Token,再把這個 Token 發送給客戶端
4.客戶端收到 Token 以後可以把它存儲起來,比如放在 Cookie 裏
5.客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 Token
6.服務端收到請求,然後去驗證客戶端請求裏面帶着的 Token,如果驗證成功,就向客戶端返回請求的數據
在這裏插入圖片描述

Token Auth的優點
支持跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通過HTTP頭傳輸.
無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因爲Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.
更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可.
去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可.
更適用於移動應用: 當你的客戶端是一個原生平臺(iOS, Android,Windows 8等)時,Cookie是不被支持的
(你需要通過Cookie容器進行處理),這時採用Token認證機制就會簡單得多。
CSRF:因爲不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求僞造)的防範。
性能: 一次網絡往返時間(通過數據庫查詢session信息)總比做一次HMACSHA256計算 的Token驗證和解析要費時得多.
不需要爲登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要爲登錄頁面做特殊處理. 基於標準化:你的API可以採用標準化的 JSON Web Token ( JWT). 這個標準已經存在多個後端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

5HRM中的TOKEN簽發與驗證

5.1什麼是JWT

JSON Web Token(JWT)是一個非常輕巧的規範。這個規範允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息。在Java世界中通過JJWT實現JWT創建和驗證。
5.2JJWT的快速入門

5.2.1token的創建
(1)創建maven工程,引入依賴


<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>

(2)創建類CreateJwtTest,用於生成token


public class CreateJwtTest {
public static void main(String[] args) { JwtBuilder builder= Jwts.builder().setId("888")
.setSubject("小白")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,"itcast"); System.out.println( builder.compact() );
}
}

(3)測試運行,輸出如下:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MTM0NTh9.gq0J- cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk

5.2.2token的解析
我們剛纔已經創建了token ,在web應用中這個操作是由服務端進行然後發給客戶端,客戶端在下次向服務端發送請求時需要攜帶這個token(這就好像是拿着一張門票一樣),那服務端接到這個token 應該解析出token中的信息
(例如用戶id),根據這些信息查詢數據庫返回相應的結果。
創建ParseJwtTest





public class ParseJwtTest {
public static void main(String[] args) {
String token="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiO jE1MjM0MTM0NTh9.gq0J-cOM_qCNqU_s-d_IrRytaNenesPmqAIhQpYXHZk";
Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(token).getBody();
System.out.println("id:"+claims.getId()); System.out.println("subject:"+claims.getSubject()); System.out.println("IssuedAt:"+claims.getIssuedAt());
}
}

試着將token或簽名祕鑰篡改一下,會發現運行時就會報錯,所以解析token也就是驗證token
5.2.3自定義claims
我們剛纔的例子只是存儲了id和subject兩個信息,如果你想存儲更多的信息(例如角色)可以定義自定義claims
(1)創建CreateJwtTest3,並存儲指定的內容


public class CreateJwtTest3 {
public static void main(String[] args) {
//爲了方便測試,我們將過期時間設置爲1分鐘
long now = System.currentTimeMillis();//當前時間long exp = now + 1000*60;//過期時間爲1分鐘
JwtBuilder builder= Jwts.builder().setId("888")
.setSubject("小白")
.setIssuedAt(new Date())
.signWith(SignatureAlgorithm.HS256,"itcast")
.setExpiration(new Date(exp))
.claim("roles","admin") //自定義claims存儲數據
.claim("logo","logo.png"); System.out.println( builder.compact() );
}
}

(2)修改ParseJwtTest,獲取指定信息


public class ParseJwtTest {
public static void main(String[] args) {
String compactJws="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1MjM0MT czMjMsImV4cCI6MTUyMzQxNzM4Mywicm9sZXMiOiJhZG1pbiIsImxvZ28iOiJsb2dvLnBuZyJ9.b11p4g4rE94r qFhcfzdJTPCORikqP_1zJ1MP8KihYTQ";
Claims claims = Jwts.parser().setSigningKey("itcast").parseClaimsJws(compactJws).getBody();
System.out.println("id:"+claims.getId()); System.out.println("subject:"+claims.getSubject()); System.out.println("roles:"+claims.get("roles")); System.out.println("logo:"+claims.get("logo"));
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("簽發時間:"+sdf.format(claims.getIssuedAt())); System.out.println("過期時間:"+sdf.format(claims.getExpiration())); System.out.println("當前時間:"+sdf.format(new Date()) );
}
}

5.3JWT工具類

在ihrm_common工程中創建JwtUtil工具類

@ConfigurationProperties("jwt.config") public class JwtUtil {

private String key; private long ttl;

public String getKey() { return key;
}


public void setKey(String key) { this.key = key;
}


public long getTtl() { return ttl;
}


public void setTtl(long ttl) { this.ttl = ttl;
}


/**
* 籤 發 token
*/
public String createJWT(String id, String subject,Map<String,Object> map){ long now=System.currentTimeMillis();
long exp=now+ttl;
JwtBuilder jwtBuilder = Jwts.builder().setId(id)
.setSubject(subject).setIssuedAt(new  Date())
.signWith(SignatureAlgorithm.HS256, key); for(Map.Entry<String,Object> entry:map.entrySet()) {
jwtBuilder.claim(entry.getKey(),entry.getValue());
}
if(ttl>0){
jwtBuilder.setExpiration( new Date(exp));

}
/**

}
String token = jwtBuilder.compact(); return token;
*解析JWT
*@param token
*@return
*/
public Claims parseJWT(String token){ Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token).getBody();
}catch (Exception e){

}
return claims;
}
}

(3)修改ihrm_common工程的application.yml, 添加配置


jwt: config:
key: saas-ihrm ttl: 360000

5.4登錄成功簽發token

(1)配置JwtUtil。修改ihrm_system工程的啓動類


@Bean
public JwtUtil jwtUtil(){ return new util.JwtUtil();
}

(2)添加登錄方法


/**
*用戶登錄
*1.通過service根據mobile查詢用戶
*2.比較password
*3.生成jwt信息
*
*/
@RequestMapping(value="/login",method = RequestMethod.POST) public Result login(@RequestBody Map<String,String> loginMap) {
String mobile = loginMap.get("mobile"); String password = loginMap.get("password");
User user = userService.findByMobile(mobile);
//登錄失敗
if(user == null || !user.getPassword().equals(password)) { return new Result(ResultCode.MOBILEORPASSWORDERROR);
}else {
//登錄成功
Map<String,Object> map = new HashMap<>(); map.put("companyId",user.getCompanyId()); map.put("companyName",user.getCompanyName());
String token = jwtUtils.createJwt(user.getId(), user.getUsername(), map); return new Result(ResultCode.SUCCESS,token);
}
}

(3)測試運行結果
在這裏插入圖片描述
使用postman驗證登錄返回:

{"success":true,"code":10000,"message":"操作成
功!","data":"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxMDYyNjYxODkxNjE4Mzc3NzI4Iiwic3ViIjoiemhhb
mdzYW4iLCJpYXQiOjE1NDI0NjgzNzcsImNvbXBhbnlJZCI6IjEiLCJjb21wYW55TmFtZSI6IuS8oOaZuuaSreWu oiIsImV4cCI6MTU0MjU1NDc3N30.J-8uv8jOp2GMLpBwrUOksnErjA4-DOJ_qvy7tsJbsa8"}

5.5獲取用戶信息鑑權

需求:用戶登錄成功之後,會發送一個新的請求到服務端,獲取用戶的詳細信息。獲取用戶信息的過程中必須登錄 才能,否則不能獲取。
前後端約定:前端請求微服務時需要添加頭信息Authorization ,內容爲Bearer+空格+token
(1)添加響應值對象


@Getter @Setter
@NoArgsConstructor
public class ProfileResult {

private String mobile; private String username;
private String company; private Map roles;

public ProfileResult(User user) { this.mobile = user.getMobile(); this.username = user.getUsername(); this.company = user.getCompanyName();
//角色數據
Set<String> menus = new HashSet<>(); Set<String> points = new HashSet<>(); Set<String> apis = new HashSet<>(); Map rolesMap = new HashMap<>();
for (Role role : user.getRoles()) {
for (Permission perm : role.getPermissions()) { String code = perm.getCode(); if(perm.getType() == 1) {
menus.add(code);
}else if(perm.getType() == 2) { points.add(code);
}else {
apis.add(code);
}
}
}
rolesMap.put("menus",menus); rolesMap.put("points",points); rolesMap.put("apis",points); this.roles = rolesMap;
}
}

(2)添加profile方法


/**
* 獲取個人信息
*/
@RequestMapping(value = "/profile", method = RequestMethod.POST) public Result profile(HttpServletRequest request) throws Exception {
//臨時使用
String userId = "1";
User user = userService.findById(userId);
return new Result(ResultCode.SUCCESS,new ProfileResult(user));
}

(3)驗證token
思路:從請求中獲取key爲Authorization的token信息,並使用jwt驗證,驗證成功後獲取隱藏信息。 修改profile方法添加如下代碼

@RequestMapping(value = "/profile", method = RequestMethod.POST) public Result profile(HttpServletRequest request) throws Exception {
//請求中獲取key爲Authorization的頭信息
String authorization = request.getHeader("Authorization"); if(StringUtils.isEmpty(authorization)) {
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
//前後端約定頭信息內容以	Bearer+空格+token 形式組成
String token = authorization.replace("Bearer ", "");
//比較並獲取claims
Claims claims = jwtUtil.parseJWT(token); if(claims == null) {
throw new CommonException(ResultCode.UNAUTHENTICATED);
}
String userId = claims.getId();
User user = userService.findById(userId);
return new Result(ResultCode.SUCCESS,new ProfileResult(user));
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章