六,RBAC简介

article/2025/9/16 9:04:37

六,RBAC

RBAC(基于角色的权限控制 role base access control)是一种设计模式,是用来设计和管理权限相关数据的一种模型
RBAC权限数据的管理,都是重复的CRUD的操作,这里我们就不再重复的从0到1开发,我们只是把模块中需要注意的逻辑和代码讲一下,重复代码不再复述
RBAC模式:基于角色的访问控制,表结构如下:
在这里插入图片描述

6.2 需求分析

6.2.1 用户管理

用户列表
在这里插入图片描述
用户添加修改
在这里插入图片描述

6.2.2 角色管理

角色列表
在这里插入图片描述
角色修改
在这里插入图片描述

6.2.3 菜单管理

菜单列表
在这里插入图片描述
菜单修改
在这里插入图片描述
菜单不同于单表管理,是一个树状表结构管理,这里我们重点关注菜单(树状表结构的数据)的增删改查

6.3 导入权限数据管理工程

这里我们直接导入素材中的权限管理工程,因为业务比较复杂,不再从0到1开发,大家重点关注。重点模块的业务逻辑实现即可

6.4 实体类

package com.lxs.legou.security.po;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.lxs.legou.core.po.BaseEntity;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;import java.util.Date;@Data
@TableName("user_")
public class User extends BaseEntity {@TableField("user_name_")private String userName; //登录名@TableField("real_name_")private String realName; //真实姓名@TableField("password_")private String password;@TableField("salt")private String salt; //加密密码的盐@TableField("sex_")private String sex;@TableField("tel_")private String tel;@TableField("email_")private String email;@TableField("desc_")private String desc;@TableField("lock_")private Boolean lock; //是否锁定@TableField("birthday_")@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd")@DateTimeFormat(pattern="yyyy-MM-dd")private Date birthday;@TableField("principal_")private Boolean principal; //是否为部门负责人,用于"常用语:直接上级"@TableField("dept_id_")private Long deptId; //部门@TableField("post_id_")private Long postId; //岗位@TableField(exist = false)private String deptName; //部门名称,用于列表显示@TableField(exist = false)private Long[] roleIds; //瞬时属性,用户的角色列表,如:[1,3,4,5]@TableField(exist = false)private String postName; //部门名称,用于列表显示
}
package com.lxs.legou.security.po;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lxs.legou.core.po.BaseEntity;
import lombok.Data;/*** @Title:* @Description:** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
@Data
@TableName("role_")
public class Role extends BaseEntity {@TableField("name_")private String name;@TableField("title_")private String title;@TableField("desc_")private String desc;@TableField(exist = false)private Long[] userIds; //瞬时属性,角色的用户列表,如:[1,3,4,5]}
package com.lxs.legou.security.po;import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.lxs.legou.core.po.BaseTreeEntity;
import lombok.Data;/*** @file Menu.java* @Copyright (C) http://www.lxs.com* @author lxs* @email lxosng77@163.com* @date 2018/7/13*/
@Data
@TableName("menu_")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Menu extends BaseTreeEntity {@TableField("path_")private String path;@TableField("name_")private String name;@TableField("component_")private String component;@TableField("hide_in_menu_")private Boolean hideInMenu = false; //设为true后在左侧菜单不会显示该页面选项@TableField("not_cache_")private Boolean notCache = false; //设为true后页面不会缓存@TableField("icon_")private String icon; //该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_'@TableField(exist = false)private Long roleId; //查询条件,拥有查询角色的菜单@TableField(exist = false)private Long userId; //查询条件,拥有查询用户的菜单@TableField(exist = false)private boolean selected; //角色选择菜单,选中角色已有的菜单
}

这里需要重点注意Menu实体类,因为是树状结构的数据需要继承BaseTreeEntity,通过
parent_id进行自身的关联

6.5 DAO和映射文件

参考具体导入的代码,实现具体用户角色,菜单的CRUD操作,因为代码较多这里不再转帖

6.6 Service

