springcloud-gateway整合jwt+jcasbin實(shí)現(xiàn)權(quán)限控制的詳細(xì)過程
jcasbin簡(jiǎn)介:
jcasbin 是一個(gè)用 Java 語(yǔ)言打造的輕量級(jí)開源訪問控制框架https://github.com/casbin/jcasbin,是casbin的Java語(yǔ)言版本。目前在 GitHub 開源。jcasbin 采用了元模型的設(shè)計(jì)思想,支持多種經(jīng)典的訪問控制方案,如基于角色的訪問控制 RBAC、基于屬性的訪問控制 ABAC 等。
jcasbin 的主要特性包括:
1.支持自定義請(qǐng)求的格式,默認(rèn)的請(qǐng)求格式為{subject, object, action};
2.具有訪問控制模型 model 和策略 policy 兩個(gè)核心概念;
3.支持 RBAC 中的多層角色繼承,不止主體可以有角色,資源也可以具有角色;
4.支持超級(jí)用戶,如 root 或 Administrator,超級(jí)用戶可以不受授權(quán)策略的約束訪問任意資源;
5.支持多種內(nèi)置的操作符,如 keyMatch,方便對(duì)路徑式的資源進(jìn)行管理,如 /foo/bar 可以映射到 /foo*
jcasbin 不做的事情:
1.身份認(rèn)證 authentication (即驗(yàn)證用戶的用戶名、密碼),jcasbin 只負(fù)責(zé)訪問控制。應(yīng)該有其他專門的組件負(fù)責(zé)身份認(rèn)證,然后由 jcasbin 進(jìn)行訪問控制,二者是相互配合的關(guān)系;
2.管理用戶列表或角色列表。jcasbin 認(rèn)為由項(xiàng)目自身來(lái)管理用戶、角色列表更為合適,jcasbin 假設(shè)所有策略和請(qǐng)求中出現(xiàn)的用戶、角色、資源都是合法有效的。
項(xiàng)目架構(gòu):
基于springboot+springcloud+nacos的簡(jiǎn)單分布式項(xiàng)目,項(xiàng)目交互采用openFeign框架,單獨(dú)提取出來(lái)成為一個(gè)獨(dú)立的model:feign
父pom文件:
<properties>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
<druid.version>1.2.4</druid.version>
<spring-boot.version>2.2.6.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.9.RELEASE</spring-cloud-alibaba.version>
<sql.version>8.0.29</sql.version>
<jwt.version>0.9.0</jwt.version>
<swagger2.version>2.9.2</swagger2.version>
<jcasbin.version>1.32.1</jcasbin.version>
<jdbc-adapter.version>2.3.3</jdbc-adapter.version>
</properties>
<dependencies>
<dependency>
<groupId>com.distribute</groupId>
<artifactId>commonUtil</artifactId>
<version>${version}</version>
</dependency>
<dependency>
<groupId>org.casbin</groupId>
<artifactId>jcasbin</artifactId>
<version>${jcasbin.version}</version>
</dependency>
<dependency>
<groupId>org.casbin</groupId>
<artifactId>jdbc-adapter</artifactId>
<version>${jdbc-adapter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.distribute</groupId>
<artifactId>feign</artifactId>
<version>${version}</version>
</dependency>
<!--鑒權(quán)-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${sql.version}</version>
<scope>runtime</scope>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>gateway項(xiàng)目:
pom文件:
<dependencies>
<dependency>
<groupId>com.distribute</groupId>
<artifactId>feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>gateway相關(guān)核心代碼:
注冊(cè)中心采用nacos,關(guān)于nacos的使用可以自行學(xué)習(xí),不是本文關(guān)鍵。
網(wǎng)關(guān)采用gateway,核心就是gateway中的過濾器接口:GlobalFilter:
@Slf4j
@Component
@Order(value = Integer.MIN_VALUE)
public class AuthorityGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private ConfigProperty configProperty;
@Autowired
private AdminUserInterfaceFeign adminUserInterfaceFeign;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//filter的前置處理
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
InetSocketAddress remoteAddress = request.getRemoteAddress();
//3 獲得請(qǐng)求頭 ,獲得token值
HttpHeaders headers = request.getHeaders();
//判斷白名單和是否有權(quán)限
if (validateWhiteList(path)) {
return chain
//繼續(xù)調(diào)用filter
.filter(exchange)
//filter的后置處理
.then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("請(qǐng)求路徑:{},遠(yuǎn)程IP地址:{},響應(yīng)碼:{}", path, remoteAddress, statusCode);
}));
} else if(hasPower(request)){
return chain
//繼續(xù)調(diào)用filter
.filter(exchange)
//filter的后置處理
.then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("請(qǐng)求路徑:{},遠(yuǎn)程IP地址:{},響應(yīng)碼:{}", path, remoteAddress, statusCode);
}));
}else {
return noPower(exchange);
}
}
@Override
public int getOrder() {
return 0;
}
/**
* 判斷是否有權(quán)限
*/
private boolean hasPower( ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
List<String> authorizationList = headers.getOrEmpty("Authorization");
if(authorizationList.size()==0){
return false;
}else{
try {
Claims claims = JwtUtil.parseJWT(authorizationList.get(0));
//判斷token是否過期
Date expireTime = claims.getExpiration();
Date now = new Date();
if (now.after(expireTime))
{
return false;
}
String userName = claims.getSubject();
String path = request.getPath().pathWithinApplication().value();
String method = request.getMethodValue();
Policy checkPower = new Policy(userName,path,method);
CommonResult result = adminUserInterfaceFeign.checkPower(checkPower);
return result.isSuccess() && (Boolean) result.getData();
}catch (Exception e){
return false;
}
}
}
/**
* 網(wǎng)關(guān)拒絕,返回Result
*
* @param
*/
private Mono<Void> noPower(ServerWebExchange serverWebExchange) {
// 權(quán)限不夠攔截
serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(JSONUtil.toJsonStr(CommonResult.error(HttpStatusCode.UNAUTHORIZED)).getBytes(StandardCharsets.UTF_8));
ServerHttpResponse response = serverWebExchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定編碼,否則在瀏覽器中會(huì)中文亂碼
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
public boolean validateWhiteList(String requestPath) {
for (String whiteList : configProperty.getWhiteList()) {
if (requestPath.contains(whiteList) || requestPath.matches(whiteList)) {
return true;
}
}
return false;
}
}
網(wǎng)關(guān)中首先校驗(yàn)是否屬于白名單,白名單可以寫在application.yml中,通過實(shí)體類加載:
application.yml:
distribute:
config:
whiteList:
- admin/login
- admin/role/checkPowerConfigProperty:
@Component
@ConfigurationProperties(prefix = "distribute.config")
@Data
public class ConfigProperty {
List<String> whiteList;
}訪問的資源(比如Controller路徑)如果不存在于白名單,則通過Feign調(diào)用admin-user項(xiàng)目中的鑒權(quán)方法進(jìn)行鑒權(quán),關(guān)于admin-user項(xiàng)目以及feign的使用,在之后會(huì)提到,GlobalFilter中涉及的jwt工具類,文末會(huì)給出。
admin-user項(xiàng)目:
pom文件:
<dependencies>
<dependency>
<groupId>com.distribute</groupId>
<artifactId>feign</artifactId>
</dependency>
<!--數(shù)據(jù)庫(kù)驅(qū)動(dòng)-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--數(shù)據(jù)庫(kù)連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--jdbc連接數(shù)據(jù)庫(kù)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--服務(wù)注冊(cè)與發(fā)現(xiàn)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
登錄控制器(測(cè)試):
/**
* @author :fengwenzhe
* @date :Created in 2023/2/3 11:41
* 文件說明: </p>
*/
@RestController
@RequestMapping("admin")
public class LoginCtrl {
@PostMapping("login")
public CommonResult login(@RequestBody Account account){
String token = JwtUtil.createJWT(UUID.randomUUID().toString(), account.getUserName(), 3600L*1000);
Map<String,Object> result = new HashMap<>();
result.put("username",account.getUserName());
result.put("token",token);
return CommonResult.ok(result);
}
}jcasbin的整合:
jcasbin可以從文件加載角色權(quán)限信息,此處已整合成從數(shù)據(jù)庫(kù)加載角色權(quán)限信息,為此,需要為jcasbin配置數(shù)據(jù)源(為了方便直接使用項(xiàng)目中的數(shù)據(jù)庫(kù),實(shí)際生產(chǎn)環(huán)境可以分開)以及模型文件路徑:
application.yml:
org:
jcasbin:
model-path: jcasbin/basic_model.conf
spring:
datasource:
url: jdbc:mysql://localhost:3306/jcasbin?useSSL=false
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root使用jcasbin首先需要配置jcasbin工廠類,初始化enforcer:
@Component
public class EnforcerFactory implements InitializingBean {
private static Enforcer enforcer;
@Autowired
private EnforcerConfigProperties enforcerConfigProperties;
@Autowired
private DataSource dataSource;
@Override
public void afterPropertiesSet() throws Exception {
//從數(shù)據(jù)庫(kù)讀取策略
JDBCAdapter jdbcAdapter = new JDBCAdapter(dataSource);
String path = this.getClass().getClassLoader().getResource("").getPath();
enforcer = new Enforcer(path+enforcerConfigProperties.getModelPath(), jdbcAdapter);
enforcer.loadPolicy();//Load the policy from DB.
}
public static Enforcer getEnforcer(){
return enforcer;
}
}
@Configuration
@ConfigurationProperties(prefix = "org.jcasbin")
@Data
public class EnforcerConfigProperties {
private String modelPath;
}此后所有對(duì)jcasbin的操作都基于唯一實(shí)例enforcer,此時(shí)就可以進(jìn)行業(yè)務(wù)上的新增權(quán)限、角色、鑒權(quán)等的開發(fā)了。
RoleController角色控制器:
package com.distribute.admin.ctrl;
import com.distribute.admin.service.EnforcerFactory;
import com.distribute.common.CommonResult;
import com.distribute.entity.PermissionEntity;
import com.distribute.entity.Policy;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author :fengwenzhe
* @date :Created in 2023/2/3 11:41
* 文件說明: </p>
*/
@RestController
@RequestMapping("admin/role")
public class RoleCtrl {
/**
*@Description <獲取全部角色>
*@return com.distribute.common.CommonResult
*@date 2023/2/6 11:13
*@auther fengwenzhee
*/
@PostMapping("findAllRoleList")
public CommonResult findAllRoleList(){
return EnforcerFactory.findAllRoleList();
}
/**
*@Description <批量新增 用戶/角色 的權(quán)限>
*@param permissionEntity
*@return com.distribute.common.CommonResult
*@date 2023/2/6 11:13
*@auther fengwenzhee
*/
@PostMapping("batchAddPermission")
public CommonResult batchAddPermission(@RequestBody PermissionEntity permissionEntity){
return EnforcerFactory.batchAddPermission(permissionEntity);
}
/**
*@Description <批量刪除 用戶/角色 的權(quán)限>
*@param permissionEntity
*@return com.distribute.common.CommonResult
*@date 2023/2/5 17:08
*@auther fengwenzhee
*/
@PostMapping("batchDeletePermission")
public CommonResult batchDeletePermission(@RequestBody PermissionEntity permissionEntity){
return EnforcerFactory.batchDeletePermission(permissionEntity);
}
/**
*@Description <批量為用戶添加角色>
*@param permissionEntity
*@return com.distribute.common.CommonResult
*@date 2023/2/6 11:17
*@auther fengwenzhee
*/
@PostMapping("batchAddRoleForUser")
public CommonResult batchAddRoleForUser(@RequestBody PermissionEntity permissionEntity){
return EnforcerFactory.batchAddRoleForUser(permissionEntity);
}
/**
*@Description <批量刪除用戶角色>
*@param permissionEntity
*@return com.distribute.common.CommonResult
*@date 2023/2/5 17:08
*@auther fengwenzhee
*/
@PostMapping("batchDeleteRoleForUser")
public CommonResult batchDeleteRoleForUser(@RequestBody PermissionEntity permissionEntity){
return EnforcerFactory.batchDeleteRoleForUser(permissionEntity);
}
/**
*@Description <批量刪除角色及其涉及到的用戶與角色關(guān)系>
*@param permissionEntity
*@return com.distribute.common.CommonResult
*@date 2023/2/5 17:08
*@auther fengwenzhee
*/
@PostMapping("batchDeleteRole")
public CommonResult batchDeleteRole(@RequestBody PermissionEntity permissionEntity){
return EnforcerFactory.batchDeleteRole(permissionEntity);
}
@PostMapping("checkPower")
public CommonResult checkPower(@RequestBody Policy policy){
if(policy.getSub().equals("admin")){ //超級(jí)管理員直接放行
return CommonResult.ok(true);
}
String path = this.getClass().getClassLoader().getResource("").getPath();
// Enforcer enforcer = new Enforcer(path+"/jcasbin/basic_model.conf", path+"/jcasbin/basic_policy.csv"); 從本地文件加載權(quán)限信息
if (EnforcerFactory.getEnforcer().enforce("user_"+policy.getSub(), policy.getObj(), policy.getAct())) {
// permit alice to read data1
return CommonResult.ok(true);
} else {
// deny the request, show an error
return CommonResult.ok(false);
}
}
}
基于RBAC的模型文件basic_model.conf:
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
在RoleController角色控制器中已經(jīng)寫好了一些方法,后續(xù)可以根據(jù)需要自行新增,入?yún)?shí)體我簡(jiǎn)單封裝了一下,然后循環(huán)進(jìn)行批量操作:
PermissionEntity:
@Data
public class PermissionEntity implements Serializable {
private Integer type; //操作對(duì)象是用戶還是角色
private List<Policy> policyList;
}
Policy:
@Data
public class Policy implements Serializable {
/**想要訪問資源的用戶 或者角色*/
private String sub;
/**將要訪問的資源,可以使用 * 作為通配符,例如/user/* */
private String obj;
/**用戶對(duì)資源執(zhí)行的操作。HTTP方法,GET、POST、PUT、DELETE等,可以使用 * 作為通配符*/
private String act;
/**
*
* @param sub 想要訪問資源的用戶 或者角色
* @param obj 將要訪問的資源,可以使用 * 作為通配符,例如/user/*
* @param act 用戶對(duì)資源執(zhí)行的操作。HTTP方法,GET、POST、PUT、DELETE等,可以使用 * 作為通配符
*/
public Policy(String sub, String obj, String act) {
super();
this.sub = sub;
this.obj = obj;
this.act = act;
}
@Override
public String toString() {
return "Policy [sub=" + sub + ", obj=" + obj + ", act=" + act + "]";
}
}在EnforcerFactory中新增RoleController對(duì)應(yīng)方法:
public static CommonResult batchAddPermission(PermissionEntity permissionEntity) {
if(permissionEntity.getType()==null){
return CommonResult.error(HttpStatusCode.OPERATION_TYPE_REQUIRED);
}
if(permissionEntity.getType()==1){
//操作對(duì)象為用戶
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.addPermissionForUser("user_"+policy.getSub(),policy.getObj(),policy.getAct());
}
}else if(permissionEntity.getType()==2){
//操作對(duì)象為角色
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.addPermissionForUser("role_"+policy.getSub(),policy.getObj(),policy.getAct());
}
}else {
return CommonResult.error(HttpStatusCode.OPERATION_TYPE_ERROR);
}
return CommonResult.ok(true);
}
public static CommonResult batchAddRoleForUser(PermissionEntity permissionEntity) {
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.addRoleForUser("user_"+policy.getSub(),"role_"+policy.getObj());
}
return CommonResult.ok(true);
}
public static CommonResult batchDeleteRole(PermissionEntity permissionEntity) {
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.deleteRole("role_"+policy.getSub());
}
return CommonResult.ok(true);
}
public static CommonResult batchDeleteRoleForUser(PermissionEntity permissionEntity) {
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.deleteRoleForUser("user_"+policy.getSub(),"role_"+policy.getObj());
}
return CommonResult.ok(true);
}
public static CommonResult batchDeletePermission(PermissionEntity permissionEntity) {
if(permissionEntity.getType()==null){
return CommonResult.error(HttpStatusCode.OPERATION_TYPE_REQUIRED);
}
if(permissionEntity.getType()==1){
//操作對(duì)象為用戶
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.deletePermissionForUser("user_"+policy.getSub(),policy.getObj(),policy.getAct());
}
}else if(permissionEntity.getType()==2){
//操作對(duì)象為角色
for (Policy policy:permissionEntity.getPolicyList()){
enforcer.deletePermissionForUser("role_"+policy.getSub(),policy.getObj(),policy.getAct());
}
}else {
return CommonResult.error(HttpStatusCode.OPERATION_TYPE_ERROR);
}
return CommonResult.ok(true);
}
public static CommonResult findAllRoleList() {
List<String> roles = new ArrayList<>();
for (String role:enforcer.getAllRoles()){
roles.add(role.split("role_")[1]);
}
return CommonResult.ok(roles);
}
PS:jcasbin中對(duì)權(quán)限的把控是基于subject的,所以無(wú)法區(qū)分權(quán)限是用戶還是角色的,在這里用前綴是user_還是role_來(lái)區(qū)分,數(shù)據(jù)庫(kù)測(cè)試數(shù)據(jù)如下:

