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; } }
评论区