package com.lxs.legou.security.service;import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;import java.util.List;/*** @Title:* @Description: ** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
public interface IUserService extends ICrudService<User> {/*** 根据用户id查询角色* @param id* @return*/public List<Role> selectRoleByUser(Long id);/*** 根据用户名,查询用户个数* @param userName* @return*/public Integer findCountByUserName(String userName);/*** 根据用户名查询用户* @param userName* @return*/public User getUserByUserName(String userName);}
package com.lxs.legou.security.service;import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;import java.util.List;/*** @Title:* @Description: ** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
public interface IRoleService extends ICrudService<Role> {/*** 查询角色的用户* @param id* @return*/public List<User> selectUserByRole(Long id);}
package com.lxs.legou.security.service;import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.security.po.Menu;
import org.springframework.stereotype.Service;import java.util.List;/*** @Title:* @Description:** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
@Service
public interface IMenuService extends ICrudService<Menu> {/*** 查询用户的菜单* @param userId* @return*/public List<Menu> listByUser(Long userId);/*** 查询所有菜单,选中角色已有的菜单* @param roleId* @return*/public List<Menu> listChecked(Long roleId) ;/*** 关联角色和菜单* @param roleId* @param menuIds*/public void doAssignMenu2Role(Long roleId, Long[] menuIds);}

6.7 Controller

package com.lxs.legou.security.controller;import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.core.po.ResponseBean;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;
import com.lxs.legou.security.service.IUserService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;import java.util.List;/*** @Title:* @Description: ** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
@RestController
@RequestMapping(value = "/user")
public class UserController extends BaseController<IUserService, User> {@Autowiredprivate RestTemplate restTemplate;@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}@ApiOperation("通过登录获得用户")@GetMapping("/get/{userName}")public User getByUserName(@PathVariable("userName") String userName) {return service.getUserByUserName(userName);}@ApiOperation("通过用户ID获得角色")@GetMapping("/select-roles/{id}")public List<Role> selectRolesByUserId(@PathVariable("id") Long id) {return service.selectRoleByUser(id);}/*** 验证用户名是否存在*/@ApiOperation("验证用户名是否存在")@PostMapping("/validate-name/{userName}")public String validUserName(@PathVariable String userName,  Long id) {long rowCount = service.findCountByUserName(userName);//修改时=原来的用户名if (id != null) {User user = service.getById(id);if (null != userName && userName.equals(user.getUserName())) {return "{\"success\": true}";}}if (rowCount > 0) {return "{\"success\": false}";} else {return "{\"success\": true}";}}/*** 锁定用户*/@GetMapping("/lock/{id}")@ApiOperation("锁定账户")public ResponseBean lock(@PathVariable Long id) throws Exception {ResponseBean rm = new ResponseBean();try {User u = service.getById(id);User user = new User();user.setId(id);if (null != u.getLock() && u.getLock()) {rm.setMsg("用户已解锁");user.setLock(false);} else {rm.setMsg("用户已锁定");user.setLock(true);}service.updateById(user);} catch (Exception e) {e.printStackTrace();rm.setSuccess(false);rm.setMsg("保存失败");}return rm;}@Overridepublic void afterEdit(User domain) {//生成角色列表, 如:1,3,4List<Role> roles = service.selectRoleByUser(domain.getId());Long[] ids = new Long[roles.size()];for (int i=0; i< roles.size(); i++) {ids[i] = roles.get(i).getId();}domain.setRoleIds(ids);}}
package com.lxs.legou.security.controller;import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;
import com.lxs.legou.security.service.IRoleService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.List;/*** @Title:* @Description: ** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
@RestController
@RequestMapping("/role")
public class RoleController extends BaseController<IRoleService, Role> {@Overridepublic void afterEdit(Role entity) {//生成用户列表, 如:1,3,4List<User> users = service.selectUserByRole(entity.getId());Long[] ids = new Long[users.size()];for (int i=0; i< users.size(); i++) {ids[i] = users.get(i).getId();}entity.setUserIds(ids);}}
package com.lxs.legou.security.controller;import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.core.po.ResponseBean;
import com.lxs.legou.security.po.Menu;
import com.lxs.legou.security.service.IMenuService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;import java.util.List;/*** @Title:* @Description: ** @Copyright 2019 lxs - Powered By 雪松* @Author: lxs* @Date:  2019/10/9* @Version V1.0*/
@RestController
@RequestMapping("/menu")
public class MenuController extends BaseController<IMenuService, Menu> {/*** 查询菜单,选中角色已有的菜单* @param roleId* @return*/@ApiOperation(value = "查询菜单,选中角色已有的菜单", notes = "查询菜单,选中角色已有的菜单")@RequestMapping(value = "/list-role/{roleId}", method = {RequestMethod.GET, RequestMethod.POST})@ResponseBodypublic List<Menu> listMenuByRole(@PathVariable Long roleId) {return this.service.listChecked(roleId);}/*** 查询当前用户的菜单* @return*/@ApiOperation("查询当前用户的菜单")@GetMapping("/list-current-user")@ResponseBodypublic List<Menu> ListMenuByCurrentUser() { TODO: 2019/10/9 暂时mock,userId=15admin用户,后期整合权限框架return this.service.listByUser(15l);}/*** 给角色分配菜单* @param roleId* @param ids* @return*/@ApiOperation("给角色分配菜单")@PostMapping("/assign/{roleId}")@ResponseBodypublic ResponseBean assignMenu(@PathVariable Long roleId, @RequestParam Long[] ids) {ResponseBean rb = new ResponseBean();try {service.doAssignMenu2Role(roleId, ids);} catch (Exception e) {e.printStackTrace();rb.setSuccess(false);rb.setMsg("保存失败");}return rb;}}

6.8 网关配置

