jcasbin簡介:
jcasbin 是一個用 Java 語言打造的輕量級開源訪問控制框架https://github.com/casbin/jcasbin,是casbin的Java語言版本。目前在 GitHub 開源。jcasbin 採用了元模型的設計思想,支援多種經典的訪問控制方案,如基於角色的訪問控制 RBAC、基於屬性的訪問控制 ABAC 等。
jcasbin 的主要特性包括:
1.支援自定義請求的格式,預設的請求格式為{subject, object, action};
2.具有訪問控制模型 model 和策略 policy 兩個核心概念;
3.支援 RBAC 中的多層角色繼承,不止主體可以有角色,資源也可以具有角色;
4.支援超級使用者,如 root 或 Administrator,超級使用者可以不受授權策略的約束訪問任意資源;
5.支援多種內建的運算子,如 keyMatch,方便對路徑式的資源進行管理,如 /foo/bar 可以對映到 /foo*
jcasbin 不做的事情:
1.身份認證 authentication (即驗證使用者的使用者名稱、密碼),jcasbin 只負責訪問控制。應該有其他專門的元件負責身份認證,然後由 jcasbin 進行訪問控制,二者是相互配合的關係;
2.管理使用者列表或角色列表。jcasbin 認為由專案自身來管理使用者、角色列表更為合適,jcasbin 假設所有策略和請求中出現的使用者、角色、資源都是合法有效的。
專案架構:
基於springboot+springcloud+nacos的簡單分散式專案,專案互動採用openFeign框架,單獨提取出來成為一個獨立的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>
<!--鑑權-->
<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專案:
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相關核心程式碼:
註冊中心採用nacos,關於nacos的使用可以自行學習,不是本文關鍵。
閘道器採用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 獲得請求頭 ,獲得token值 HttpHeaders headers = request.getHeaders(); //判斷白名單和是否有許可權 if (validateWhiteList(path)) { return chain //繼續呼叫filter .filter(exchange) //filter的後置處理 .then(Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); HttpStatus statusCode = response.getStatusCode(); log.info("請求路徑:{},遠端IP地址:{},響應碼:{}", path, remoteAddress, statusCode); })); } else if(hasPower(request)){ return chain //繼續呼叫filter .filter(exchange) //filter的後置處理 .then(Mono.fromRunnable(() -> { ServerHttpResponse response = exchange.getResponse(); HttpStatus statusCode = response.getStatusCode(); log.info("請求路徑:{},遠端IP地址:{},響應碼:{}", path, remoteAddress, statusCode); })); }else { return noPower(exchange); } } @Override public int getOrder() { return 0; } /** * 判斷是否有許可權 */ 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; } } } /** * 閘道器拒絕,返回Result * * @param */ private Mono<Void> noPower(ServerWebExchange serverWebExchange) { // 許可權不夠攔截 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); //指定編碼,否則在瀏覽器中會中文亂碼 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; } }
閘道器中首先校驗是否屬於白名單,白名單可以寫在application.yml中,透過實體類載入:
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呼叫admin-user專案中的鑑權方法進行鑑權,關於admin-user專案以及feign的使用,在之後會提到,GlobalFilter中涉及的jwt工具類,文末會給出。
admin-user專案:
pom檔案:
<dependencies> <dependency> <groupId>com.distribute</groupId> <artifactId>feign</artifactId> </dependency> <!--資料庫驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!--資料庫連線池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <!--jdbc連線資料庫--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</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> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
登入控制器(測試):
/** * @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可以從檔案載入角色許可權資訊,此處已整合成從資料庫載入角色許可權資訊,為此,需要為jcasbin配置資料來源(為了方便直接使用專案中的資料庫,實際生產環境可以分開)以及模型檔案路徑:
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 { //從資料庫讀取策略 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; }
此後所有對jcasbin的操作都基於唯一例項enforcer,此時就可以進行業務上的新增許可權、角色、鑑權等的開發了。
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 <批次新增 使用者/角色 的許可權> *@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 <批次刪除 使用者/角色 的許可權> *@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 <批次刪除角色及其涉及到的使用者與角色關係> *@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")){ //超級管理員直接放行 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"); 從本地檔案載入許可權資訊 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角色控制器中已經寫好了一些方法,後續可以根據需要自行新增,入參實體我簡單封裝了一下,然後迴圈進行批次操作:
PermissionEntity:
@Data public class PermissionEntity implements Serializable { private Integer type; //操作物件是使用者還是角色 private List<Policy> policyList; }
Policy:
@Data public class Policy implements Serializable { /**想要訪問資源的使用者 或者角色*/ private String sub; /**將要訪問的資源,可以使用 * 作為萬用字元,例如/user/* */ private String obj; /**使用者對資源執行的操作。HTTP方法,GET、POST、PUT、DELETE等,可以使用 * 作為萬用字元*/ private String act; /** * * @param sub 想要訪問資源的使用者 或者角色 * @param obj 將要訪問的資源,可以使用 * 作為萬用字元,例如/user/* * @param act 使用者對資源執行的操作。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對應方法:
public static CommonResult batchAddPermission(PermissionEntity permissionEntity) { if(permissionEntity.getType()==null){ return CommonResult.error(HttpStatusCode.OPERATION_TYPE_REQUIRED); } if(permissionEntity.getType()==1){ //操作物件為使用者 for (Policy policy:permissionEntity.getPolicyList()){ enforcer.addPermissionForUser("user_"+policy.getSub(),policy.getObj(),policy.getAct()); } }else if(permissionEntity.getType()==2){ //操作物件為角色 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){ //操作物件為使用者 for (Policy policy:permissionEntity.getPolicyList()){ enforcer.deletePermissionForUser("user_"+policy.getSub(),policy.getObj(),policy.getAct()); } }else if(permissionEntity.getType()==2){ //操作物件為角色 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中對許可權的把控是基於subject的,所以無法區分許可權是使用者還是角色的,在這裡用字首是user_還是role_來區分,資料庫測試資料如下:
意思是role_管理員角色下有兩個許可權,分別是/c/main/getUser POST,和/c/main/deleteUser DELETE,v1欄位可以視為資源,v2為請求動作,
user_fengwenzhe使用者具有role_管理員的角色,鑑權時可以如下進行:
String path = request.getPath().pathWithinApplication().value(); String method = request.getMethodValue(); Policy checkPower = new Policy(userName,path,method); CommonResult result = adminUserInterfaceFeign.checkPower(checkPower);
比如此時我傳入userName=fengwenzhe,path=/c/main/getUser method=POST,就可以鑑權成功,因為有字首存在,程式碼中自行補足'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專案:
只定義feign相關介面與實現類:
/** * @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 <批次新增 使用者/角色 的許可權> *@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啟動類加入feign相關注釋:
@SpringBootApplication @ComponentScan(basePackages = {"com.distribute"}) @EnableFeignClients(basePackages = "com.distribute") //因為feign介面定義的包與專案不同級 專案預設掃描com.distribute.gateway public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
此時啟動gateway透過feign呼叫admin-user專案中的方法依然還是報錯,需要增加如下配置類:
/** *@Description <手動注入Bean Spring Cloud Gateway是基於WebFlux的,是ReactiveWeb,所以HttpMessageConverters不會自動注入。如果不注入,springcloudGateway呼叫feign時會報錯 * 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())); } }
最後自行設定異常資訊:
沒有許可權:
{ "code": 401, "data": "", "message": "沒有被授權或者授權已經失效", "success": false }
鑑權成功:
{ "data": [ "管理員" ], "success": true, "code": 200, "message": "請求已經成功處理" }
jwt工具類:
@Component public class JwtUtil { //加密 解密時的金鑰 用來生成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; //指定簽名的時候使用的簽名演算法,也就是header那部分,jwt已經將這部分內容封裝好了。 long nowMillis = System.currentTimeMillis();//生成JWT的時間 Date now = new Date(nowMillis); SecretKey key = generalKey();//生成簽名的時候使用的秘鑰secret,這個方法本地封裝了的,一般可以從本地配置檔案中讀取,切記這個秘鑰不能外露哦。它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發jwt了。 JwtBuilder builder = Jwts.builder() //這裡其實就是new一個JwtBuilder,設定jwt的body // .setClaims(claims) //如果有私有宣告,一定要先設定這個自己建立的私有的宣告,這個是給builder的claim賦值,一旦寫在標準的宣告賦值之後,就是覆蓋了那些標準的宣告的 .setId(id) //設定jti(JWT ID):是JWT的唯一標識,根據業務需要,這個可以設定為一個不重複的值,主要用來作為一次性token,從而回避重放攻擊。 .setIssuedAt(now) //iat: jwt的簽發時間 .setSubject(subject) //sub(Subject):代表這個JWT的主體,即它的所有人,這個是一個json格式的字串,可以存放什麼userid,roldid之類的,作為什麼使用者的唯一標誌。 .signWith(signatureAlgorithm, key);//設定簽名使用的簽名演算法和簽名使用的秘鑰 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); //設定過期時間 } return builder.compact(); //就開始壓縮為xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx這樣的jwt } public static Claims parseJWT(String jwt){ SecretKey key = generalKey(); //簽名秘鑰,和生成的簽名的秘鑰一模一樣 Claims claims = Jwts.parser() //得到DefaultJwtParser .setSigningKey(key) //設定簽名的秘鑰 .parseClaimsJws(jwt).getBody();//設定需要解析的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()); } }
統一結果返回類:
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狀態碼列舉所有狀態碼註解 */ USERNAME_PASSWORD_DENY(1000, "username password deny", "使用者名稱或密碼錯誤"), OK(200, "OK", "請求已經成功處理"), OPERATION_TYPE_ERROR(512, "", "操作型別不正確"); //錯誤碼 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; } }