意思是role_管理員角色下有兩個(gè)權(quán)限,分別是/c/main/getUser POST,和/c/main/deleteUser DELETE,v1字段可以視為資源,v2為請(qǐng)求動(dòng)作,
user_fengwenzhe用戶具有role_管理員的角色,鑒權(quán)時(shí)可以如下進(jìn)行:
String path = request.getPath().pathWithinApplication().value();
String method = request.getMethodValue();
Policy checkPower = new Policy(userName,path,method);
CommonResult result = adminUserInterfaceFeign.checkPower(checkPower);比如此時(shí)我傳入userName=fengwenzhe,path=/c/main/getUser method=POST,就可以鑒權(quán)成功,因?yàn)橛星熬Y存在,代碼中自行補(bǔ)足'user_':
if (EnforcerFactory.getEnforcer().enforce("user_"+policy.getSub(), policy.getObj(), policy.getAct())) {
// permit to read data
return CommonResult.ok(true);
} else {
// deny the request, show an error
return CommonResult.ok(false);
}feign項(xiàng)目:
只定義feign相關(guān)接口與實(shí)現(xiàn)類:
/**
* @author :fengwenzhe
* @date :Created in 2023/2/2 21:48
* 文件說明: </p>
*/
@FeignClient(value = "platform-admin-user",fallback = AdminUserFeignImpl.class)
@Component
public interface AdminUserInterfaceFeign {
@PostMapping("admin/role/checkPower")
CommonResult checkPower(@RequestBody Policy policy);
/**
*@Description <批量新增 用戶/角色 的權(quán)限>
*@param permissionEntity
*@return com.distribute.common.CommonResult
*@date 2023/2/6 11:13
*@auther fengwenzhee
*/
@PostMapping("batchAddPermission")
CommonResult batchAddPermission(@RequestBody PermissionEntity permissionEntity);
}package com.distribute.impl;
import com.distribute.AdminUserInterfaceFeign;
import com.distribute.common.CommonResult;
import com.distribute.common.HttpStatusCode;
import com.distribute.entity.PermissionEntity;
import com.distribute.entity.Policy;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author :fengwenzhe
* @date :Created in 2023/2/2 22:03
* 文件說明: </p>
*/
@Component
public class AdminUserFeignImpl implements AdminUserInterfaceFeign {
@Override
public CommonResult checkPower(@RequestBody Policy power) {
return CommonResult.error(HttpStatusCode.REQUEST_TIMEOUT);
}
@Override
public CommonResult batchAddPermission(PermissionEntity permissionEntity) {
return CommonResult.error(HttpStatusCode.REQUEST_TIMEOUT);
}
}gateway啟動(dòng)類加入feign相關(guān)注釋:
@SpringBootApplication
@ComponentScan(basePackages = {"com.distribute"})
@EnableFeignClients(basePackages = "com.distribute") //因?yàn)閒eign接口定義的包與項(xiàng)目不同級(jí) 項(xiàng)目默認(rèn)掃描com.distribute.gateway
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
此時(shí)啟動(dòng)gateway通過feign調(diào)用admin-user項(xiàng)目中的方法依然還是報(bào)錯(cuò),需要增加如下配置類:
/**
*@Description <手動(dòng)注入Bean Spring Cloud Gateway是基于WebFlux的,是ReactiveWeb,所以HttpMessageConverters不會(huì)自動(dòng)注入。如果不注入,springcloudGateway調(diào)用feign時(shí)會(huì)報(bào)錯(cuò)
* No qualifying bean of type 'org.springframework.boot.autoconfigure.http.HttpMessageConverters>
*/
@Configuration
public class FeignConfig {
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
}最后自行設(shè)置異常信息:
沒有權(quán)限:
{
"code": 401,
"data": "",
"message": "沒有被授權(quán)或者授權(quán)已經(jīng)失效",
"success": false
}鑒權(quán)成功:
{
"data": [
"管理員"
],
"success": true,
"code": 200,
"message": "請(qǐng)求已經(jīng)成功處理"
}jwt工具類:
@Component
public class JwtUtil {
//加密 解密時(shí)的密鑰 用來(lái)生成key
public static final String JWT_KEY = "IT1995";
/**
* 生成加密后的秘鑰 secretKey
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static String createJWT(String id, String subject, long ttlMillis){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定簽名的時(shí)候使用的簽名算法,也就是header那部分,jwt已經(jīng)將這部分內(nèi)容封裝好了。
long nowMillis = System.currentTimeMillis();//生成JWT的時(shí)間
Date now = new Date(nowMillis);
SecretKey key = generalKey();//生成簽名的時(shí)候使用的秘鑰secret,這個(gè)方法本地封裝了的,一般可以從本地配置文件中讀取,切記這個(gè)秘鑰不能外露哦。它就是你服務(wù)端的私鑰,在任何場(chǎng)景都不應(yīng)該流露出去。一旦客戶端得知這個(gè)secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。
JwtBuilder builder = Jwts.builder() //這里其實(shí)就是new一個(gè)JwtBuilder,設(shè)置jwt的body
// .setClaims(claims) //如果有私有聲明,一定要先設(shè)置這個(gè)自己創(chuàng)建的私有的聲明,這個(gè)是給builder的claim賦值,一旦寫在標(biāo)準(zhǔn)的聲明賦值之后,就是覆蓋了那些標(biāo)準(zhǔn)的聲明的
.setId(id) //設(shè)置jti(JWT ID):是JWT的唯一標(biāo)識(shí),根據(jù)業(yè)務(wù)需要,這個(gè)可以設(shè)置為一個(gè)不重復(fù)的值,主要用來(lái)作為一次性token,從而回避重放攻擊。
.setIssuedAt(now) //iat: jwt的簽發(fā)時(shí)間
.setSubject(subject) //sub(Subject):代表這個(gè)JWT的主體,即它的所有人,這個(gè)是一個(gè)json格式的字符串,可以存放什么userid,roldid之類的,作為什么用戶的唯一標(biāo)志。
.signWith(signatureAlgorithm, key);//設(shè)置簽名使用的簽名算法和簽名使用的秘鑰
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp); //設(shè)置過期時(shí)間
}
return builder.compact(); //就開始?jí)嚎s為xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx這樣的jwt
}
public static Claims parseJWT(String jwt){
SecretKey key = generalKey(); //簽名秘鑰,和生成的簽名的秘鑰一模一樣
Claims claims = Jwts.parser() //得到DefaultJwtParser
.setSigningKey(key) //設(shè)置簽名的秘鑰
.parseClaimsJws(jwt).getBody();//設(shè)置需要解析的jwt
return claims;
}
public static void main(String[] args){
Account account = new Account();
account.setUserName("it1995");
account.setPassword("123456");
String jwt = createJWT(UUID.randomUUID().toString(), JSONUtil.toJsonStr(account), 3600 * 24);
System.out.println("加密后:" + jwt);
//解密
Claims claims = parseJWT(jwt);
System.out.println("解密后:" + claims.getSubject());
}
}
統(tǒng)一結(jié)果返回類:
package com.distribute.common;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author :fengwenzhe
* @date :Created in 2023/2/2 20:38
* 文件說明: </p>
*/
@Data
@AllArgsConstructor
public class CommonResult {
private Object data;
private boolean success;
private Integer code;
private String message;
//私有化,防止new
private CommonResult() {}
//成功
public static CommonResult ok(Object data, HttpStatusCode statusCode) {
return new CommonResult(data,true,statusCode.code,statusCode.zhMessage); //code 也可以使用字典管理
}
//成功返回 重載 message沒有特別要求
public static CommonResult ok(Object data) {
return CommonResult.ok(data, HttpStatusCode.OK); //message 也可以使用字典管理
}
// 失敗
public static CommonResult error( HttpStatusCode statusCode) {
return new CommonResult("",false, statusCode.code, statusCode.zhMessage);
}
}
package com.distribute.common;
import lombok.Data;
public enum HttpStatusCode {
/**
* http狀態(tài)碼枚舉所有狀態(tài)碼注解
*/
USERNAME_PASSWORD_DENY(1000, "username password deny", "用戶名或密碼錯(cuò)誤"),
OK(200, "OK", "請(qǐng)求已經(jīng)成功處理"),
OPERATION_TYPE_ERROR(512, "", "操作類型不正確");
//錯(cuò)誤碼
public Integer code;
//提示信息
public String enMessage;
//提示信息
public String zhMessage;
HttpStatusCode(int code, String enMessage, String zhMessage) {
this.code = code;
this.enMessage = enMessage;
this.zhMessage = zhMessage;
}
}到此這篇關(guān)于springcloud-gateway整合jwt+jcasbin實(shí)現(xiàn)權(quán)限控制的文章就介紹到這了,更多相關(guān)springcloud-gateway整合jwt+jcasbin內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java的基礎(chǔ)語(yǔ)法學(xué)習(xí)筆記
這里為大家整理了Java的基礎(chǔ)語(yǔ)法學(xué)習(xí)筆記,包括關(guān)鍵詞、運(yùn)算符與基本的流程控制語(yǔ)句寫法等,需要的朋友可以參考下2016-05-05
Springboot處理跨域的實(shí)現(xiàn)方式(附Demo)
這篇文章主要介紹了Springboot處理跨域的實(shí)現(xiàn)方式(附Demo),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
JAVA核心知識(shí)之ConcurrentHashMap源碼分析
這篇文章主要介紹了JAVA核心知識(shí)之ConcurrentHashMap源碼分析,想了解ConcurrentHashMap的同學(xué)一定要看啊2021-04-04
Java面試必考之如何在項(xiàng)目中優(yōu)雅的拋出異常
這篇文章主要為大家詳細(xì)介紹了Java中的幾種異常關(guān)鍵字和異常類相關(guān)知識(shí),本文比較適合剛?cè)肟覬ava的小白以及準(zhǔn)備秋招的大佬閱讀,需要的可以收藏一下2023-06-06