 id: security-service
uri: lb://security-service
predicates:- Path=/api/security/**

6.9 前端代码

6.9.1 菜单管理

因为菜单管理不同于单表操作,展示的树状结构的数据显示,这里关注下具体实现
在这里插入图片描述
在这里插入图片描述

6.9.2 用户角色管理

用户列表
在这里插入图片描述
用户修改
在这里插入图片描述
选择角色的组件

<template>
<Select :value="currentValue" @on-change="handleInput" multiple >
<Option v-for="item in roleList" :value="item.id" :key="item.id">{{
item.name }}</Option>
</Select>
</template>
<script>
import instance from '@/libs/api/index'
export default {
name: 'selectRoles',
data () {
return {
roleList: []
}
},
computed: {
currentValue: function () {
return this.value
}
},
props: ['value'], // 接收一个 value prop
methods: {
handleInput (value) {
this.$emit('input', value) // 触发 input 事件,并传入新值
}
},
mounted () {
instance.post(`/security/role/list`).then(response => {
this.roleList = response.data
}).catch(error => {
console.log(error)
})
}
}
</script>

具体角色管理和用户管理相似,这里不再复述
注意:前端代码比较复杂可以拷贝,做到会修改即可,有兴趣的同学可以拿出时间从0到1编码

六,组织机构管理

组织机构模块主要包括部门,职位,和字典的管理,其中部门跟菜单相似是树状结构数据,跟权限数据模块管理类似,这里时间问题,不再重复讲解,大家导入提供的组织机构管理工程即可,后续可以使用,能够修改即可
网关路由配置

- id: admin-service
uri: lb://admin-service
predicates:
- Path=/api/admin/**

6.1 表结构

在这里插入图片描述

6.2 功能介绍

岗位管理
在这里插入图片描述
在这里插入图片描述
部门管理
在这里插入图片描述
权限数据和组织机构数据管理,不涉及新的技术,但是业务逻辑比较复杂,这里我们导入即可,不再重复实现crud操作了,有时间大家根据标准代码,自己写一遍

6.3 代码实现

具体参考标准代码,因为跟之前业务逻辑基本相同,这里不再重复。

六,单点登录(SSO)分析

6.1.什么是SSO

在企业发展初期,企业使用的系统很少,通常一个或者两个,每个系统都有自己的登录模块,运营人员每天用自己的账号登录,很方便。但随着企业的发展,用到的系统随之增多,运营人员在操作不同的系统时,需要多次登录,而且每个系统的账号都不一样,这对于运营人员来说,很不方便。于是,就想到是不是可以在一个系统登录,其他系统就不用登录了呢?这就是单点登录要解决的问题。单点登录英文全称Single Sign On,简称就是SSO。
它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

6.2.普通认证机制

在这里插入图片描述
如上图所示,我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成登录认证。这时,我们在这个用户的session中标记登录状态为yes(已登录),同时在浏览器(Browser)中写入Cookie,这个Cookie是这个用户的唯一标识。下次我们再访问这个应用的时候,请求中会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。
如果不做特殊配置,这个Cookie的名字叫做jsessionid,值在服务端(server)是唯一的。

6.3.同域名下的SSO

一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。
我们只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.a.com中登录了,其实是在sso.a.com的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.a.com下写入了Cookie。那么我们怎么才能让app1.a.com和app2.a.com登录呢?这里有两个问题:
   Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。
   sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的
在这里插入图片描述
那么我们如何解决这两个问题呢?针对第一个问题,sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。
Cookie的问题解决了,我们再来看看session的问题。我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:SpringSession。这样第2个问题也解决了。

6.4.基于token的认证

在这里插入图片描述
最近几年由于单页app、web APIs等的兴起,基于token的身份验证开始流行。当我们谈到利用token进行认证,我们一般说的就是利用JSON Web Tokens(JWTs)进行认证。虽然有不同的方式来实现token,事实上,JWTs 已成为标准。因此在本文中将互换token与JWTs。
基于token的身份验证是无状态的,服务器不需要记录哪些用户已经登录或者哪些JWTs已经处理。每个发送到服务器的请求都会带上一个token,服务器利用这个token检查确认请求的真实性。
这里可以把token理解成一张演唱会的门票。服务器(演唱会主办方)每次只需要检查你这张门票的有效性,不需要知道你这张门票是在哪里买的,从谁买的,什么时候买的等等。不同等级的门票可以坐的位置不同,同样的,权限不同的用户可以进行的操作也不同。

六, JWT讲解

6.1 需求分析

我们之前已经搭建过了网关,使用网关在网关系统中比较适合进行权限校验。
在这里插入图片描述
那么我们可以采用JWT的方式来实现鉴权校验。

6.2 什么是JWT

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

6.3 JWT的构成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

(1)头部(Header)

头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。

{"typ":"JWT","alg":"HS256"}

在头部指明了签名算法是HS256算法。 我们进行BASE64编码http://base64.xpcha.com/,编码后的字符串如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

小知识:Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。JDK 中提供了非常方便
的 BASE64Encoder 和 BASE64Decoder,用它们可以非常方便的完成基于 BASE64 的编码和解码。

(2)载荷(payload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
(1)标准中注册的声明(建议但不强制使用)

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

(2)公共的声明
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
(3)私有的声明
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
这个指的就是自定义的claim。比如下面面结构举例中的admin和name都属于自定的claim。这些claim跟JWT标准规定的claim区别在于:JWT规定的claim,JWT的接收方在拿到JWT之后,都知道怎么对这些标准的claim进行验证(还不知道是否能够验证);而private claims不会验证,除非明确告诉接收方要对这些claim进行验证以及规则才行。
定义一个payload:

{"sub":"1234567890","name":"John Doe","admin":true}

然后将其进行base64加密,得到Jwt的第二部分。

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

(3)签证(signature)

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
   header (base64后的)
   payload (base64后的)
   secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

6.4 JJWT的介绍和使用

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。
官方文档:
https://github.com/jwtk/jjwt

6.4.1 创建TOKEN

(1)依赖引入
在gateway项目中的pom.xml中添加依赖:

<!--鉴权-->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version>
</dependency>

(2)创建测试
在gateway的/test/java下创建测试类,并设置测试方法

public class JwtTest {
/****
* 创建Jwt令牌
*/
@Test
public void testCreateJwt(){
JwtBuilder builder= Jwts.builder()
.setId("888") //设置唯一编号
.setSubject("小白") //设置主题 可以是JSON数据
.setIssuedAt(new Date()) //设置签发日期
.signWith(SignatureAlgorithm.HS256,"kaikeba");//设置签名 使用HS256
算法,并设置SecretKey(字符串)
//构建 并返回一个字符串
System.out.println( builder.compact() );
}
}

