需求
公司打算重构权限系统,主要因为现有的系统存在这些问题:
查询用户步骤繁琐:选系统(主要是这一点)——用户名——查询按钮;
简化权限只分配到角色:现在权限全部汇总到一起,不知道权限从何而来;
角色承担职责过多,杂而乱;
登录名不唯一,无账号找回功能
…
总之就是一个很乱的权限系统,考虑到security的强大功能,灵活易拓展(各种自定义),耦合性低,所以打算用security去是实现。
其实,我是不建议大家直接去了解security,大家可以先考虑一下没引入security之前,登录认证授权流程是怎样的,因为毕竟security也是按照这些去封装代码的。
如果直接去了解security的话,多少会有点迷茫和含糊,因为它比较复杂(功能特别强大,拓展性极好),在知道本质之后去了解,一切都会显得很清晰~~~
为了方便入门,下面我主要讲解的是没有被security封装之前的登录授权流程代码~~
如果这些您已了解,那么请直接看下篇的登录流程源码分析。
前提
在了解security之前,我们必须知道这几个东西:
-
什么是会话?
-
授权的数据模型?
-
RBAC方式
什么是会话?
用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保证在会话中。会话就是系统为了保持当前 用户的登录状态所提供的机制。
常见的方式有两种:
- 基于session方式;
- 基于token方式;
session方式,我们可能很熟悉了,它的交互流程就是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的 sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数 据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。如图:
基于token方式如下图:
它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份
基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持 cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式。如今移动互联网时代 更多类型的客户端需要接入系统,系统多是采用前后端分离的架构进行实现,所以基于token的方式更适合。
授权的数据模型
即表结构的对应关系
主体(用户id、账号、密码、…)
角色(角色id、角色名称、…)
主体(用户)和角色关系(用户id、角色id、…)
权限(权限id、权限标识、权限名称、资源名称、资源访问地址、…)
角色与权限的关系(角色id,权限id…)
RBAC方式
如何实现授权,可以通过RBAC方式实现授权,RBAC有两种方式:
- 基于角色的访问控制(存在硬编码问题)
- 基于资源的访问控制(推荐)
具体如下:
基于角色的访问控制
用户必须 具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
根据上图中的判断逻辑,授权代码可表示如下:
if(主体.hasRole("总经理角色id")){
查询工资
}
如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是 总经理或部门经理”,修改代码如下:
if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){
查询工资
}
由此,我们可以看出,以上存在硬编码问题!
基于资源的访问控制
根据上图中的判断,授权代码可以表示为:
if(主体.hasPermission("查询工资权限标识")){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改 授权代码,系统可扩展性强
入门
在了解security之前,我们先来了解一下没有security的时候,登录流程是怎么走的。
为了防止迷路,我先说一下步骤:
-
搭建项目环境(applicationcontext.xml,springmvc.xml,登录页面,初始化spring容器等);
-
配置所需要的bean以及业务;
-
认证逻辑;
-
会话管理
- 基于session,通过HttpSession操作,主要用到的方法有setAttribute(),getAttribute(),invalidate();
- 在认证成功的时候调用setAttribute()方法储存,在访问资源的时候调用getAttribute(),在登出的时候调用invalidate();
-
授权功能
其实授权的本质就是一个拦截器,通过页面当前访问的资源与后台查到的用户权限做匹配,如果一致,则放行。
以上部分我们重点讲解3,5其余步骤略。
认证逻辑
认证的流程为:
- 首先,当用户请求过来的时候,我们是不是需要判断一下它传过来的值是否为空,即用户名或者密码为空;
- 其次我们需要拿着用户名字去我们数据库查询是否有这个用户,没有的话,直接抛异常“没有该用户”;
- 如果通过用户名查到用户的话,那我们是不是需要用它来跟用户输入的值去做对比,如果一致则放行,不一致则抛异常(“账号或密码错误”);
以上三点用代码来表示的话,如下:
public User authentication(AuthenticationRequest authenticationRequest){
if(authenticationRequest == null || StringUtils.isEmpty(authenticationRequest.getUsername()) || StringUtils.isEmpty(authenticationRequest.getPassword())){
throw new RuntimeException("用户名或密码为空");
}
User user = userDao.getUserByUsername(authenticationRequest.getUsername());
if(Objects.isNull(user)){
throw new RuntimeException("该用户不存在!");
}
if(!authenticationRequest.getPassword().equals(a.getPassword())){
throw new RuntimeException("用户名或密码错误!");
}
return user;
}
其实这段代码被security封装到了一个叫userDetailService的类中,但是其最本质的功能跟这个是一样的;
授权功能
流程如下:
- 为了模仿授权功能的实现,我们增加一个r1的资源(访问此路径的前提是在用户已认证的情况下),想要访问此资源必须要有p1的权限;
- 现在一名叫‘王五’的用户想访问它,所以我们就需要判断一下王五是否含有p1权限;
- 如果含有p1权限,则放行;
代码如下:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//用户身份信息
Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
if(object == null){
//没有认证
writeContent(response,"请登录");
}
UserDto userDto = (UserDto) object;
//请求的url
String requestURI = request.getRequestURI();
if( userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")){
return true;
}
writeContent(response,"没有权限,拒绝访问");
return false;
}
其实这块代码也被security封装到了userDetailService中,但是security可能跟这个有点出入,security它引入了一个AccessDecisionVoter用于投票选举机制,又采用3种模式判定,这个我们在后面详细解释…
总结
至此,我们security种的认证授权最本质的代码就说到这了,当然security的功能要比这强大太多,下一结我们去看看security的底层源码,看看它就是是怎么进行登录授权流程的。
下一篇链接:SpringBoot集成SpringSecurity(二) 登录认证流程解析
参考文献
推荐视频:https://www.bilibili.com/video/av73730658?from=search&seid=10438598876647660573
因为我就是用这个视频去入手的,特别适合入门,讲的很透彻,大家感兴趣的可以看看
另:如有总结不对的地方,麻烦大家指出,希望我们共同给进步_