环境:springboot+vue前后端分离
工具:码云+vscode+idea

一句话:
正向代理—卖票的黄牛,反向代理—租房中介
什么是跨域?跨域就是前端url访问后跟后端不一样。
如前端访问http://xxxxxx/login,但是后端接口提供的地址是http://xxxxx/api/login
基于最简单的登录界面示例

主要处理跨域问题
import Vue from 'vue'
import App from './App'
import router from './router'
// import list from './axios';等价于var list = require('./axios');
// 设置反向代理,前端请求默认发送到 http://localhost:8443/api
var axios = require('axios')
axios.defaults.baseURL = 'http://localhost:8443/api'
// 全局注册,之后可在其他组件中通过 this.$axios 发送数据
Vue.prototype.$axios = axios
// 取消生产提示
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
router,
render: h => h(App)
}).$mount('#app')

假设前端登录页面访问地址为http://localhost:8080/#/login,那么经过上面的跨域后点击登录访问后端的实际地址为http://localhost:8443/api/login
axios json数据可以结合这个注解封装对象,@RequestBody会自动调用对象的set方法进行封装

总的步骤:
前端:vue+跨域
后端:数据库+springboot+spring jpa规范
首先在vue ui界面引入插件添加elementUI
代码引用
// main.js
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
大家如果留心观察就会发现,之前我们做的页面的 URL 里有一个 # 号,这个 # 号有什么含义呢?
假设在 html 中有这么一段代码:
在互联网流量如此庞大的今天,我们需要想办法减轻后端服务器的压力,利用 AJAX,我们可以不重载页面就刷新数据,如果再加上 # 号的特性(即改变 URL 却不请求后端),我们就可以在前端实现页面的整体变化,而不用每次都去请求后端。
为了实现前端路由,我们可以监听 # 号后面内容的变化(hashChange),从而动态改变页面内容。URL 的 # 号后面的地址被称为 hash ,估计是哪个大神拍脑袋想的,不用深究。这种实现方式我们称之为 Hash 模式,是非常典型的前端路由方式。
另一种常用的方式被称为 History 模式,这种方式使用了 History API,History API 顾名思义就是针对历史记录的 API ,这种模式的原理是先把页面的状态保存到一个对象(state)里,当页面的 URL 变化时找到对应的对象,从而还原这个页面。其实原本人家这个功能是为了方便浏览器前进后退的,不得不说程序员们的脑洞真大。使用了这种模式,就可以摆脱 # 号实现前端路由。
Vue 已经为我们提供了两种模式的前端路由,无需我们自己去实现。
// 修改 router\index.js,加入 mode: 'history
import Vue from 'vue'
import Router from 'vue-router'
import AppIndex from '@/components/home/AppIndex'
import Login from '@/components/Login'
Vue.use(Router)
export default new Router({
mode: 'history',
routes: [
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/index',
name: 'AppIndex',
component: AppIndex
}
]
})
拦截器简单逻辑:
a.访问其他页面未登录跳转登录页面;b.访问登录页面不拦截。
前端执行:npm run build
打包后放到后端resources\static
文件夹下
安全管理可以采用Spring Security 和 Shiro。