运行打印结果:
再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间。

6.4.2 TOKEN解析

我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

/***
* 解析Jwt令牌数据
*/
@Test
public void testParseJwt(){
String
compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiLllK_kuIDnmoTmoIfor4YiLCJpc3MiOiLpooH
lj5HogIUiLCJzdWIiOiLkuLvpopgiLCJleHAiOjE2MDIzMjIwMDEsIm15Y2l0eSI6ImJlaWppbmciLCJ
teWFkZHJlc3MiOiJjbiJ9.c7UxJaQVxulwVqvIZLP_8RxSTbKOyixcU6mgMFaYrw0";
Claims claims = Jwts.parser().
setSigningKey("kaikeba").
parseClaimsJws(compactJwt).
getBody();
System.out.println(claims);
}

运行打印效果:

{jti=888, sub=小白, iat=1562062287}

试着将token或签名秘钥篡改一下,会发现运行时就会报错,所以解析token也就是验证token.

6.4.3 设置过期时间

有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。

6.4.3.1 token过期设置在这里插入图片描述

解释:

.setExpiration(date)//用于设置过期时间 ,参数为Date类型数据

运行,打印效果如下:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE1NjIwNjI5MjUsImV4cCI6MTU2MjA2MjkyNX0._vs4METaPkCza52LuN02NGGWIIO7v51xt40DHY1U1Q

