springcloud-gateway整合jwt+jcasbin實(shí)現(xiàn)權(quán)限控制的詳細(xì)過程
jcasbin簡(jiǎn)介:
jcasbin 是一個(gè)用 Java 語言打造的輕量級(jí)開源訪問控制框架https://github.com/casbin/jcasbin,是casbin的Java語言版本。目前在 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)目自身來管理用戶、角色列表更為合適,jcasbin 假設(shè)所有策略和請(qǐng)求中出現(xiàn)的用戶、角色、資源都是合法有效的。
項(xiàng)目架構(gòu):
基于springboot+springcloud+nacos的簡(jiǎn)單分布式項(xiàng)目,項(xiàng)目交互采用openFeign框架,單獨(dú)提取出來成為一個(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/checkPower
ConfigProperty:
@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的,所以無法區(qū)分權(quán)限是用戶還是角色的,在這里用前綴是user_還是role_來區(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í)的密鑰 用來生成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ù)的值,主要用來作為一次性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)于SpringCloud整合RabbitMQ的實(shí)例
- 一文詳解gRPC快速整合SpringCloud
- SpringSecurit鹽值加密的密碼驗(yàn)證以及強(qiáng)密碼驗(yàn)證過程
- springcloud?整合?openfeign的方法
- springcloud整合seata的實(shí)現(xiàn)代碼
- SpringCloud?Stream?整合RabbitMQ的基本步驟
- springcloud整合到項(xiàng)目中無法啟動(dòng)報(bào)錯(cuò)Failed to start bean 'eurekaAutoServiceRegistration'
相關(guān)文章
Spring boot + mybatis + orcale實(shí)現(xiàn)步驟實(shí)例代碼講解
這篇文章主要介紹了Spring boot + mybatis + orcale的實(shí)現(xiàn)步驟實(shí)例代碼講解,需要的朋友可以參考下2017-12-12string boot 與 自定義interceptor的實(shí)例講解
下面小編就為大家分享一篇string boot 與 自定義interceptor的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-12-12springboot項(xiàng)目整合注冊(cè)功能模塊開發(fā)實(shí)戰(zhàn)
這篇文章主要介紹了springboot項(xiàng)目整合注冊(cè)功能模塊開發(fā)實(shí)戰(zhàn),在用戶的注冊(cè)是首先需要查詢當(dāng)前的用戶名是否存在,如果存在則不能進(jìn)行注冊(cè),相當(dāng)于一個(gè)查詢語句,本文通過實(shí)例代碼詳細(xì)講解,需要的朋友可以參考下2022-11-11Java中綴表達(dá)式轉(zhuǎn)后綴表達(dá)式實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Java中綴表達(dá)式轉(zhuǎn)后綴表達(dá)式實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Java中綴表達(dá)式轉(zhuǎn)換成后綴表達(dá)式的相關(guān)算法原理與具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-03-03springMvc異步的DeferredResult long?polling應(yīng)用示例解析
這篇文章主要為大家介紹了springMvc中DeferredResult的long?polling應(yīng)用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03Mybatis-plus通過添加攔截器實(shí)現(xiàn)簡(jiǎn)單數(shù)據(jù)權(quán)限
系統(tǒng)需要根據(jù)用戶所屬的公司,來做一下數(shù)據(jù)權(quán)限控制,具體一點(diǎn),就是通過表中的company_id進(jìn)行權(quán)限控制,項(xiàng)目使用的是mybatis-plus,所以通過添加攔截器的方式,修改查詢sql,實(shí)現(xiàn)數(shù)據(jù)權(quán)限,本文就通過代碼給大家詳細(xì)的講解一下,需要的朋友可以參考下2023-08-08Java基于二維數(shù)組實(shí)現(xiàn)的數(shù)獨(dú)問題示例
這篇文章主要介紹了Java基于二維數(shù)組實(shí)現(xiàn)的數(shù)獨(dú)問題,涉及java針對(duì)數(shù)組的遍歷、計(jì)算、轉(zhuǎn)換等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01spring boot定時(shí)器實(shí)現(xiàn)定時(shí)同步數(shù)據(jù)的操作步驟
在Java中,@Scheduled注解是用于指定定時(shí)任務(wù)的執(zhí)行規(guī)則的,這篇文章給大家介紹spring boot定時(shí)器實(shí)現(xiàn)定時(shí)同步數(shù)據(jù)的操作步驟,感興趣的朋友一起看看吧2023-12-12