从零开始搭建一个项目骨架,最好选择适宜熟悉的技能,并且在未来易拓展,适合微服务化体系等。所以一般以Springboot作为咱们的结构根底,这是离不开的按钮图片了。
然后数据层,咱们常用的是Mybatis,易上ios系统手,方便保护。但是单表操作比较困难,特别是增加字段或削减字段的时分,比较繁琐,所以这儿我推荐运用Mybajson格式tis PlusJSON(mp.baomidou.cjsonpom/),为简化开发而生,只需简单装备,即可快速进行CRUD操作,然后节省大量时刻。
Spring上下文无关文法Security,运用ios15security作为咱们的权限操控和会话操控的iOS结构。
- SpringBoot
- myb进程上下文atis plus
- spring security
- lombok按钮开关怎么接
- redis
- hibernate validat上下文无关文法ior
- jwt
二、新建SpringBoot 项目,留意版本
1、新建SpringBoot工程
这儿,咱们运用IDEA来开发咱们项目
开发东西与环境:
idea
mysql
jdk 8
maven3.3.9
新建SiOSpringBoot
删去部分内容
2、整合MyBatis plus,生按钮英文翻译成代码
(1)引进依靠
<!--整合mybatis plus https://baomidou.com/-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--mp代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
(2)设置装备文件
server:
port: 8081
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/zhengadminvue?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
新上下文什么意思建一个包:经过@mapperScan注解指定要变成完结类的接口地点的包,然后包下面的一切接口在编译之后都会生上下文英语成相应的完结类。
@Configuration
@ManagedBean("cn.itbluebox.springbootadminvue.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//防止全表更新插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
创立对应的mapper文件
(3)创立数据库和表
SQL语句
DROP TABLE IF EXISTS `sys_menu`;
CREATE TABLE `sys_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
`name` varchar(64) NOT NULL,
`path` varchar(255) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(255) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
`component` varchar(255) DEFAULT NULL,
`type` int(5) NOT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(32) DEFAULT NULL COMMENT '菜单图标',
`orderNum` int(11) DEFAULT NULL COMMENT '排序',
`created` datetime NOT NULL,
`updated` datetime DEFAULT NULL,
`statu` int(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
`code` varchar(64) NOT NULL,
`remark` varchar(64) DEFAULT NULL COMMENT '补白',
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
`statu` int(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`) USING BTREE,
UNIQUE KEY `code` (`code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_role_menu
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_menu`;
CREATE TABLE `sys_role_menu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL,
`menu_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`email` varchar(64) DEFAULT NULL,
`city` varchar(64) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
`last_login` datetime DEFAULT NULL,
`statu` int(5) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_USERNAME` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;
(4)代码生成
package cn.itbluebox.springbootadminvue;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 演示比如,履行 main 办法操控台输入模块表名回车主动生成对应项目目录中
public class CodeGenerator {
/**
* <p>
* 读取操控台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 大局装备
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("itbluebox");
gc.setOpen(false);
// gc.setSwagger2(true); 实体特点 Swagger2 注解
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);
// 数据源装备
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/itzheng-vue-admin?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包装备
PackageConfig pc = new PackageConfig();
// pc.setModuleName(scanner("模块名"));
pc.setParent("cn.itbluebox.springbootadminvue");
mpg.setPackageInfo(pc);
// 自界说装备
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 假如模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 假如模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自界说输出装备
List<FileOutConfig> focList = new ArrayList<>();
// 自界说装备会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自界说输出文件名 , 假如你 Entity 设置了前后缀、此处留意 xml 的称号会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判别自界说文件夹是否需求创立
checkDir("调用默许办法创立的目录,自界说目录用");
if (fileType == FileType.MAPPER) {
// 现已生成 mapper 文件判别存在,不想从头生成回来 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 装备模板
TemplateConfig templateConfig = new TemplateConfig();
// 装备自界说输出模板
//指定自界说模板途径,留意不要带上.ftl/.vm, 会依据运用的模板引擎主动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 战略装备
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("BaseEntity");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
strategy.setSuperControllerClass("BaseController");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id", "created", "updated", "statu");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
// strategy.setTablePrefix("sys_");//动态调整
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
1、获取对应项目一切的表和字段的信息
2、新建一个freemarker的页面模板
3、供给相关需求进行烘托的动态数据
# 获取表
SELECT
*
FROM
information_schema. TABLES
WHERE
TABLE_SCHEMA = (SELECT DATABASE());
# 获取字段
SELECT
*
FROM
information_schema. COLUMNS
WHERE
TABLE_SCHEMA = (SELECT DATABASE())
AND TABLE_NAME = "sys_user";
sys_user_role,sys_user,sys_role_menu,sys_role,sys_menu
主动生成代码
咱们发现实体类和controller报错缺少对应的Bese
创立BaseEntity
package cn.itbluebox.springbootadminvue.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class BaseEntity implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private LocalDateTime created;
private LocalDateTime updated;
private Integer statu;
}
留意每一个Controller的引进
(5)编按钮的工作原理写测验办法
/**
* <p>
* 前端操控器
* </p>
*
* @author itbluebox
* @since 2022-05-26
*/
@RestController
@RequestMapping("/sys-user")
public class SysUserController extends BaseController {
@Autowired
private SysUserService sysUserService;
@GetMapping("list")
public List<SysUser> getUserList(){
List<SysUser> list = sysUserService.list(new QueryWrapper<>(null));
return list;
}
}
在发动类上设置对应的mapper扫描
@SpringBootApplication
@MapperScan("cn.itbluebox.springbootadminvue.mapper")
public class SpringbootAdminvueApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAdminvueApplication.class, args);
}
}
发动项目
拜访接口
http://localhost:8081/sys-user/list
拜访成功
在数上下文切换据库傍边增加一些数据
改写页面
三、成果封装ios下载
由于是前后端分离的项目,所以咱们有必要jsonp一致一个成果回来封装类,这样前后端交互的时分有个一致的标准,约好成果回来的数据是正常的或者遇到反常了。
这儿咱们用ios应用商店到了一个Result的类,这个用于咱们的异步一致回来的成果封装。一般来说,成果里边有几个要素必要的
- 是否成功,可用code表明(如200表明成功,400表明反常)
- 成果音讯
- 成果数据
package cn.itbluebox.springbootadminvue.common.lang;
import lombok.Data;
import java.io.Serializable;
@Data
public class Result implements Serializable {
private int code;
private String msg;
private Object data;
public static Result success(Object data){
return success(200,"操作成功",data);
}
public static Result success(int code,String msg,Object data){
Result r = new Result();
r.setData(data);
r.setMsg(msg);
r.setCode(code);
return r;
}
public static Result fail(String msg){
return fail(400,msg, null);
}
public static Result fail(int code,String msg,Object data){
Result r = new Result();
r.setData(data);
r.setMsg(msg);
r.setCode(code);
return r;
}
}
修正SysUserControlle按钮开关r
/**
* <p>
* 前端操控器
* </p>
* @author itbluebox
* @since 2022-05-26
*/
@RestController
@RequestMapping("/sys-user")
public class SysUserController extends BaseController {
@Autowired
private SysUserService sysUserService;
@GetMapping("list")
public Result getUserList(){
List<SysUser> list = sysUserService.list(new QueryWrapper<>(null));
return Result.success(list);
}
}
http://localhost:8081/sys-user/list
四、大局反常处理
有时分不可避免服务器报错的情况,假如不装备反常处理机按钮开关制,就会默许回来tomcat或者nginx的5XX页面,对一般用户来说,不ios模拟器太友爱,用户也不明白什么情况。这时分需求咱们程序员规划回来一个友爱简单的格局给前进程上下文端。
处理办法如下:经过运用@ControllerAdvice来进行一致反常处理,
@ExceptionHandler(value = RuntimeException.class)
来指定捕获的Exception各个类型反常,这个反常的处理,是大局的,一切相似的反常,都会跑到这个当地处理。
步骤二jsonobject、界说大局反常处理,
@ControllerAdvice
表明界说大局操控器反常处理,
@ExceptionHandler
表明针对性反常处理,可对每种反常针对性处理。
/**
* 大局反常处理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseStatus(HttpStatus.FORBIDDEN)
@ExceptionHandler(value = AccessDeniedException.class)
public Result handler(AccessDeniedException e) {
log.info("security权限缺乏:----------------{}", e.getMessage());
return Result.fail("权限缺乏");
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result handler(MethodArgumentNotValidException e) {
log.info("实体校验反常:----------------{}", e.getMessage());
BindingResult bindingResult = e.getBindingResult();
ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
return Result.fail(objectError.getDefaultMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
public Result handler(IllegalArgumentException e) {
log.error("Assert反常:----------------{}", e.getMessage());
return Result.fail(e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = RuntimeException.class)
public Result handler(RuntimeException e) {
log.error("运行时反常:----------------{}", e);
return Result.fail(e.getMessage());
}
}
五、整合Spring Security
1、Spring Security介绍
Spring Security是一个能够为基于Spring的企业运用体系供给声明式的安全拜访操控处理方案的安全结构。
它供给了一组能够在Spring运用上下文中装备的Bea按钮n,
充分利用了Spring IoC,DI(操控回转Inversion of Control ,DI:Dependency Injjson格式怎么打开ection 依靠注入)和AOP(面向切面编程)功用,
为运用体系供给声明式的安全拜访操控功用,削减了为企业体系安全操控编写大量重复代码的作按钮的工作原理业。
流程说明按钮符号:
客户端建议一个恳求,进入 Security 过滤器链。
当到 LogoutFilter 的时分判别是否是登出途径,假上下文语境如是登出途径则到 logoutHandler ,假如登出成功则到 logoutSuccessHandler 登出上下文什么意思成功处理。假如不是登出途iOS径则直接进入下一个过滤器。
当到 UsernamePasswordAuthenticationFilter 的时分判别是否为登录途径,假如是,则进入该过滤器进行登录操作,假如登录失利则到 AuthenticationFailurios15eHandler ,登录失利处理器处理,假如登录成功则到 Authen上下文图ticationSuccessHandler 登录成功处理器处理,假如不是登录恳上下文字间距怎么调求则不进入该过滤器。
进入认证BasicAuthenticationFilter进行用户认证,成功的话会把json数据认证了的成果写入到SecurityContextHolder中SecurityContext的联系上下文特点authenticatio上下文英语n上面。
假如认证失利就会交给AuthenticationEntryPoint认证失利处理类,或者抛出反常被后json格式续ExceptionTranslationFilter过滤器处理反常,上下文语境假如是AuthenticationException就交给AuthenticationEntryPoint处理,假如是AccessDeniedException反常则交给AccessDeniedHandler处理。
当到 FilterSecurityInterceptor 的时分会拿到 uri ,依据 uri 去找对应的鉴权办理器,鉴权办理器做鉴权作业,鉴权成功则到 Controlljson格式怎么打开er 层,不然到 AccessDeniedHan按钮的工作原理dler 鉴权失利处理器处理。
2、引进Security与jwt
首要咱们导入security包按钮英文翻译,由于咱们前后端交互用户凭证用的是JWT,所以咱们也导入jwt的相关上下文字间距怎么调包,然后由于验证码的json存储需求用到redis,所以引进redis。最后为了一些东西类,咱们引进hutool。
-
pom.xml
<!-- springboot security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>
<!-- hutool东西类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
从头发动项目
拜访:httios应用商店p://localhost:808按钮1
用户名:ujsonobjectserios越狱
暗码:操控台现已输出
http://localhost:8081/sys-user/list
由于每次发动暗码都会改变,所以咱们经过装备文件来装备一下默许的用户名和暗码:
application.yml
spring:
security:
user:
name: user
password: 111111
3、设置Redis的东西类
package cn.itbluebox.springbootadminvue.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时刻
*
* @param key 键
* @param time 时刻(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 依据key 获取过期时刻
*
* @param key 键 不能为null
* @return 时刻(秒) 回来0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判别key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删去缓存
*
* @param key 能够传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
//============================String=============================
/**
* 一般缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 一般缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失利
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 一般缓存放入并设置时刻
*
* @param key 键
* @param value 值
* @param time 时刻(秒) time要大于0 假如time小于等于0 将设置无限期
* @return true成功 false 失利
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递加
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递加因子有必要大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要削减几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子有必要大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
//================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的一切键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失利
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时刻
*
* @param key 键
* @param map 对应多个键值
* @param time 时刻(秒)
* @return true成功 false失利
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,假如不存在将创立
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失利
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,假如不存在将创立
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时刻(秒) 留意:假如已存在的hash表有时刻,这儿将会替换原有的时刻
* @return true 成功 false失利
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删去hash表中的值
*
* @param key 键 不能为null
* @param item 项 能够使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判别hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递加 假如不存在,就会创立一个 并把新增后的值回来
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要削减记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
//============================set=============================
/**
* 依据key获取Set中的一切值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 依据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可所以多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时刻(秒)
* @param values 值 可所以多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可所以多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表一切值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 经过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,顺次类推;index<0时,-1,表尾,-2倒数第二个元素,顺次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时刻(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时刻(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 依据索引修正list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//================有序调集 sort set===================
/**
* 有序set增加元素
*
* @param key
* @param value
* @param score
* @return
*/
public boolean zSet(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public long batchZSet(String key, Set<ZSetOperations.TypedTuple> typles) {
return redisTemplate.opsForZSet().add(key, typles);
}
public void zIncrementScore(String key, Object value, long delta) {
redisTemplate.opsForZSet().incrementScore(key, value, delta);
}
public void zUnionAndStore(String key, Collection otherKeys, String destKey) {
redisTemplate.opsForZSet().unionAndStore(key, otherKeys, destKey);
}
/**
* 获取zset数量
* @param key
* @param value
* @return
*/
public long getZsetScore(String key, Object value) {
Double score = redisTemplate.opsForZSet().score(key, value);
if(score==null){
return 0;
}else{
return score.longValue();
}
}
/**
* 获取有序集 key 中成员 member 的排名 。
* 其中有序集成员按 score 值递减 (从大到小) 排序。
* @param key
* @param start
* @param end
* @return
*/
public Set<ZSetOperations.TypedTuple> getZSetRank(String key, long start, long end) {
return redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
}
}
4、设置RedisConfig
package cn.itbluebox.springbootadminvue.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(new ObjectMapper());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
六、用户认证
首要咱们来处理用户认证问题,分为初次登陆联系上下文,和二次认证。
初次登录认证:用户名、暗码和验证码完结登录
二次to上下文图ken认证:恳求头带着Jwt进行身份认证
运用用户名暗码来ios是什么意思登录的,然后咱们还想增加图片验证码,那么security给咱们供给的UsernamePasswordAuthenticationFilter能运用吗?
首要securitios是什么意思y的一切过滤器都是没有图片验证码ios14.4.1更新了什么这回事的,看起来不适用了。其实这儿咱们能够灵敏点,假上下文语境如你仍然想沿袭自带的UsernamePasswordAuthenticationF上下文字间距怎么调ilter上下文语境,那么咱们就在这过滤器之前增加一个图片验证码过滤器。当然了咱们也能够经过自界说过滤器承继UsernamePasswordAuthenticationFilt联系上下文er,然后自己把验证码验JSON证逻辑和认证逻辑写在一起,这也是一种处理办法。
咱们这次处理办法是在UsernamePasios15swordAuthenticjson怎么读ationFilter之前自界说一个图片过滤器CaptchaFilter,提早json格式校验验证码是否正确,这样咱们就能够运用UsernamePasswordjson格式怎么打开AuthenticationFilter了,然后登录正常或失利咱们都能够经过对应的Ha上下文无关文法ndler来按钮符号回来咱们特定格局的封装成果数据。
1、生成验证码
首要咱们先生成验证码,之前咱们现已引用了google的验证码生成器,咱们先来装备一下图片验证码的生成规矩:
KaptchaConfig
package cn.itbluebox.springbootadminvue.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
@Bean
public DefaultKaptcha producer() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "4");
properties.put("kaptcha.image.height", "40");
properties.put("kaptcha.image.width", "120");
properties.put("kaptcha.textproducer.font.size", "30");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
package cn.itbluebox.springbootadminvue.common.lang;
public class Const {
public final static String CAPTCHA_KEY = "captcha";
}
package cn.itbluebox.springbootadminvue.controller;
import cn.hutool.core.map.MapUtil;
import cn.itbluebox.springbootadminvue.common.lang.Const;
import cn.itbluebox.springbootadminvue.common.lang.Result;
import com.google.code.kaptcha.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.UUID;
@RestController
public class AuthController extends BaseController {
@Autowired
Producer producer;
@GetMapping("/captcha")
public Result captcha() throws IOException {
String key = UUID.randomUUID().toString();
String code = producer.createText();
BufferedImage image = producer.createImage(code);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image,"jpg",outputStream);
BASE64Encoder encoder = new BASE64Encoder();
String str = "data:image/jpeg;base64,";
String base64Img = str + encoder.encode(outputStream.toByteArray());
redisUtil.hset(Const.CAPTCHA_KEY,key,code,120);
return Result.success(
MapUtil.builder()
.put("token",key)
.put("https://juejin.im/post/7118206768634675207/captchaImg",base64Img)
.build()
);
}
}
留意在上面的BaseControlljson格式怎么打开er 傍边增加一些新内容
public class BaseController {
@Autowired
HttpServletRequest req;
@Autowired
RedisUtil redisUtil;
}
发动
先发动Redis
发动项目
2、前端完结验证码显现
发动前端项目
去除mokejson解析
3、处理跨域问题
@Configuration
public class CorsConfig implements WebMvcConfigurer {
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addExposedHeader("Authorization");
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
// .allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}
4、设置过滤器
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] URL_WHITELIST = {
"/login",
"/logout",
"/captcha",
"/favicon.ico",
};
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
//登录装备
.formLogin()
/* .successHandler()
.failureHandler()
*/
//禁用session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//装备阻拦规矩
.and()
.authorizeRequests()
.antMatchers(URL_WHITELIST).permitAll()
.anyRequest().authenticated()
//反常处理器
//装备自界说的过滤器
;
}
}
从头发动项目
改写json解析页面
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
Result result = Result.fail("用户名或暗码过错");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
ServletOutputStream outputStream = response.getOutputStream();
//生成jwt 。 并放置到恳求头中
Result result = Result.success("成功");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginFailureHandler loginFailureHandler;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
private static final String[] URL_WHITELIST = {
"/login",
"/logout",
"/captcha",
"/favicon.ico",
};
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
//登录装备
.formLogin()
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
//禁用session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//装备阻拦规矩
.and()
.authorizeRequests()
.antMatchers(URL_WHITELIST).permitAll()
.anyRequest().authenticated()
//反常处理器
//装备自界说的过滤器
;
}
}
改写页面
5、设置点击改写二维码
<el-image
@click="getCaptcha" :src="https://juejin.im/post/7118206768634675207/captchaImg" ></el-image>
设置点击后清空对应的内容
6、设置验证码过滤器
(1)设置验证码过错反jsonobject常
public class CaptchaException extends AuthenticationException {
public CaptchaException(String msg) {
super(msg);
}
}
(2)验证码过滤器
package cn.itbluebox.springbootadminvue.security;
import cn.itbluebox.springbootadminvue.common.exception.CaptchaException;
import cn.itbluebox.springbootadminvue.common.lang.Const;
import cn.itbluebox.springbootadminvue.utils.RedisUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CaptchaFilter extends OncePerRequestFilter {
@Autowired
private RedisUtil redisUtil;
@Autowired
private LoginFailureHandler loginFailureHandler;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String url = request.getRequestURI();
if("/login".equals(url) && request.getMethod().equals("POST") ){
try{
//校验验证码
validate(request);
//假如不正确,就跳转到认证失利处理器
}catch (CaptchaException e){
//交给失利的处理器(认证失利处理器)
loginFailureHandler.onAuthenticationFailure(request,response,e);
}
}
filterChain.doFilter(request,response);
}
//校验逻辑
private void validate(HttpServletRequest request) {
String code = request.getParameter("code");
String key = request.getParameter("token");
if(StringUtils.isBlank(code) || StringUtils.isBlank(key)){
throw new CaptchaException("验证码过错");
}
if(!code.equals(redisUtil.hget(Const.CAPTCHA_KEY,key))){
throw new CaptchaException("验证码过错");
}
//一次性运用
redisUtil.hdel(Const.CAPTCHA_KEY);
}
}
7、装备过滤器
//反常处理器
//装备自界说的过滤器
.and()
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class);
七、完结登录并生成JWT
登录成功之后json前端就能够获取到了jwt的信息,
前端中咱们是保存在了store中,
一起也保存在了localStorage中,
然后每次axios恳求之前,
咱们都会增加上咱们的恳求头信息,能够回顾一下。
1、编写JwtUtils
package cn.itbluebox.springbootadminvue.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
@ConfigurationProperties(prefix = "itbluebox.jwt")
public class JwtUtils {
private long expire;
private String secret;
private String header;
//生成 JWT
public String generateToken(String username){
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * expire);
return Jwts.builder()
.setHeaderParam("typ","JWT")
.setSubject(username)
.setIssuedAt(nowDate)
.setExpiration(expireDate)//7天逾期
.signWith(SignatureAlgorithm.ES512,secret)
.compact();
}
//解析JWT
public Claims getClaimByToken(String jwt){
try{
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
}catch (Exception e){
return null;
}
}
//JWT 是否过期的办法
public boolean isTokenExpired(Claims claims){
return claims.getExpiration().before(new Date());
}
}
2、编写Jwt对应的装备文件
itbluebox:
jwt:
header: Authorization
expire: 604800 #7天,秒单位
secret: 212wdseqw23red232r3rds23r21212hg #填够32位
八、身份认证 – 1
登录成功之后前端就能够获取到了jwt的信息,前端中咱们是保存在了store中,一起也保存在了localStorios是什么意思age中,上下文然后每次axios恳求之前,咱们都会增加上咱们的恳求头信息
所以后端进行用户身份识别的时分,咱们需求经过恳求头中获取jwt,然后解分出咱们的用户名,这样咱们就能够知道是谁在拜访咱们的接口啦,然后判别用户是否有权限等操作。
那么咱们自界说一个过滤器用来进行识别jwt。
1、JwtAu按钮英文thenticationFilter
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
private JwtUtils jwtUtils;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtils.getHeader());
if(StrUtil.isBlankOrUndefined(jwt)){
chain.doFilter(request,response);
return;
}
Claims claim = jwtUtils.getClaimByToken(jwt);
if(ObjectUtils.isEmpty(claim)){
throw new JwtException("token 反常");
}
if(jwtUtils.isTokenExpired(claim)){
throw new JwtException("token现已过期");
}
String username = claim.getSubject();
//获取用户的权限信息
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username,null,null);
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request,response);
}
}
2、完善Secu上下文切换rityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private LoginFailureHandler loginFailureHandler;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
CaptchaFilter captchaFilter;
@Bean
JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
return jwtAuthenticationFilter;
}
private static final String[] URL_WHITELIST = {
"/login",
"/logout",
"/captcha",
"/favicon.ico",
};
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
//登录装备
.formLogin()
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
//禁用session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//装备阻拦规矩
.and()
.authorizeRequests()
.antMatchers(URL_WHITELIST).permitAll()
.anyRequest().authenticated()
//反常处理器
//装备自界说的过滤器
.and()
.addFilter(jwtAuthenticationFilter())
.addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)
;
}
}
3、建议恳求进程上下文测验
http://localhost:8081/sys-user/list
九、用户认证失利或权限缺乏反常处理
1、认证联系上下文失利处理器
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
}
}
2、反常处理器
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
}
}
3、Securiios15tyConfig傍边
@Autowired
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
JwtAccessDeniedHandler jwtAccessDeniedHandler;
//反常处理器
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
4、完善JwtAccessDeniedHandler和JwtAuthenticationEntryPoint
(1)JwtAccessDeniedHandler
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
ServletOutputStream outputStream = response.getOutputStream();
Result result = Result.fail(accessDeniedException.getMessage());
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
(2)JwtAuthentication按钮开关符号EntryPoint
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
ServletOutputStream outputStream = response.getOutputStream();
Result result = Result.fail("请先登录");
outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
5、内容测验
向接口发送恳求:http://localhost:8081/sys-user/list
6、用户登录查库
UserDetailSerios下载viceImpl
SysUser sysUser = sysUserService.getByUserName(username);
public interface SysUserService extends IService<SysUser> {
SysUser getByUserName(String username);
}
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
@Override
public SysUser getByUserName(String username) {
return getOne(new QueryWrapper<SysUser>().eq("username",username));
}
}
package cn.itbluebox.springbootadminvue.security;
import cn.hutool.core.lang.Assert;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class AccountUser implements UserDetails {
private Long userId;
private String password;
private final String username;
private final Collection<? extends GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
public AccountUser(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(userId, username, password, true, true, true, true, authorities);
}
public AccountUser(Long userId, String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
Assert.isTrue(username != null && !"".equals(username) && password != null,
"Cannot pass null or empty values to constructor");
this.userId = userId;
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
完善SecurityConfig
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
完善UserDetailSe执行上下文rviceImpl
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private SysUserService sysUserService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser sysUser = sysUserService.getByUserName(username);
if(ObjectUtils.isEmpty(sysUser)){
throw new UsernameNotFoundException("用户名或暗码不正确");
}
return new AccountUser(sysUser.getId(),sysUser.getUsername(),sysUser.getPassword(),getUserAuthority(sysUser.getId()));
}
/*
* 获取用户权限信息(人物,菜单权限)
* */
public List<GrantedAuthority> getUserAuthority(Long userId){
return null;
}
}
完善SecurityConfig
@Autowired
UserDetailServiceImpl userDetailService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userDetailService);
}
@RestController
@RequestMapping("/sys-user")
public class SysUserController extends BaseController {
@Autowired
private SysUserService sysUserService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping("list")
public Result getUserList(){
List<SysUser> list = sysUserService.list(new QueryWrapper<>(null));
return Result.success(list);
}
@GetMapping("list/pass")
public Result pass(){
//加密后的暗码
String password = bCryptPasswordEncoder.encode("111111");
boolean matches = bCryptPasswordEncoder.matches("111111", password);
System.out.println("匹配成果:"+matches);
return Result.success(password);
}
}
编写一个测验办法生成一下暗码
@SpringBootTest
class SpringbootAdminvueApplicationTests {
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Test
void contextLoads() {
String password = bCryptPasswordEncoder.encode("111111");
boolean matches = bCryptPasswordEncoder.matches("111111", password);
System.out.println("匹配成果:"+matches);
System.out.println(password);
}
}
在数据库json格式怎么打开傍边增加对应的账号和暗码
将装备文件傍边SpringSecurity的内容注释掉
server:
port: 8081
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/itzheng-vue-admin?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: root
# security:
# user:
# name: user
# password: 111111
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
itbluebox:
jwt:
header: Authorization
expire: 604800 #7天,秒单位
secret: 212wdseqw23red232r3rds23r21212hg #填够32位
发送登录恳求
7、按钮拼音用户授权
然后关于权限JSON部分,也是sec按钮开关怎么接urity的重要功用,当用户认证成功之后,咱们就知道谁在拜访体系接口,这是又有一个问题,便是这个用户有上下文什么意思没有权限来拜访咱们这个接口呢,要处理这个问题,咱axios们需求知执行上下文道用户有哪些权限,哪些人物,这样security才干axios和ajax区别咱们做权限判别。
之前咱们现已界说及几张表,用户、人物、菜单、以及一些关联表,一般当权限粒度比较细的时分,咱们都经过判别用户有没有此菜单或操作的权限,而不是经过人物判别,而用户和菜单是不直接做关联的,是经过用户具有哪些人物,然后人物具有哪些菜单权限这样来获得的。
问题1:咱们是在哪里赋予用户权限的?有两上下文什么意思个当地:
- 1、用户登录,调用axios和ajax区别调用UserDetailsService.loadUserByUsernjson数据ame()办法时分能够回来用户的权限信息。
- 2、接口调用进行身ios是苹果还是安卓份认证过滤器时分JWTAuthenticationFilter,需求回来用户权限jsonp信息
问题2:在哪里决定什么接口需求什么权限?
Security内置的权限注解:
- @PreAuthorize:办法履行前进行权限查看
- @PostAuthorize:办法履行后进行权限查看
- @Secured:相似于@PreAuthorize
能够在Controller的办法前增加jsonobject这些注解表明接口需求什么权限。
@RestController
@RequestMapping("/sys-user")
public class SysUserController extends BaseController {
@Autowired
private SysUserService sysUserService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
//协作权限具有admin的才干拜访
@PreAuthorize("hasRole('admin')")
@GetMapping("list")
public Result getUserList(){
List<SysUser> list = sysUserService.list(new QueryWrapper<>(null));
return Result.success(list);
}
//一般用户、超级办理员
//当时办法只有具有sys:user:list的权限的办理员才干拜访办法
@PreAuthorize("hasAnyAuthority('sys:user:list')")
@GetMapping("list/pass")
public Result pass(){
//加密后的暗码
String password = bCryptPasswordEncoder.encode("111111");
boolean matches = bCryptPasswordEncoder.matches("111111", password);
System.out.println("匹配成果:"+matches);
return Result.success(password);
}
}
8、完善权ios是什么意思限办法
/*
* 获取用户权限信息(人物,菜单权限)
* */
public List<GrantedAuthority> getUserAuthority(Long userId){
//人物(ROLE_admin)、菜单操作权限、sys:user:list
String authority = sysUserService.getUserAuthorityInfo(userId); //ROLE_admin,ROLE_normal,sys:user:list,....
return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
}
String getUserAuthorityInfo(Long userId);
在SysUserServiceImpl傍边
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
@Autowired
private SysRoleService sysUserService;
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser getByUserName(String username) {
return getOne(new QueryWrapper<SysUser>().eq("username",username));
}
@Override
public String getUserAuthorityInfo(Long userId) {
//经过用户id获取对应的人物信息
String authority = null;
//获取人物
//经过用户id,查出对应的用户人物id,经过人物id查询,对运用户的人物信息
List<SysRole> roles = sysUserService.list(new QueryWrapper<SysRole>().inSql("id", "select role_id from sys_user_role where user_id = " + userId));
if(roles.size() > 0){
String roleCode = roles.stream().map(r -> "ROLE_"+r.getCode()).collect(Collectors.joining(","));
authority = roleCode;
}
//获取菜单操作权限
List<Long> menuIds = sysUserMapper.getNavMenuIds(userId);
return null;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.itbluebox.springbootadminvue.mapper.SysUserMapper">
<select id="getNavMenuIds" resultType="java.lang.Long">
select
DISTINCT rm.menu_id
from
sys_user_role ur
left join sys_role_menu rm on ur.role_id = rm.role_id
where ur.user_id = #{userId}
</select>
</mapper>
完善SysUserServiceImpl
/**
* <p>
* 服务完结类
* </p>
* @author itbluebox
* @since 2022-05-26
*/
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
@Autowired
private SysRoleService sysUserService;
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysMenuService sysMenuService;
@Override
public SysUser getByUserName(String username) {
return getOne(new QueryWrapper<SysUser>().eq("username",username));
}
@Override
public String getUserAuthorityInfo(Long userId) {
//经过用户id获取对应的人物信息
String authority = null;
//获取人物编码
//经过用户id,查出对应的用户人物id,经过人物id查询,对运用户的人物信息
List<SysRole> roles = sysUserService.list(new QueryWrapper<SysRole>().inSql("id", "select role_id from sys_user_role where user_id = " + userId));
if(roles.size() > 0){
String roleCode = roles.stream().map(r -> "ROLE_"+r.getCode()).collect(Collectors.joining(","));
authority = roleCode.concat(",");
}
//获取菜单操作权限
List<Long> menuIds = sysUserMapper.getNavMenuIds(userId);
if(menuIds.size() > 0){
List<SysMenu> sysMenus = sysMenuService.listByIds(menuIds);
String menuPerms = sysMenus.stream().map(m -> m.getPerms()).collect(Collectors.joining(","));
authority = authority.concat(menuPerms);
}
return authority;
}
}
完善JwtAuthentios是苹果还是安卓icationFilter
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private UserDetailServiceImpl userDetailService;
@Autowired
private SysUserService sysUserService;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtils.getHeader());
if(StrUtil.isBlankOrUndefined(jwt)){
chain.doFilter(request,response);
return;
}
Claims claim = jwtUtils.getClaimByToken(jwt);
if(ObjectUtils.isEmpty(claim)){
throw new JwtException("token 反常");
}
if(jwtUtils.isTokenExpired(claim)){
throw new JwtException("token现已过期");
}
String username = claim.getSubject();
SysUser sysUser = sysUserService.getByUserName(username);
//获取用户的权限信息
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(username,null,userDetailService.getUserAuthority(sysUser.getId()));
SecurityContextHolder.getContext().setAuthentication(token);
chain.doFilter(request,response);
}
}
9、测验运行
http://localhost:8081/captcha上下文什么意思
建议获取验证码恳求
建议登录按钮开关恳按钮英文翻译求
http://localhost:808按钮英文1/login
复制token
粘贴到回去信息的header傍边
建议获取信息恳求:http://localhost:8081/sys-user/list按钮