6.4.3.2 解析TOKEN

/***
* 解析Jwt令牌数据
*/
@Test
public void testParseJwt(){
String
compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4ODgiLCJzdWIiOiLlsI_nmb0iLCJpYXQiOjE
1NjIwNjI5MjUsImV4cCI6MTU2MjA2MjkyNX0._vs4METaPkCza52LuN0-
2NGGWIIO7v51xt40DHY1U1Q";
Claims claims = Jwts.parser().
setSigningKey("lxs").
parseClaimsJws(compactJwt).
getBody();
System.out.println(claims);
}

打印效果:
在这里插入图片描述
当前时间超过过期时间,则会报错。
6.4.4 自定义claims
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。
创建测试类,并设置测试方法:
创建token:
在这里插入图片描述
运行打印效果:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiLllK_kuIDnmoTmoIfor4YiLCJpc3MiOiLpooHlj5HogIUiLCJzdWIiOiLkuLvpopgiLCJleHAiOjE2MDIzMjIwMDEsIm15Y2l0eSI6ImJlaWppbmciLCJteWFkZHJlc3MiOiJjbiJ9.c7UxJaQVxulwVqvIZLP_8RxSTbKOyixcU6mgMFaYrw0

解析TOKEN:

/***
* 解析Jwt令牌数据
*/
@Test
public void testParseJwt(){
String
compactJwt="eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiLllK_kuIDnmoTmoIfor4YiLCJpc3MiOiLpooH
lj5HogIUiLCJzdWIiOiLkuLvpopgiLCJleHAiOjE2MDIzMjIwMDEsIm15Y2l0eSI6ImJlaWppbmciLCJ
teWFkZHJlc3MiOiJjbiJ9.c7UxJaQVxulwVqvIZLP_8RxSTbKOyixcU6mgMFaYrw0";
Claims claims = Jwts.parser().
setSigningKey("itcast").
parseClaimsJws(compactJwt).
getBody();
System.out.println(claims);
}

运行效果:
在这里插入图片描述

6.5 鉴权处理

6.5.1 思路分析

在这里插入图片描述

1.用户通过访问微服务网关调用微服务,同时携带头文件信息
2.在微服务网关这里进行拦截,拦截后获取用户要访问的路径
3.识别用户访问的路径是否需要登录,如果需要,识别用户的身份
是否能访问该路径[这里可以基于数据库设计一套权限]
4.如果需要权限访问,用户已经登录,则放行
5.如果需要权限访问,且用户未登录,则提示用户需要登录
6.用户通过网关访问用户微服务,进行登录验证
7.验证通过后,用户微服务会颁发一个令牌给网关,网关会将
用户信息封装到头文件中,并响应用户
8.用户下次访问,携带头文件中的令牌信息即可识别是否登录

6.5.2用户登录签发TOKEN

(1)生成令牌工具类

在gateway中创建类JwtUtil,主要辅助生成Jwt令牌信息,代码如下:
gateway/src/main/java/com/lxs/clound/util/JwtUtil.java

package com.lxs.clound.util;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
/*** @author* @version 1.0* @description 令牌工具类:辅助生成Jwt令牌信息* @createDate 2022/6/15 16:38**/
public class JwtUtil {//有效期为public static final Long JWT_TTL = 3600000L;// 60 * 60 *1000 一个小时//Jwt令牌信息public static final String JWT_KEY = "kaikeba";/*** 生成令牌* @param id* @param subject* @param ttlMillis* @return*/public static String createJWT(String id, String subject, Long ttlMillis) {//指定算法SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;//当前系统时间long nowMillis = System.currentTimeMillis();//令牌签发时间Date now = new Date(nowMillis);//如果令牌有效期为null,则默认设置有效期1小时if (ttlMillis == null) {ttlMillis = JwtUtil.JWT_TTL;}//令牌过期时间设置long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);//生成秘钥SecretKey secretKey = generalKey();//封装Jwt令牌信息JwtBuilder builder = Jwts.builder().setId(id) //唯一的ID.setSubject(subject) // 主题 可以是JSON数据.setIssuer("admin") // 签发者.setIssuedAt(now) // 签发时间.signWith(signatureAlgorithm, secretKey) // 签名算法以及密匙.setExpiration(expDate); // 设置过期时间return builder.compact();}/*** 生成加密 secretKey** @return*/public static SecretKey generalKey() {byte[] encodedKey =Base64.getEncoder().encode(JwtUtil.JWT_KEY.getBytes());SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length,"AES");return key;}/*** 解析令牌数据** @param jwt* @return* @throws Exception*/public static Claims parseJWT(String jwt) throws Exception {SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}public static void main(String[] args) {String jwt = JwtUtil.createJWT("weiyibiaoshi", "aaaaaa", null);System.out.println(jwt);try {Claims claims = JwtUtil.parseJWT(jwt);System.out.println(claims);} catch (Exception e) {e.printStackTrace();}}
}

