springcloud-gateway整合jwt+jcasbin实现权限控制的详细过程

作者:陈扬天 时间:2023-11-20 12:57:09 

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_来区分,数据库测试数据如下:

springcloud-gateway整合jwt+jcasbin实现权限控制的详细过程

意思是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;
   }
}

来源:https://www.cnblogs.com/fengwenzhee/p/17095646.html

标签:springcloud,gateway,jwt,jcasbin,权限控制
0
投稿

猜你喜欢

  • kotlin java 混合代码 maven 打包实现

    2023-04-09 13:33:41
  • Hibernate环境搭建与配置方法(Hello world配置文件版)

    2022-07-31 06:26:04
  • C#中的尾递归与Continuation详解

    2021-12-05 16:35:15
  • unity scrollRect实现按页码翻页效果

    2021-10-11 11:52:43
  • C/C++ 左移<<, 右移>>的作用及说明

    2021-12-30 01:42:59
  • 清除aspx页面缓存的程序实现方法

    2021-10-28 06:16:57
  • 解析maven的用法和几个常用的命令(推荐)

    2022-04-16 23:31:15
  • Mybatis selectKey 如何返回新增用户的id值

    2022-07-14 05:10:38
  • HashMap工作原理_动力节点Java学院整理

    2023-06-20 20:33:15
  • C#控件picturebox实现画图功能

    2022-03-07 18:36:43
  • Intellij IDEA如何去掉@Autowired 注入警告的方法

    2023-02-13 04:34:49
  • 详谈Java中net.sf.json包关于JSON与对象互转的坑

    2023-03-02 12:38:31
  • Android 中在有序广播中添加自定义权限的实例

    2021-08-10 05:09:35
  • c# 实现康威生命游戏(细胞自动机)的示例

    2022-01-08 04:36:22
  • C# 拼图魔方小游戏

    2023-10-27 20:41:22
  • C#中SQL Command的基本用法

    2023-10-04 06:01:32
  • Socket通信原理和实践

    2022-07-05 02:42:31
  • SpringBoot集成Tomcat服务架构配置

    2022-06-30 09:10:11
  • Android中volley封装实践记录

    2021-07-05 19:55:03
  • HttpServletResponse乱码问题_动力节点Java学院整理

    2021-10-18 17:48:51
  • asp之家 软件编程 m.aspxhome.com