思路:
使用 “全局前置守卫”(router.beforeEach),在导航触发时向后端发送一个包含用户信息的请求;
后端查询数据库中该用户可以访问的菜单(也就是 vue 的路由信息)并返回;
前端把接收到的数据添加到路由里,并根据新的路由表动态渲染出导航栏,使不同用户登录后看到不同的菜单。同时,由于路由表也是按需加载的,所以用户也无法通过 URL 访问没有权限的页面;
前端按钮或者组件或图表,根据权限进行显示;可以根据权限动态渲染,或没权限进行提示。
比如根据用户id显示其拥有的书籍,每天访问页面数量或时间等等。
controller层登录验证核心操作三步:
1.获取subject,2.生成令牌,3.拿令牌认证得结果
@CrossOrigin
@PostMapping(value = "/api/login")
@ResponseBody
public Result login(@RequestBody User requestUser) {
String username = requestUser.getUsername();
// 1.获取Subject
Subject subject = SecurityUtils.getSubject();
// 2.根据用户密码和名字生成令牌
UsernamePasswordToken token = new UsernamePasswordToken(username,requestUser.getPassword());
// 3.拿令牌去进行登录验证,失败会报AuthenticationException异常
try{
subject.login(token);
return ResultFactory.buildSuccessResult(username);
}catch (AuthenticationException a) {
String msg = "账户或密码错误!";
return ResultFactory.buildFailResult(msg);
}catch (Exception e){
String msg = "未知错误!";
return ResultFactory.buildFailResult(msg);
}
}
shiro核心由
subject:“现在在与软件交互的东西”,这个东西可能是你是我,可能是第三方进程。说白了就是穿了马甲的用户类,负责存储与修改当前用户的信息和状态。(功能实现基本调用其API)
securityManager:管理器,实际操作人
realm:链接数据加工并传给securityManager
subject—>securityManager—>realm
拦截有多种实现,但是用户登录要有时效性,同时要避免频繁查询数据库导致压力
前端在每次请求时都加上用户名和密码,交由后端验证。
弊端:频繁查询数据库,导致服务器压力较大;信息被截取,攻击者就可以 一直 利用用户名密码登录(注意不是因为明文不安全,是由于无法控制时效性)。可以采用session+token解决。
1.session(会话机制)
作用:管理用户状态,比如控制会话存在时间,在会话中保存属性等。
操作描述:
(1)服务器接收到第一个请求时,生成 session 对象,并通过响应头告诉客户端在 cookie 中放入 sessionId
(2)客户端之后发送请求时,会带上包含 sessionId 的 cookie
(3)服务器通过 sessionId 获取 session ,进而得到当前用户的状态(是否登录)等信息
总的来说,客户端只需要在登录的时候发送一次用户名密码,此后只需要在发送请求时带上 sessionId,服务器就可以验证用户是否登录了。
session 存储在内存中,在用户量较少时访问效率较高,但一个服务器保存了几十几百万个 session 就十分难顶了。同时由于同一用户的多次请求需要访问到同一服务器,不能简单做集群,需要通过一些策略(session sticky)来扩展,比较麻烦。(session全面管理用户状态会占用很多服务器资源)
2.token
对于 token 的理解,比较常见的误区是:
(a)生成方面,使用随机算法生成的字符串、设备 mac 地址甚至 sessionId 作为 token。虽然从字面意思上讲这些也算是“令牌”,但是毫无意义。这是真的 “没有状态” 了,对于状态的控制甚至需要用 session 完成,那只用 session 不好吗?
(b)验证方面,把 token 存储在 session 或数据库中,比对前端传来的 token 与存储的 token 是否一致。鬼鬼,同样的骚操作。更骚的是为了提高比对效率把 token 存储在 redis 中,大家一看哇偶好高端好有道理,就直接采用这种方法了,甚至都懒得想 token 到底是个什么。。
简单来说,一个真正的 token 本身是携带了一些信息的,比如用户 id、过期时间等,这些信息通过签名算法防止伪造,也可以使用加密算法进一步提高安全性,但一般没有人会在 token 里存储密码,所以不加密也无所谓,反正被截获了结果都一样。(一般会用 base64 编个码,方便传输)
在 web 领域最常见的 token 解决方案是 JWT(JSON Web Token),其具体实现可以参照官方文档,这里不再赘述。
token 的安全性类似 session 方案,与明文密码的差异主要在于过期时间。其作用流程也与 session 类似:
(1)用户使用用户名密码登录,服务器验证通过后,根据用户名(或用户 id 等),按照预先设置的算法生成 token,其中也可以封装其它信息,并将 token 返回给客户端(可以设置到客户端的 cookie 中,也可以作为 response body)。
(2)客户端接收到 token,并在之后发送请求时带上它(利用 cookie、作为请求头或作为参数均可)。
(3)服务器对 token 进行解密、验证。
最后再强调一下:
token 的优势是无需服务器存储!!!
token 的优势是无需服务器存储!!!
token 的优势是无需服务器存储!!!
不要再犯把 token 存储到 session 或是数据库中这样的错误了。
无论是明文用户名密码,还是 sessionId 和 token,都可以用三种方式存储,即 cookie、localStorage 和 sessionStorage。
但 cookie 和 local/session Storage 分工又有所不同,cookie 可以作为传递的参数,并可通过后端进行控制,local/session Storage 则主要用于在客户端中保存数据,其传输需要借助 cookie 或其它方式完成。
三种方式对比
特性 | cookie | localStorage | sessionStorage |
---|---|---|---|
生命周期 | 一般由服务器生成,可设置失效时间。如果在浏览器端生成cookie,默认是关闭浏览器后失效 | 除非被清除,否则永久保存 | 仅在当前会话下有效,关闭页面或浏览器后被清除 |
数据大小 | 4K左右 | 一般为5MB | 一般为5MB |
通讯方式 | 每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题 | 仅在客户端(即浏览器)中保存,不参与和服务器的通信 | 同 localStorage |
通常来说,在可以使用 cookie 的场景下,作为验证用途进行传输的用户名密码、sessionId、token 直接放在 cookie 里即可。而后端传来的其它信息则可以根据需要放在 local/session Storage 中,作为全局变量之类进行处理。
shiro 的安全管理实际上是基于会话实现的,所以我们用 session 方案就可以了。
subject.login()实际上会产生一个session,并自动把 sessionId 设置到 cookie。这个看 DelegatingSubject
类还看不出来,还要再继续深入分析源码。我们简单做个测试,使用 postman 发一个登录请求:

可以看到返回了一个JSESSIONID,这是sessionid在tomcat中的叫法。
默认的情况下,跨域的 cookie 是被禁止的,后端不能设置,前端也不能发送,所以两边都要设置。
由于**跨域情况下会先发出一个 options 请求试探,这个请求是不带 cookie 信息的,所以 shiro 无法获取到 sessionId,将导致认证失败。**所以后端需要设置放过options请求。
// 拦截器设置
// 放行 options 请求,否则无法让前端带上自定义的 header 信息,导致 sessionID 改变,shiro 验证失败
if(HttpMethod.OPTIONS.toString().equals(httpServletRequest.getMethod())){
httpServletResponse.setStatus(HttpStatus.NO_CONTENT.value());
return true;
}
//MyWebConfigurer
/**
* 配置“全局”跨源请求处理。 配置的CORS映射适用于带注释的控制器,功能端点和静态资源
* @param registry
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
// 允许跨域情况下使用 cookie
// 默认情况下不设置此选项,在这种情况下,也不会设置Access-Control-Allow-Credentials标头,因此不允许使用凭据
.allowCredentials(true)
// 允许跨域请求的来源列表。不能使用通配符 *,这也是出于安全上的考虑。注意地址是前端访问地址
.allowedOrigins("http://localhost:8080")
// 允许跨域的方法类型
.allowedMethods("POST","GET","PUT","OPTIONS","DELETE")
//特殊值"*"可用于允许所有标头。
//根据CORS规范,如果标头名称是以下之一,则不需要列出标头名称: Cache-Control , Content-Language , Expires , Last-Modified或Pragma 。
//默认情况下,所有标头都是允许的
.allowedHeaders("*");
}
//main.js
axios.defaults.withCredentials = true
前端每次发送请求时就会带上 sessionId,shiro 就可以通过 sessionId 获取登录状态并执行是否登录的判断。
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
if (store.state.user) {
// 当用户状态判断正确,再向后端发送验证请求通过再跳转显示页面
axios.get('/authentication').then(resp => {
if (resp) {
next()
}
})
} else {
next({
// 未登录跳转登录页面
path: 'login',
// 将跳转的路由path作为参数,登录成功后跳转到该路由
query: { redirect: to.fullPath }
})
}
} else {
next()
}
})
即访问每个页面前都向后端发送一个请求,目的是经由拦截器验证服务器端的登录状态,防止上述情况的发生。后端这个接口可以暂时写成空的:
@ResponseBody
@GetMapping(value = "api/authentication")
public String authentication(){
return "身份认证成功";
}
shiro的rememberMe 机制不是单纯地设置 cookie 存活时间,而是又单独保存了一种新的状态。之所以这样设计,也是出于安全性考虑,把 “记住我” 的状态与实际登录状态做出区分,这样,就可以控制用户在访问不太敏感的页面时无需重新登录,而访问类似于购物车、订单之类的页面时必须重新登录。
admin_menu
表的各个字段:
字段 解释 id 唯一标识 path 与 Vue 路由中的 path 对应,即地址路径 name 与 Vue 路由中的 name 属性对应 name_zh 中文名称,用于渲染导航栏(菜单)界面 icon_cls element 图标类名,用于渲染菜单名称前的小图标 component 组件名,用于解析路由对应的组件 parent_id 父节点 id,用于存储导航栏层级关
windows 下默认不区分 mysql 字段大小写,而 linux 区分,所以数据库字段不推荐大小写混用(最好都小写),而 Java 属性一般采用小驼峰法命名,JPA 会自动将小驼峰命名转换为下划线命名,比如 nameZh 自动转换为 name_zh。 数据库中不存在对应字段的属性,需要用 @Transient 注记标注出来。 
根据用户查询出对应菜单的步骤是:
整合查询出的菜单数据的思路如下:
public void handleMenus(List<AdminMenu> menus) {
for (AdminMenu menu : menus) {
List<AdminMenu> children = getAllByParentId(menu.getId());
menu.setChildren(children);
}
Iterator<AdminMenu> iterator = menus.iterator();
while (iterator.hasNext()) {
AdminMenu menu = iterator.next();
if (menu.getParentId() != 0) {
iterator.remove();a
}
}
}
JPA 为我们提供了持久化上下文(Persistence Context,是一个实体的集合),用于确保相同的持久化对象只有一个实例,且在存在相应实例时不会再次访问数据库(详细内容见 「JPA Persistence Context」)。因此,我们查询到的 children 列表中的每一个 AdminMenu 对象实例都复用了 Menus 列表中的 AdminMenu 对象。
为什么删除子项时用 iterator.remove() 而不用 List 的 remove 方法呢?是因为使用 List 遍历时,如果删除了某一个元素,后面的元素会补上来,也就是说后面元素的索引和列表长度都会发生改变。而循环仍然继续,循环的次数仍是最初的列表长度,这样既会漏掉一些元素,又会出现下标溢出,运行时表现就是会报 ConcurrentModificationException。而 iterator.remove() 进行了一些封装,会把当前索引和循环次数减 1,从而避免了这个问题。
注意:find参数如果是集合要加findxxxxxIn
如:List findAllByRidIn(List rids);
用户-角色-权限表设计架构

解释一下权限表的设计:
name
即权限的名称,推荐使用英文desc_
即对权限功能的具体描述url
即权限对应的接口,是实现功能控制的关键PathMatchingFilter 是 Shiro 提供的路径过滤器,我们可以通过继承它来编写过滤放行条件,即判断是否具有相应权限。判断的逻辑为:
// SpringContextUtils是自己设置的工具类,用getContext方法获取容器然后获取对应service
if (null==adminPermissionService) {
adminPermissionService = SpringContextUtils.getContext().getBean(AdminPermissionService.class);
}
在 Shiro 的配置文件中,我们不能把 URLPathMatchingFilter 用 @Bean 被 Spring 管理起来。 原因是 Shiro 存在 bug, 这个也是过滤器,ShiroFilterFactoryBean 也是过滤器,当他们都出现的时候,默认的什么 anno,authc 过滤器就失效了。所以不能把他声明为 @Bean。
因此,我们无法在 URLPathMatchingFilter 中使用 @Autowired 注入 AdminPermissionService 类,所以需要借助一个工具类利用 Spring 应用上下文获取 AdminPermissionService 的实例。
注意:当采用过滤器时,如果对特点访问路径如/admin/**采用过滤规则,那么在访问/admin/**这个路径的接口时,@RequiresPermissions权限验证将不生效。
// xxxRealm中添加授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取当前用户的所有权限
String username = principalCollection.getPrimaryPrincipal().toString();
Set<String> permissions = adminPermissionService.listPermissionURLsByUser(username);
// 将权限放入授权信息中
SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
s.setStringPermissions(permissions);
return s;
}
// 注解的方式控制用户信息查询
@RequiresPermissions("/api/admin/user")
@GetMapping("/api/admin/user")
public List<User> listUsers() throws Exception {
return userService.list();
}
略
推荐书籍:《Effective Java》以及《重构——改善既有代码设计》
对于 Java 来说,第一线的规范学习材料包括:

针对我们的项目,在代码规范方面要做的事情,自顶向下包括:
注意:代码要清晰明了,但清晰并非简洁,写出的代码要别人很快理解看懂才是好代码。
解决代码规范问题能让你吃上程序员这口饭,但只有具备解决性能问题的能力,才能让这碗饭吃地香。
性能问题,除了 算法 之外,还要考虑 软件架构、软件设计、软件部署 ,以及一些具体的 优化技巧。
搞明白这些方面真的不容易,但同样借用范学雷老师的话,一个人能否解决好性能问题,很多时候都不是取决于聪明程度,而是取决于他的意识和见识。成熟的解决方案就在那儿,容易理解,也容易操作,只是我们没有想到,没有看到,没有用到这些解决方案。
Vitaly Friedman 的大作:
「Front-End Performance Checklist(前端优化清单)」

由于项目前端逻辑较为简单,主要工作其实在于加载方面的优化。优化的原则其实可以简单概括为 “减少请求数量,降低请求大小”。
后端性能提升的关注点主要在于 提高响应速度。
相比前端,这是一个更为复杂的命题,需要从多个方面考虑。下面我给你列出一些名词:
高并发、缓存、消息队列、数据库优化、负载均衡、集群、分布式、微服务、JVM 调优
当然,还是要在挣扎中前行。现在我们还没有具体硬件环境,仅就软件来说,努力的方向有三个,一是 代码,二是 “技术应用”,三是 “优化设计”。 
吴翰清 老师的 《白帽子讲 Web 安全》。
即使迟早会被吊打,起码也要知道对面是哪门哪派、使的什么招吧。
我们还需要一个 评估工具 ——

那么根据很久之前的计划以及大家的反馈,这次我们来聊一聊缓存的使用。主要有以下几个关注点:
各个点之间需要衔接,要衔接就会有两个层次的不均衡:
一是性能的不均衡,包括速率、吞吐量等,造成这种不均衡的原因包括软件、硬件、网络、协议、策略等、位置多个维度 二是数据本身活跃性的不均衡,有些数据会被频繁传递,有些很久才被访问一次
基于这两个不平衡,诞生了各种缓存方案。比较常见的有以下几种:
旁路缓存或叫边缘缓存,如果缓存中存在,则从缓存获取,如果不存在,则从数据库获取,并写入缓。

把数据库藏在缓存背后,一切请求交由缓存响应。也就是说,如果命中缓存,则直接从缓存获取,如果没有命中,则从数据库中查询,写入缓存后再由缓存返回。
应用这种模式,写入缓存的操作会阻塞请求的响应

请求更新数据,如果该数据在缓存中存在,则先更新缓存,再更新数据库。
请求更新数据,更新缓存,至于数据库什么时候更新,不一定,有机会再更新,可以攒一波再更新,有缓存在就行。异步的方式有数据不一致的风险,但够快。
使用缓存时,我们一般都会考虑以下几个问题:
下面我们来聊一聊这三个问题如何应对。
数据一致性问题:
一个系统,如果数据都是不变的,应用 Cache-Aside 模式,可以做到缓存中的数据永远和数据库中一致,需要考虑的就是缓存什么时候过期,或者缓存更新的算法,做到尽可能地找出热点数据即可。
但大部分系统是要更新数据的,数据更新了缓存没有及时更新,有时候没有问题,但在一些场景下不能容忍,比如支付宝,你买了东西一看钱没变,于是疯狂买买买,后来突然一下钱全没了,这谁顶的住对不对。
于是我们在写场景下更新缓存,采用先更数据库再更缓存的模式,比如你买了个煎饼果子,支付宝实际余额从 100 变成了 90,你老婆同时在别的地方用你的支付宝又买了杯豆浆,实际余额变成 85,数据库没问题,但你买煎饼果子时缓存服务卡了一下子,更新操作发生在了豆浆事件的后面,你们俩回家一看查出来的余额是 90,以为白嫖了 5 块钱,但其实还是假象。
其实数据一致性问题还是在并发这个范畴内,整体原则就是分析实际场景,尽可能选择既高效又安全的方案。当然这并不是一件容易的事,如果容易就没有那么多年薪百万的架构师了。
缓存穿透:
引发缓存穿透的情形一般有两种,一是大量查询一个数据库里也没有的数据,这种数据正常不会被缓存,结果每次都要到数据库里兜一圈。那我们可以设置一个规则,数据库没有的数据我们也缓存起来,值设置成空就行了。
另一种情形是,数据库里有这个数据,之前从没人查询过,但突然有那么一瞬间来了一大波请求,缓存根本来不及反应,压力就全都到了数据库上。这种怎么办?两种办法,一是限流,二是预判。
限流好理解,请求少了就反应的过来了。预判怎么预判?你怎么知道哪个数据会被频繁访问?
不好意思,一般还真的知道,一个数据突然被访问的情况,一般是你自己捣鼓出来的什么幺蛾子,比如淘宝要搞双十一,那有些数据一定会被突然频繁访问,这些数据当然能预判个八九不离十。在请求排山倒海般到来之前,先把它填充到缓存里就完事儿了。(这种做法通常称为缓存预热)
缓存雪崩:
其实本质上雪崩和穿透是一类问题,只是出现的阶段不一样,穿透是缓存已经稳定建立起来了,雪崩是缓存突然同时过期了。当然还有一种情况,就是完全还没有缓存的时候,一大波请求涌入。比如缓存没做持久化,结果机房断电了,重启之后就是没有缓存的。
解决方法仍然是限流和缓存预热。其实这些名词也是没意思,奈何总是有人会问,有人会考。
put方法的执行要在setFilters方法执行之前,如果在其后则不会生效
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
Map<String, Filter> customizedFilter = new HashMap<>(); // 自定义过滤器设置 1
// 设置自定义过滤器名称为 url
customizedFilter.put("url", getURLPathMatchingFilter()); // 自定义过滤器设置 2,命名,需在设置过滤路径前
filterChainDefinitionMap.put("/api/authentication","authc"); // 防鸡贼登录
filterChainDefinitionMap.put("/api/admin/**", "authc");
// 对管理接口的访问启用自定义拦截(url 规则),即执行 URLPathMatchingFilter 中定义的过滤方法
filterChainDefinitionMap.put("/api/admin/**", "url"); // 自定义过滤器设置 3,设置过滤路径
// 启用自定义过滤器
shiroFilterFactoryBean.setFilters(customizedFilter); // 自定义过滤器设置 4,启用
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
////必须认证(登录)才能访问/user/*(登录拦截)
//filterChainDefinitionMap.put("/user/*","authc");
////设置过滤器的约束定义(参数是map)
//shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
////设置登录请求
//shiroFilterFactoryBean.setLoginUrl("/toLogin");
return shiroFilterFactoryBean;
// 单个参数
List<AdminRoleMenu> findAllByRid(int rid);
// 列表参数:方法尾缀多个'In'
List<AdminRoleMenu> findAllByRidIn(List<Integer> rids);
switch按钮是true或者false,在数据库采用tinyint存储,用0表示false,1表示true;java后端存储采用类型boolean
// 标识与此AuthenticationInfo实例关联的帐户的主体。
/**
* The principals identifying the account associated with this AuthenticationInfo instance.
*/
protected PrincipalCollection principals;
// 填充认证信息
public SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName) {
this.principals = new SimplePrincipalCollection(principal, realmName);
this.credentials = hashedCredentials;
this.credentialsSalt = credentialsSalt;
}
principal–与指定领域关联的“主要”主体。
hashedCredentials –验证给定主体的哈希凭据。
certificateSalt –对给定的hashedCredentials进行哈希处理时使用的盐
realmName –从中获取主体和凭据的领域。
在xxxRealm中的认证过程中会通过SimpleAuthenticationInfo对认证信息进行存储,其中principal经过调用SimplePrincipalCollection变为principals;
获得**主体(principal)**通过principals.getPrimaryPrincipal().toString();获取,当在填充认证信息时主体填充的是username(用户名)时,那么获得的也是用户名字符串。
注意:**主体(principal)**是必须唯一的,也可以是邮箱、身份证等等。
//vue put请求
this.$axios.put('/admin/role', {
// 对象数据(key:对象)
adminRole: {
id: role.id,
name: role.name,
nameZh: role.nameZh,
enabled: role.enabled
},
// 列表数据(key:list<Integer>)
menusIds: this.$refs.menusTree.getCheckedKeys(),
permsIds: this.$refs.permsTree.getCheckedKeys()
})
后端获取解析采用alibaba.fastjson工具包
// 导入依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
// 后端解析数据获取不同类型的json
public Result editRole(@RequestBody Map<String, Object> dataBody){
// 1.修改角色基础信息
AdminRole adminRole = JSON.parseObject(JSON.toJSONString(dataBody.get("adminRole")),AdminRole.class);
adminRoleService.addOrUpdate(adminRole);
// 2.修改对应菜单
List<Integer> menusIds = JSON.parseObject(JSON.toJSONString(dataBody.get("menusIds")),List.class);
adminRoleMenuService.updateRoleMenu(adminRole.getId(), menusIds);
// 3.修改对应权限
List<Integer> permsIds = JSON.parseObject(JSON.toJSONString(dataBody.get("permsIds")),List.class);
adminRolePermissionService.updateRolePerm(adminRole.getId(),permsIds);
return ResultFactory.buildSuccessResult(adminRoleService.listWithPermsAndMenus());
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。