gateway/src/main/java/com/lxs/clound/util/BCrypt.java
用于BCrypt加密解密的工具类(拷贝即可)

(2) 通过Feign调用用户微服务,实现具体登录

gateway/src/main/java/com/lxs/clound/client/UserClient.java

package com.lxs.clound.client;import com.lxs.legou.security.api.UserApi;
import com.lxs.legou.security.po.Role;
import com.lxs.legou.security.po.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;/*** @author* @version 1.0* @description Feign调用用户微服务,实现具体登录* @createDate 2022/6/15 17:05**/
@FeignClient(name = "security-service", fallback = UserClient.UserClientFallback.class)
public interface UserClient extends UserApi {@Component@RequestMapping("/fallback") //这个可以避免容器中requestMapping重复class UserClientFallback implements UserClient {private static final Logger LOGGER =LoggerFactory.getLogger(UserClientFallback.class);@Overridepublic User getByUserName(String userName) {LOGGER.info("异常发生,进入fallback方法");return null;}@Overridepublic List<Role> selectRolesByUserId(Long id) {LOGGER.info("异常发生,进入fallback方法");return null;}}
}

引入legou-security-instance依赖

  <dependency><!--引入权限微服务--><groupId>com.lxs</groupId><artifactId>legou-security-instance</artifactId><version>${project.version}</version><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></exclusion><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></exclusion></exclusions></dependency>

(3) 用户登录成功 则 签发TOKEN,修改登录的方法:

在这里插入图片描述
代码如下:

package com.lxs.clound.controller;import com.fasterxml.jackson.databind.ObjectMapper;
import com.lxs.clound.client.UserClient;
import com.lxs.clound.util.BCrypt;
import com.lxs.clound.util.JwtUtil;
import com.lxs.legou.security.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.*;/*** @author* @version 1.0* @description 用户登录-测试用途* @createDate 2022/6/15 17:26**/
@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserClient userClient;@Autowiredprivate ObjectMapper om;@RequestMapping("/login")public ResponseEntity login(String username,String password) throws Exception {//1.从数据库中查询用户名对应的用户的对象User user = userClient.getByUserName(username);if (user == null) {//2.判断用户是否为空 为空返回数据return new ResponseEntity("用户名密码错误", HttpStatus.UNAUTHORIZED);}//3如果不为空 判断 密码是否正确 正确则登录成功if(BCrypt.checkpw(password, user.getPassword())){//成功Map<String,Object> info = new HashMap<String,Object>();info.put("role","USER");info.put("success","SUCCESS");info.put("username",username);//1.生成令牌String jwt = JwtUtil.createJWT(UUID.randomUUID().toString(), om.writeValueAsString(info), null);return ResponseEntity.ok(jwt);}else{//失败return new ResponseEntity("用户名密码错误", HttpStatus.UNAUTHORIZED);}}
}

执行测试后的效果
在这里插入图片描述

6.5.3 自定义全局过滤器

创建 过滤器类,如图所示:
在这里插入图片描述
AuthorizeFilter代码如下:

