1. 从登录到增删查改整体流程
首先在拦截器上打一个断点,分析登录的过程
简单来说就是用户在请求中携带X_ACCESS_TOKEN
这个参数,然后将这个参数转交给后端,后端将其解析为JWT
对象进行验证,调用底层的login
接口,如果没有抛exception就是登录成功了
这个TOKEN的有效时间是一个小时
权限管理
权限管理的包在下面这里
package org.jeecg.modules.system.controller;
保存角色权限的逻辑
@RequestMapping(value = "/saveRolePermission", method = RequestMethod.POST)
//@RequiresRoles({ "admin" })<====
public Result<String> saveRolePermission(@RequestBody JSONObject json) {
long start = System.currentTimeMillis();
Result<String> result = new Result<>();
try {
String roleId = json.getString("roleId");
String permissionIds = json.getString("permissionIds");
String lastPermissionIds = json.getString("lastpermissionIds");
this.sysRolePermissionService.saveRolePermission(roleId, permissionIds, lastPermissionIds);
//update-begin---author:wangshuai ---date:20220316 for:[VUEN-234]用户管理角色授权添加敏感日志------------
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
baseCommonService.addLog("修改角色ID: "+roleId+" 的权限配置,操作人: " +loginUser.getUsername() ,CommonConstant.LOG_TYPE_2, 2);
//update-end---author:wangshuai ---date:20220316 for:[VUEN-234]用户管理角色授权添加敏感日志------------
result.success("保存成功!");
log.info("======角色授权成功=====耗时:" + (System.currentTimeMillis() - start) + "毫秒");
} catch (Exception e) {
result.error500("授权失败!");
log.error(e.getMessage(), e);
}
return result;
}
String roleId = json.getString("roleId");
String permissionIds = json.getString("permissionIds");
String lastPermissionIds = json.getString("lastpermissionIds");
baseCommonService.addLog("修改角色ID: "+roleId+" 的权限配置,操作人: " +loginUser.getUsername() ,CommonConstant.LOG_TYPE_2, 2);
这里的话可以看出,这里的话这个框架做了个优化,按照一般的方法是前端把这个表单上所有的授权人的ID
都拿到,然后删除对应的表中全部授权,然后再把这个这次的授权全部刷回去。
但是它做了一个优化,把用户做出操作之前的授权人ID
先存下来,然后再存一个做出操作后的授权人ID
,然后在service中做一个比较,根据比较的差异来进行删除、增加操作。
经过以上的流程就是一个添加权限的过程
增删查改的支持
jeecg-boot
的增删查改的支持是通过查询生成器来完成的
package org.jeecg.common.system.query;
这个类中包含了大量组装SQL的方法,通过前端发送的信息,来组装SQL,最终完成一键建表,在前台CRUD的功能
1.1 Shiro框架简介
Shiro
是一个功能强大而且容易使用的Java
安全框架,执行身份验证
、授权
、加密
和会话管理
,使用Shiro
易于理解的api,可以快速搭建安全验证框架。
Subject
:是主体的含义,外部应用与subject
进行交互,subject
中记录了当前的操作用户,将用户的概念理解为当前操作的主体。外部程序通过subject
进行认证授权,而subject
是通过SecurityManager
安全管理器进行认证授权的SecurityManager
:也就是安全管理器,对全部的subject
进行安全管理,是shiro
的核心,负责对所有的subject
进行安全管理,通过SM
可以完成对subject
的认证,授权等,实际上SM
是通过Authenticator
进行认证的,通过Authorizer
进行授权,通过SessionManager
进行会话管理。
简单来说,SecrityManager集成了认证,授权,连接会话管理的三大功能。
Authenticator
:认证器,对用户身份进行认证,它是一个接口标准,shiro
提供了ModularRealmAuthenticator
实现子类,也可以自定义认证器。Authorizer
:也就是授权器,用户通过认证器通过,在访问功能的时候需要通过授权器
判断用户是否具有此功能的操作权限。Realm
:领域,相当于dataSource
数据源,SM
进行安全认证的时候需要通过Relam
获取用户权限数据SessionManager
:会话管理,shiro
框架定义了一套会话管理,它不依赖于web
容器的session
,所以shiro
可以使用在非web
应用上,也可以将分布式应用的会话集中在一点进行管理,此特性可以使得它实现单点登录。
1.2 认证
身份认证
:就是判断一个用户是否是合法用户的处理过程,最常用的简单身份认证方式就是通过核对用户输入的口令,看其是否与系统中存储的该用户名和口令一致,来判断用户身份是否正确。
principal
:身份信息,是主体进行身份认证的标识,标识必须具有唯一性
,一个主体可以有多个身份,但是必须有一个主身份。credential
:凭证信息,是只有主体知道的安全信息,如密码,证书等。
1.3 helloworld程序
// 1、创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 2、给安全管理器设置realm,这个里面会存储一个键值对,可以由用户自行规定
//比如说我们这里设定的是username = password
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
// 3、给全局安全工具类SecurityUtils设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
// 4、拿到当前的subject
Subject subject = SecurityUtils.getSubject();
// 5、创建令牌
AuthenticationToken token = new UsernamePasswordToken("christy","123456");
try {
// 6、用户认证
System.out.println("认证状态:"+subject.isAuthenticated());
subject.login(token);
System.out.println("认证状态:"+subject.isAuthenticated());
} catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败:用户不存在!");
} catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败:密码不正确!");
} catch (Exception e){
e.printStackTrace();
}
}
1.4 使用自定义的Realm实现认证
首先我们需要搞清楚其内部是如何运作的。
在这一步中,我们首先是通过
UsernamePasswordToken("christy","123456")
,处理后得到一个token
对象,然后调用了主体的login
方法,这个方法需要一个token
然后这里做了一个装饰器,将
login
交给底层的securityManager
来做login
,同样的,这次的认证需要一个被认证的主体
,以及根据用户和口令
产生的token
进行验证
做方法分发,将
token
分发到认证器。
对token进行认证
我们直接来看到这个
doGet
的方法,这个方法中,校验了用户是否被锁定或者被禁用。那么应该还有一个密码的校验方法。
然后用户名通过了校验,那么就会调用断言方法进行密码匹配。
/**
* 自定义realm
*/
public class CustomerRealm extends AuthorizingRealm {
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 在token中获取用户名
String principal = (String) token.getPrincipal();
System.out.println(principal);
// 模拟根据身份信息从数据库查询
if("christy".equals(principal)){
// 参数说明:用户名 | 密码 | 当前realm的名字
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123456",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
2.业务代码源代码阅读
以一个简单的对Room
实体类的增删查改进行分析。
2.1 Swagger框架简介
Swagger
是一套围绕OpenAPI
规范构建的开源工具,可以帮助设计,构建和记录使用Restful API
,api文档和api定义同步更新。
- 导入swagger
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.10.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-common -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-common</artifactId>
<version>2.10.5</version>
</dependency>
- 开启swagger
@Configuration
@EnableSwagger2
class SwaggerConfig{
//配置Swagger的实例
@Bean
public Docket getDocket(){
return new Docket(DocumentationType.SWAGGER_2);
}
}
2.2 常用配置
@Configuration
@EnableSwagger2
class SwaggerConfig{
//配置Swagger的实例
@Bean
public Docket getDocket(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")//配置分组
.apiInfo(webApiInfo())
.select()
.paths(Predicates.and(PathSelectors.regex("/api/.*")))
.build();
}
@Bean
public Docket adminApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("adminApi")
.apiInfo(adminApiInfo())
.select()
//只显示admin路径下的页面
.paths(Predicates.and(PathSelectors.regex("/admin/.*")))
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-API文档")
.description("本文档描述了os-system中后端接口定义")
.version("1.0")
.contact(new Contact("Lumxi", "https://lumxi.github.io/", "1274356440@qq.com"))
.build();
}
private ApiInfo adminApiInfo(){
return new ApiInfoBuilder()
.title("模拟操作系统-API文档")
.description("本文档描述了os-system中后端接口定义")
.version("1.0")
.contact(new Contact("Lumxi", "https://lumxi.github.io/", "1274356440@qq.com"))
.build();
}
}
2.3 常用注解
@ApiModel
:修饰整个类,描述controller
的作用
@ApiModel(value="room对象", description="房间表")//表示这个类是什么,是拿来干什么的
public class Room implements Serializable {}
在实体类上使用,标记类时swagger
会解析这个类
@ApiOperation
:描述一个类的一个方法,或者一个接口
@ApiOperation(value="房间表-分页列表查询", notes="房间表-分页列表查询")
public Result<IPage<Room>> queryPageList(){}
@ApiParam
:单个参数的描述
在Rest
接口上或者Rest接口参数前使用,为Rest接口参数添加其他元数据
public CommonResult updatePassword(
@ApiParam(value = "手机号码",required = true) @RequestParam String userPhone,
@ApiParam(value = "验证码",required = true) @RequestParam String authCode){
return userMemberService.updatePassword(userPhone,authCode);
}
@ApiModelProperty
:用对象来接收参数的时候,用来描述对象的一个字段
在使用被@ApiModelProperty
注解的模型类的属性上,表示对model
属性的说明或者数据操作更改。
@ApiModelProperty(value = "主键")
private String id;
@ApilmplicitParam
:一个请求参数@ApiImplicitParams
:多个请求参数
@ApiImplicitParams({
@ApiImplicitParam(name = "mobile", value = "手机号码", dataType = "string", paramType = "query", example = "13802780104", required = true),
@ApiImplicitParam(name = "user_name", value = "登录账号", dataType = "string", paramType = "query", example = "lihailin9073", required = true),
@ApiImplicitParam(name = "password", value = "登录密码", dataType = "string", paramType = "query", example = "123456", required = true),
@ApiImplicitParam(name = "validate_code", value = "注册验证码", dataType = "string", paramType = "query", example = "3679", required = true)
})
2.4 实体类阅读总结
实体类中定义了我们在使用低代码时使所定义的字段,同时通过Swagger
来生成对应的文档字段,其中@Excel
字段是autopoi
中的注解,应该是@Excel
相关的注解。
2.5 DAO&Service层阅读总结
在代码生成器中,DAO
层全部使用的是MP
的接口,需要注意的是,如果要正确注入mapper
,需要在启动项上加上mapperScan
2.6 Controller
RequsestParam
将请求参数绑定到控制器的方法参数上
@RequestParam(value="参数名",required="true/false",defaultValue="")