springcloud-gateway整合jwt+jcasbin实现权限控制

jcasbin简介:


jcasbin 是一个用 Java 语言打造的轻量级开源访问控制框架是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)
publicclass 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);
                    }));
        } elseif(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
    publicint getOrder() {
        return 0;
    }


    /**
     * 判断是否有权限
     */privateboolean hasPower( ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
       List<String> authorizationList =  headers.getOrEmpty("Authorization");
       if(authorizationList.size()==0){
           returnfalse;
       }else{
           try {
               Claims claims = JwtUtil.parseJWT(authorizationList.get(0));
               //判断token是否过期
               Date expireTime = claims.getExpiration();
               Date now = new Date();
               if (now.after(expireTime))
               {
                   returnfalse;
               }

                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){
               returnfalse;
           }
       }
    }

    /**
     * 网关拒绝,返回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));

    }


    publicboolean validateWhiteList(String requestPath) {
        for (String whiteList : configProperty.getWhiteList()) {
            if (requestPath.contains(whiteList) || requestPath.matches(whiteList)) {
                returntrue;
            }
        }
        returnfalse;
    }
}
网关中首先校验是否属于白名单,白名单可以写在application.yml中,通过实体类加载:
application.yml:
distribute:
   config:
     whiteList:
      - admin/login
      - admin/role/checkPower
ConfigProperty:
@Component
@ConfigurationProperties(prefix = "distribute.config")
@Data
publicclass 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")

publicclass 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

publicclass EnforcerFactory implements InitializingBean {

privatestatic Enforcer enforcer;

@Autowired

private EnforcerConfigProperties enforcerConfigProperties;

@Autowired

private DataSource dataSource;

@Override

publicvoid 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. }

publicstatic Enforcer getEnforcer(){

return enforcer;

}

}

@Configuration

@ConfigurationProperties(prefix = "org.jcasbin")

@Data

publicclass 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")

publicclass 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 data1return CommonResult.ok(true);

} else {

// deny the request, show an errorreturn 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

publicclass PermissionEntity implements Serializable {

private Integer type; //操作对象是用户还是角色private List<Policy> policyList;

}

Policy:

@Data

publicclass 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对应方法:

publicstatic 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());

}

}elseif(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);

}

publicstatic CommonResult batchAddRoleForUser(PermissionEntity permissionEntity) {

for (Policy policy:permissionEntity.getPolicyList()){

enforcer.addRoleForUser("user_"+policy.getSub(),"role_"+policy.getObj());

}

return CommonResult.ok(true);

}

publicstatic CommonResult batchDeleteRole(PermissionEntity permissionEntity) {

for (Policy policy:permissionEntity.getPolicyList()){

enforcer.deleteRole("role_"+policy.getSub());

}

return CommonResult.ok(true);

}

publicstatic CommonResult batchDeleteRoleForUser(PermissionEntity permissionEntity) {

for (Policy policy:permissionEntity.getPolicyList()){

enforcer.deleteRoleForUser("user_"+policy.getSub(),"role_"+policy.getObj());

}

return CommonResult.ok(true);

}

publicstatic 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());

}

}elseif(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);

}

publicstatic 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 datareturn CommonResult.ok(true);

} else {

// deny the request, show an errorreturn 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

publicinterface 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

publicclass 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.gatewaypublicclass GatewayApplication {

publicstaticvoid 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

publicclass FeignConfig {

@Bean

@ConditionalOnMissingBean

public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {

returnnew HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));

}

}

最后自行设置异常信息:


没有权限:
{
    "code": 401,
    "data": "",
    "message": "没有被授权或者授权已经失效",
    "success": false
}
鉴权成功:
{
    "data": [
        "管理员"
    ],
    "success": true,
    "code": 200,
    "message": "请求已经成功处理"
}
jwt工具类:

@Component
publicclass JwtUtil {

    //加密 解密时的密钥 用来生成keypublicstaticfinal String JWT_KEY = "IT1995";

    /**
     * 生成加密后的秘钥 secretKey
     * @return*/publicstatic SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


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

    publicstatic Claims parseJWT(String jwt){

        SecretKey key = generalKey();  //签名秘钥,和生成的签名的秘钥一模一样
        Claims claims = Jwts.parser()  //得到DefaultJwtParser
                .setSigningKey(key)         //设置签名的秘钥
                .parseClaimsJws(jwt).getBody();//设置需要解析的jwtreturn claims;
    }

    publicstaticvoid 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
publicclass CommonResult {
    private Object data;
    privateboolean success;
    private Integer code;
    private String message;
    //私有化,防止newprivate CommonResult() {}

    //成功publicstatic CommonResult ok(Object data,  HttpStatusCode statusCode) {
        returnnew CommonResult(data,true,statusCode.code,statusCode.zhMessage);  //code 也可以使用字典管理    }

    //成功返回 重载 message没有特别要求publicstatic CommonResult ok(Object data) {
        return CommonResult.ok(data, HttpStatusCode.OK); //message 也可以使用字典管理    }

    // 失败publicstatic CommonResult error( HttpStatusCode statusCode) {
        returnnew CommonResult("",false, statusCode.code, statusCode.zhMessage);
    }
}
package com.distribute.common;

import lombok.Data;

publicenum 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;
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
SpringCloud是一个基于Spring Boot的开源微服务框架。SpringCloud Gateway是SpringCloud生态中的一个组件,提供了一种基于路由的API网关解决方案。JWT是JSON WEB Token的缩写,是一种用于身份认证和授权的开放标准。OAuth2是一种授权框架,用于向用户授权第三方应用访问他们的资源。 在微服务架构中,每个服务都是独立的,网关作为服务的入口,可以实现对外的请求过滤和路由。SpringCloud Gateway使用HttpClient进行内部请求的调度和路由。同时,它还提供了一些高阶的路由和过滤功能,如重定向、URL重写、限流、熔断、重试等。 JWT是一种轻量级的认证方案,通过在HTTP请求中添加一个JSON WEB Token,实现对用户进行身份认证和授权。JWT的使用极大地简化了认证过程,前后端可以通过JWT判断用户的身份和权限。 OAuth2为开发者提供了一种授权框架,可以授权第三方应用获取用户访问他们的资源。OAuth2支持多种授权类型,如授权码模式、密码模式、客户端模式和隐式模式。使用OAuth2,可以更好地保护用户的隐私和安全。 综上所述,SpringCloud Gateway、JWT和OAuth2都是现代化的解决方案,对于设计和开发微服务架构的应用程序来说,它们都是必不可少的组件。有了它们,开发人员可以更好的搭建分布式架构,确保数据安全性、隐私安全性和服务的可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盈梓的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值