package com.lxs.clound.filter;import com.lxs.clound.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 全局过滤器*/
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {//令牌头名字private static final String AUTHORIZE_TOKEN = "Authorization";/**** 全局过滤器* @param exchange* @param chain* @return*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//获取Request、Response对象ServerHttpRequest request = exchange.getRequest();ServerHttpResponse response = exchange.getResponse();//获取请求的URIString path = request.getURI().getPath();//如果是登录、goods等开放的微服务[这里的goods部分开放],则直接放行,这里不做完整演示,// 完整演示需要设计一套权限系统测试访问 http://localhost:8062/user/login?username=admin&password=admin ,效果如下:if (path.startsWith("/api/user/login") ||path.startsWith("/api/brand/search/")) {//放行Mono<Void> filter = chain.filter(exchange);return filter;}//获取头文件中的令牌信息String tokent = request.getHeaders().getFirst(AUTHORIZE_TOKEN);//如果头文件中没有,则从请求参数中获取if (StringUtils.isEmpty(tokent)) {tokent = request.getQueryParams().getFirst(AUTHORIZE_TOKEN);}//如果为空,则输出错误代码if (StringUtils.isEmpty(tokent)) {//设置方法不允许被访问,405错误代码response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);return response.setComplete();}//解析令牌数据try {Claims claims = JwtUtil.parseJWT(tokent);} catch (Exception e) {e.printStackTrace();//解析失败,响应401错误response.setStatusCode(HttpStatus.UNAUTHORIZED);return response.setComplete();}//放行return chain.filter(exchange);}/**** 过滤器执行顺序* @return*/@Overridepublic int getOrder() {return 0;}
}

测试访问 http://localhost:8062/api/item/brand/list ,效果如下:
在这里插入图片描述


http://chatgpt.dhexx.cn/article/fN2dyHC3.shtml

相关文章

RBAC简介

目录 RBAC简介RBAC0RBAC1RBAC2RBAC3 RBAC简介 RBAC是Role Based Access Control的英文缩写&#xff0c;意思是基于角色访问控制。 RBAC实际上就是针对产品去发掘需求时所用到的Who&#xff08;角色&#xff09;、What&#xff08;拥有什么资源&#xff09;、How&#xff08;有…

RBAC 权限

RBAC权限分析 RBAC 全称为基于角色的权限控制&#xff0c;本段将会从什么是RBAC&#xff0c;模型分类&#xff0c;什么是权限&#xff0c;用户组的使用&#xff0c;实例分析等几个方面阐述RBAC 什么是RBAC RBAC 全称为用户角色权限控制&#xff0c;通过角色关联用户&#xff…

RBAC模型

最近开始在找java项目&#xff0c;大部分时间都是跟着视频或者代码一步一步敲过来&#xff0c;但是对代码的理论层面还是有所欠缺&#xff0c;今天就来分享一个系统设计中的一个模型。不管是哪一个系统&#xff0c;都绕不开权限控制&#xff0c;因为现在的角色太多了&#xff0…

RBAC入门教程及实例演示

RBAC 一、RBAC的作用 在很多系统中&#xff0c;会要求不同的账户对应着不同的角色和权限。如教务管理系统&#xff0c;分为以下几种功能&#xff0c;不同的功能对应着不同的角色 如果要做到登录后根据账户的角色&#xff0c;给出相应的菜单&#xff0c;及规定当前角色只能做出…

RBAC简介(*)

一.RBAC是什么 1.RBAC模型概述 RBAC是Role Based Access Control的英文缩写&#xff0c;意思是 基于角色的访问控制。 RBAC实际上就是针对产品去挖掘需求时所用到的Who&#xff08;角色&#xff09;、What&#xff08;拥有什么资源&#xff09;、How&#xff08;有哪些操作&am…

什么是 RBAC 模型?

前言 RBAC&#xff08;Role-Based Access Control&#xff09;&#xff0c;基于角色的访问控制&#xff0c;现在主流的权限管理系统的权限设计都是 RBAC 模型&#xff0c;或者是 RBAC 模型的变形。 我们需要思考一个问题&#xff1a;为什么要做权限的管理&#xff1f; 我的理…

RBAC权限管理(详细)

RBAC权限设计思想 为了达成不同账号(员工、总裁)登录系统后看到不同页面&#xff0c;执行不同功能&#xff0c;RBAC(Role-Based Access control)权限模型&#xff0c;就是根据角色的权限&#xff0c;分配可视页面。 三个关键点: 用户:使用系统的人 角色&#xff1a;使用系统…

什么是RBAC

一、RBAC是什么 1、RBAC模型概述 RBAC模型&#xff08;Role-Based Access Control&#xff1a;基于角色的访问控制&#xff09;模型是20世纪90年代研究出来的一种新模型&#xff0c;但其实在20世纪70年代的多用户计算时期&#xff0c;这种思想就已经被提出来&#xff0c;直到…

最小生成树,秒懂!

&#x1f447;&#x1f447;关注后回复 “进群” &#xff0c;拉你进程序员交流群&#x1f447;&#x1f447; 作者丨bigsai 来源丨bigsai 前言 在数据结构与算法的图论中&#xff0c;(生成)最小生成树算法是一种常用并且和生活贴切比较近的一种算法。但是可能很多人对概念不是…

