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;
}
}

springcloud-gateway整合jwt+jcasbin实现权限控制的更多相关文章

  1. springboot + 注解 + 拦截器 + JWT 实现角色权限控制

    1.关于JWT,参考: (1)10分钟了解JSON Web令牌(JWT) (2)认识JWT (3)基于jwt的token验证 2.JWT的JAVA实现 Java中对JWT的支持可以考虑使用JJWT开源 ...

  2. ASP.NET Core 实战:基于 Jwt Token 的权限控制全揭露

    一.前言 在涉及到后端项目的开发中,如何实现对于用户权限的管控是需要我们首先考虑的,在实际开发过程中,我们可能会运用一些已经成熟的解决方案帮助我们实现这一功能,而在 Grapefruit.VuCore ...

  3. 轻松上手SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制实战

    前言 我们知道在项目开发中,后台开发权限认证是非常重要的,springboot 中常用熟悉的权限认证框架有,shiro,还有就是springboot 全家桶的 security当然他们各有各的好处,但 ...

  4. springcloud gateway整合sentinel

    1.引入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spri ...

  5. Spring Cloud实战: 基于Spring Cloud Gateway + vue-element-admin 实现的RBAC权限管理系统,实现网关对RESTful接口方法权限和自定义Vue指令对按钮权限的细粒度控制

    一. 前言 信我的哈,明天过年. 这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT ...

  6. Spring Cloud实战 | 第十一篇:Spring Cloud Gateway 网关实现对RESTful接口权限控制和按钮权限控制

    一. 前言 hi,大家好,这应该是农历年前的关于开源项目 的最后一篇文章了. 有来商城 是基于 Spring Cloud OAuth2 + Spring Cloud Gateway + JWT实现的统 ...

  7. Spring Cloud Data Flow整合Cloudfoundry UAA服务做权限控制

    我最新最全的文章都在南瓜慢说 www.pkslow.com,欢迎大家来喝茶! 1 前言 关于Spring Cloud Data Flow这里不多介绍,有兴趣可以看下面的文章.本文主要介绍如何整合Dat ...

  8. 【SpringBoot技术专题】「权限校验专区」Shiro整合JWT授权和认证实现

    本章介绍一下常用的认证框架Shiro结合springboot以及集合jwt快速带您开发完成一个认证框架机制. Maven配置依赖 <dependency> <groupId>o ...

  9. SpringCloud微服务实战——搭建企业级开发框架(二十三):Gateway+OAuth2+JWT实现微服务统一认证授权

      OAuth2是一个关于授权的开放标准,核心思路是通过各类认证手段(具体什么手段OAuth2不关心)认证用户身份,并颁发token(令牌),使得第三方应用可以使用该token(令牌)在限定时间.限定 ...

  10. springcloud+gateway微服务整合swagger

    单一的微服务集成swagger: maven: <dependency> <groupId>io.springfox</groupId> <artifactI ...

随机推荐

  1. JS 可编辑表格的实现(进阶)

    1.前言 在普通的可编辑表格的基础上,改进可编辑表格.数据来自外部的json(模拟服务端),通过json数据生成可编辑表格.根据实际情况,表格没有新增数据功能.表格的可编辑列,计算的列,每列的数据大小 ...

  2. vue 项目中,后端返回文件流,导出excel

    之前写过文件流导出excel,这次直接把上次的代码拿过来复制粘贴,但是导出的表格里面没有数据,只显示undefined. 这是之前的代码 // api接口页面 // excel导出接口 export ...

  3. QMetaObject::connectSlotsByName: No matching signal for xxx

    问题描述 这个问题是没有与 xxx 这个槽函数匹配的信号,但是我做了 QMetaObject::connectSlotsByName(this);, 自动连接.并且确保了函数名和信号名是没有错误的,还 ...

  4. Go 的windows安装与环境配置

    1.请前往go的官网下载安装包:https://golang.org/dl/ 安装你如果C盘够大比较土豪就一路next即可,在这里小编穷就安装到了D:\Program Files\Go 2.环境变量配 ...

  5. 【Java SE】Day03流程控制语句

    一.流程控制(顺序结构) 二.选择结构 1.多分支中case的穿透性 2.switch的括号可以是 基本/引用类型(String.enum枚举) 三.循环结构 for循环结束后内存消失,效率高 四.扩 ...

  6. 对于async和await的使用方式、作用效果不怎么理解 ?没关系,初步看这篇就够了

    结论 同步还是异步,区别如下: 同步:你使用 await 修饰符去调用一个异步(async)方法(是异步方法,不过是阻塞式的,可简单理解为同步): 异步:你获取异步方法返回的 Task,就是异步(后文 ...

  7. go-micro v3 rpc服务一次改造经历

    地址:https://github.com/go-micro/go-micro grpc-test-demo:https://gitee.com/jn-shao/go-gmicro-rpc-test. ...

  8. Redis的数据持久化

    介绍 Redis 的数据持久化方案 Redis 的数据持久化主要有两大机制,AOF 日志和 RDB 快照. AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态. RDB 持久化 ...

  9. 二阶段目标检测网络-Cascade RCNN 详解

    摘要 1,介绍 1.1,Faster RCNN 回顾 1.2,mismatch 问题 2,实验分析 2.1,改变IoU阈值对Detector性能的影响 2.2,提高IoU阈值的影响 2.3,和Iter ...

  10. Django AttributeError: 'BugDeserializer' object has no attribute '_meta'

    BugDeserializer 对象中没有 '_meta' 属性,定位到调用BugDeserializer位置, 用于序列化时,将模型类对象传入instance参数 在update时,数据传入有误,更 ...