算法 - 最小生成树实现

算法能力是一个门槛&#xff0c;也是个有基础的门槛 无论你是iOS工程师&#xff0c;android工程师&#xff0c;java工程师&#xff0c;前端&#xff0c;后端还是全栈等等… 算法能力的强弱一方面在于思想&#xff0c;你是否有计算机思维抽象具体问题的能力 更重要的还在与基…

最小树形图(有向图的最小生成树)

我们知道&#xff0c;无向图的最小生成树的求法有Krusal和prime算法&#xff0c;一个是归点一个是归边&#xff0c;在具体实现上Krusal可以用并查集实现&#xff0c;难度不大。 这里稍微区别一下最短路径和最小生成树&#xff08;因为我又搞混了23333&#xff09; 最小生成树能…

Kruskal算法(最小生成树)

上篇Prim算法简要的讲解了最小生成树。也提到过Prim算法堆优化&#xff0c;但本蒟蒻并没有贴Prim &#xff08;堆优化的代码&#xff09;。至于为什么没有贴呢&#xff1f;上篇Prim算法blog末尾有说明。 好勒&#xff01;咱们接着讲Kruskal算法。这跟Prim算法有很大的…

最小生成树matlab求解

一、最小生成树 连通所有顶点且总路径最小修建连通7个城市的铁路网&#xff0c;可修建的路线和对应造价如图所示&#xff0c;如何修建使总费用最少&#xff1f; 问题分析&#xff1a; 连通7个城市&#xff1a;生成的图中&#xff0c;从任意顶点起步&#xff0c;沿着边一定可以…

最小生成树——贪心算法

文章目录 1.生成树和最小生成树1.1 问题的定义1.2 MST性质 2.普里姆算法&#xff08;Prim&#xff09;2.1 算法流程2.2 算法正确性证明2.3 算法实现2.4 时间复杂度2.5 测试代码 3.克鲁斯卡尔算法&#xff08;kruskal&#xff09;3.1 算法流程3.2 算法正确性证明3.3 算法实现 参…

C++ 最小生成树

目录&#xff1a; 前置知识 最小生成树 Prim 算法 kruskal 算法 前置知识&#xff1a; 连通图&#xff1a;在无向图中&#xff0c;若任意两个顶点 u 与 v 都有路径相通&#xff0c;则称该无向图为连通图。 强连通图&#xff1a;在有向图中&#xff0c;若任意两个顶点 u 与 …

最小生成树(C语言实现)

求这个网的最小生成树 /** 普里姆算法和克鲁斯卡尔算法求最小生成树* 采用邻接矩阵存储**/ #include<stdio.h>#define MAX_VERTEX_NUM 20 //图的定义 typedef struct {int vertexNum;int edgeNum;char vertex[MAX_VERTEX_NUM];int arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM]…

最小生成树(C语言)

最小生成树问题&#xff08;C语言&#xff09; 所谓一个 带权图 的最小生成树&#xff0c;就是原图中边的权值最小的生成树 &#xff0c;所谓最小是指边的权值之和小于或者等于其它生成树的边的权值之和。 kruskal 克鲁斯卡尔算法&#xff08;Kruskal&#xff09;是一种使用贪…

最小生成树kruskal算法

最小生成树kruskal算法 概述算法分析代码 概述 克鲁斯卡尔 ( K r u s k a l ) (Kruskal) (Kruskal)算法是求连通网的最小生成树的另一种方法。与普里姆 ( P r i m ) (Prim) (Prim)算法不同&#xff0c;它的时间复杂度为 O ( e l o g e ) O(eloge) O(eloge)(e为网中的边数)&…

最小生成树:

定义&#xff1a; 将图中的 n 结点用 n-1 条边连接起来&#xff0c;使其各个边的权值之和最小的生成树。 普里姆算法&#xff1a; 从点的角度出发。 1、start 数组的值表示起始点的下标&#xff0c;start 的下标代表目的顶点&#xff1b;lowcost 数组的下标代表目的顶点&…

图——最小生成树

图——最小生成树 1. 基本概念 在一个连通无向图G(V, E)中&#xff0c;对于其中的每条边(u,v)∈E&#xff0c;赋予其权重w(u, v)&#xff0c;则最小生成树问题就是要在G中找到一个连通图G中所有顶点的无环子集T⊆E&#xff0c;使得这个子集中所有边的权重之和w(T) ∑ ( u , …