手把手0基础项目实战(三)——教你开发一套电商平台的安全框架

写在最前

本文是《手把手项目实战系列》的第三篇文章,预告一下,整个系列会介绍如下内容:

  • 《手把手0基础项目实战(一)——教你搭建一套可自动化构建的微服务框架(SpringBoot+Dubbo+Docker+Jenkins)》
  • 《手把手0基础项目实战(二)——微服务架构下的数据库分库分表实战》
  • 《手把手0基础项目实战(三)——教你开发一套安全框架》
  • 《手把手0基础项目实战(四)——电商订单系统架构设计与实战(分布式事务一致性保证)》
  • 《手把手0基础项目实战(五)——电商系统的缓存策略》
  • 《手把手0基础项目实战(六)——基于配置中心实现集群配置的集中管理和熔断机制》
  • 《手把手0基础项目实战(七)——电商系统的日志监控方案》
  • 《手把手0基础项目实战(八)——基于JMeter的系统性能测试》

几乎所有的Web系统都需要登录、权限管理、角色管理等功能,而且这些功能往往具有较大的普适性,与系统具体的业务关联性较小。因此,这些功能完全可以被封装成一个可配置、可插拔的框架,当开发一个新系统的时候直接将其引入、并作简单配置即可,无需再从头开发,极大节约了人力成本、时间成本。

在Java Web领域,有两大主流的安全框架,Spring Security和Apache Shiro。他们都能实现用户鉴权、权限管理、角色管理、防止Web攻击等功能,而且这两套开源框架都已经过大量项目的验证,趋于稳定成熟,可以很好地为我们的项目服务。

本文将带领大家从头开始实现一套安全框架,该框架与Spring Boot深度融合,从而能够帮助大家加深对Spring Boot的理解。这套框架中将涉及到如下内容:

  • Spring Boot AOP
  • Spring Boot 全局异常处理
  • Spring Boot CommandLineRunner
  • Java 反射机制
  • 分布式系统中Session的集中式管理

本文将从安全框架的设计与实现两个角度带领大家完成安全框架的开发,废话不多说,现在开始吧~

项目完整源码下载

https://github.com/bz51/SpringBoot-Dubbo-Docker-Jenkins


1. 安全框架的设计

1.1 开发目标

在所有事情开始之前,我们首先要搞清楚,我们究竟要实现哪些功能?

1.用户登录 所有系统都需要登录功能,这毫无疑问,也不必多说。
2.角色管理 每个用户都有且仅有一种角色,比如:系统管理员、普通用户、企业用户等等。管理员可以添加、删除、查询、修改角色信息。
3.权限管理 每种角色可以拥有不同的权限,管理员可以创建、修改、查询、删除权限,也可以为某一种角色添加、删除权限。
4.权限检测 用户调用每一个接口,都需要校验该用户是否具备调用该接口的权限。

当我们明确了开发目标之后,下面就需要基于这些目标,设计我们的系统。我们首先要做的就是要搞清楚“用户”、“角色”、“权限”的定义以及他们之间的关系。这在领域驱动设计中被称为“领域模型”。

1.2 领域模型

  • 权限:* 权限表示某一用户是否具有操作某一资源的能力。* 权限一般用“资源名称:操作名称”来表示。比如:创建用户的权限可以用“user:create”来表示,删除用户的权限可以用“user:delete”来表示。* 在Web系统中,权限和接口呈一一对应关系,比如:“user:create”对应着创建用户的接口,“user:delete”对应着删除用户的接口。因此,权限也可以理解成一个用户是否具备操作某一个接口的能力。
  • 角色:* 角色是一组权限的集合。角色规定了某一类用户共同具备的权限集合。* 比如:超级管理员这种角色拥有“user:create”、“user:delete”等权限,而普通用户只有“user:create”权限。* 从领域模型中可知,角色和权限之间呈多对多的聚合关系,即一种角色可以包含多个权限,一个权限也可以属于多种角色,并且权限可以脱离于角色而单独存在,因此他们之间是一种弱依赖关系——聚合关系。
  • 用户:* 用户和角色之间呈多对一的聚合关系,即一个用户只能属于一种角色,而一种角色却可以包含多个用户。并且角色可以脱离于用户单独存在,因此他们之间是一种弱依赖关系——聚合关系。

1.3 数据结构设计

当我们捋清楚了“权限”、“用户”、“角色”的定义和他们之间的关系后,下面我们就可以基于这个领域模型设计出具体的数据存储结构。

为了能够方便地给每一个接口标注权限,我们需要自定义三个注解@Login@Role@Permission

  • @Login:用于标识当前接口是否需要登录。当接口使用了这个注解后,用户只有在登录后才能访问。* @Role("角色名"):用于标识允许调用当前接口的角色。当接口使用了这个注解后,只有指定角色的用户才能调用本接口。* @Permission("权限名"):用于标识允许调用当前接口的权限。当接口使用了这个注解后,只有具备指定权限的用户才能调用本接口。1.4 接口权限信息初始化流程

要使得这个安全框架运行起来,首先就需要在系统初始化完成前,初始化所有接口的权限、角色等信息,这个过程即为“接口权限信息初始化流程”;然后在系统运行期间,如果有用户请求接口,就可以根据这些权限信息判断该用户是否有权限访问接口。

这一小节主要介绍接口权限信息初始化流程,不涉及任何实现细节,实现的细节将在本文的实现部分介绍。

1.当Spring完成上下文的初始化后,需要扫描本项目中所有Controller类;
2.再依次扫描Controller类中的所有方法,获取方法上的@GetMapping@PostMapping@PutMapping@DeleteMapping,通过这些注解获取接口的URL、请求方式等信息;
3.同时,获取方法上的@Login@Role@Permission,通过这些注解,获取该接口是否需要登录、允许访问的角色以及允许访问的权限信息;
4.将每个接口的权限信息、URL、请求方式存储在Redis中,供用户调用接口是鉴权使用。

1.5 用户鉴权流程

1.所有的用户请求在被执行前都会被系统拦截,从请求中获取请求的URL和请求方式;
2.然后从Redis中查询该接口对应的权限信息;
3.若该接口需要登录,并且当前用户尚未登录,则直接拒绝;
4.若该接口需要登录,并且拥有已经登录,那么需要从请求头中解析出SessionID,并到Redis中查询该用户的权限信息,然后拿着用户的权限信息、角色信息和该接口的权限信息、角色信息进行比对。若通过鉴权,则执行该接口;若未通过鉴权,则直接拒绝请求。

2. 安全框架的实现

2.1 注解的实现

本套安全框架一共定义了四个注解:@AuthScan@Login@Role@Permission

2.1.1 @AuthScan

该注解用来告诉安全框架,本项目中所有Controller类所在的包,从而能够帮助安全框架快速找到Controller类,避免了所有类的扫描。

它有且仅有一个参数,用来指定Controller所在的包:@AuthScan("com.gaoxi.controller")。它的代码实现如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthScan {public String value();
} 

注解顾名思义,它是用来在代码中进行标注,它本身不承载任何逻辑,通过注解

  • @Retention 它解释说明了这个注解的的存活时间。它的取值如下:* RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。* RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。* RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
  • @Documented 顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。* @Target 当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。* ElementType.ANNOTATION_TYPE:可以给一个注解进行注解* ElementType.CONSTRUCTOR:可以给构造方法进行注解* ElementType.FIELD:可以给属性进行注解* ElementType.LOCAL_VARIABLE:可以给局部变量进行注解* ElementType.METHOD:可以给方法进行注解* ElementType.PACKAGE:可以给一个包进行注解* ElementType.PARAMETER:可以给一个方法内的参数进行注解* ElementType.TYPE:可以给一个类型进行注解,比如类、接口、枚举

2.1.2 @Login

这个注解用于标识指定接口是否需要登录后才能访问,它有一个默认的boolean类型的值,用于表示是否需要登录,其代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Login {// 是否需要登录(默认为true)public boolean value() default true;

} 

2.1.3 @Role

该注解用于指定允许访问当前接口的角色,其代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Role {public String value();
} 

2.1.4 @Permission

该注解用于指定允许访问当前接口的权限,其代码如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Permission {public String value();
} 

2.2 权限信息初始化过程

上文中提到,注解本身不含任何业务逻辑,它只是在代码中起一个标识的作用,那么怎么才能让注解“活”起来?这就需要通过反射机制来获取注解。

2.2.1 在接口上声明权限信息

当完成这些注解的定义后,接下来就需要使用他们,如下面代码所示:

public interface ProductController {/** * 创建产品 * @param prodInsertReq 产品详情 * @return 是否创建成功 */@PostMapping("product")@Login@Permission("product:create")public Result createProduct(ProdInsertReq prodInsertReq);} 

ProductController是一个Controller类,它提供了处理产品的各种接口。简单起见,这里只列出了一个创建产品的接口。 @PostMapping是SpringMVC提供的注解,用于标识该接口的访问路径和访问方式。 @Login声明了该接口需要登录后才能访问。 @Permission声明了用户只有拥有product:create权限才能访问该接口。

2.2.2 初始化权限信息

当系统初始化的时候,需要加载接口上的这些权限信息,存储在Redis中。在系统运行期间,当有用户请求接口的时候,系统会根据接口的权限信息判断用户是否有访问接口的权限。权限信息初始化过程的代码如下:

/**
 * @author 大闲人柴毛毛
 * @date 2017/11/1 上午10:04
 *
 * @description 初始化权限信息
 */
@AuthScan("com.gaoxi.controller")
@Component
public class InitAuth implements CommandLineRunner {@Overridepublic void run(String... strings) throws Exception {// 加载接口访问权限loadAccessAuth();}……
} 
  • 上述代码定义了一个InitAuth类,该类实现了CommandLineRunner接口,该接口中含有run()方法,当Spring的上下文初始化完成后,就会调用run(),从而完成权限信息的初始化过程。
  • 该类使用了@AuthScan("com.gaoxi.controller")注解,用于标识当前项目Controller类所在的包名,从而避免扫描所有类,一定程度上加速系统初始化的速度。
  • @Component注解会在Spring容器初始化完成后,创建本类的对象,并加入IoC容器中。

下面来看一下loadAccessAuth()方法的具体实现:

 /** * 加载接口访问权限 */private void loadAccessAuth() throws IOException {// 获取待扫描的包名AuthScan authScan = AnnotationUtil.getAnnotationValueByClass(this.getClass(), AuthScan.class);String pkgName = authScan.value();// 获取包下所有类List<Class<?>> classes = ClassUtil.getClasses(pkgName);if (CollectionUtils.isEmpty(classes)) {return;}// 遍历类for (Class clazz : classes) {Method[] methods = clazz.getMethods();if (methods==null || methods.length==0) {continue;}// 遍历函数for (Method method : methods) {AccessAuthEntity accessAuthEntity = buildAccessAuthEntity(method);if (accessAuthEntity!=null) {// 生成keyString key = generateKey(accessAuthEntity);// 存至本地MapaccessAuthMap.put(key, accessAuthEntity);logger.debug("",accessAuthEntity);}}}// 存至RedisredisService.setMap(RedisPrefixUtil.Access_Auth_Prefix, accessAuthMap, null);logger.info("接口访问权限已加载完毕!"+accessAuthMap);} 
  • 首先会读取本类上的@AuthScan注解,并获取注解中声明了Controller类所在的包pkgName
  • pkgName是一个字符串,因此需要使用Java反射机制将字符串解析成Class对象。其解析过程通过工具包ClassUtil.getClasses(pkgName)完成,具体解析过程这里就不做详细介绍了,感兴趣的同学可以参阅本项目源码。
  • ClassUtil.getClasses(pkgName)解析之后,该包下的所有Controller类将会被解析成List<Class<?>>对象,然后遍历所有的Class对象;
  • 然后依次获取每个Class对象中的Method对象,并依次遍历Method对象,通过buildAccessAuthEntity(method)方法将一个个Method对象解析成AccessAuthEntity对象(具体解析过程在稍后介绍);
  • 最后将AccessAuthEntity对象存储在Redis中,供用户访问接口时使用。

这就是整个权限信息初始化的过程,下面详细介绍buildAccessAuthEntity(method)方法的解析过程,它究竟是如何将一个Mehtod对象解析成AccessAuthEntity对象?并且AccessAuthEntity对象的结构究竟是怎样的?

首先来看一下AccessAuthEntity的数据结构:

/**
 * @author 大闲人柴毛毛
 * @date 2017/11/1 上午11:05
 * @description 接口访问权限的实体类
 */
public class AccessAuthEntity implements Serializable {/** 请求 URL */private String url;/** 接口方法名 */private String methodName;/** HTTP 请求方式 */private HttpMethodEnum httpMethodEnum;/** 当前接口是否需要登录 */private boolean isLogin;/** 当前接口的访问权限 */private String permission;// setter/getter省略
} 

AccessAuthEntity用于存储一个接口的访问路径、访问方式和权限信息。在系统初始化的时候,Controller类中的每个Mehtod对象都会被buildAccessAuthEntity()方法解析成AccessAuthEntity对象。buildAccessAuthEntity()方法的代码如下所示:

/**
 * 构造AccessAuthEntity对象
 * @param method
 * @return
 */
private AccessAuthEntity buildAccessAuthEntity(Method method) {GetMapping getMapping = AnnotationUtil.getAnnotationValueByMethod(method, GetMapping.class);PostMapping postMapping = AnnotationUtil.getAnnotationValueByMethod(method, PostMapping.class);PutMapping putMapping= AnnotationUtil.getAnnotationValueByMethod(method, PutMapping.class);DeleteMapping deleteMapping = AnnotationUtil.getAnnotationValueByMethod(method, DeleteMapping.class);AccessAuthEntity accessAuthEntity = null;if (getMapping!=null&& getMapping.value()!=null&& getMapping.value().length==1&& StringUtils.isNotEmpty(getMapping.value()[0])) {accessAuthEntity = new AccessAuthEntity();accessAuthEntity.setHttpMethodEnum(HttpMethodEnum.GET);accessAuthEntity.setUrl(trimUrl(getMapping.value()[0]));}else if (postMapping!=null&& postMapping.value()!=null&& postMapping.value().length==1&& StringUtils.isNotEmpty(postMapping.value()[0])) {accessAuthEntity = new AccessAuthEntity();accessAuthEntity.setHttpMethodEnum(HttpMethodEnum.POST);accessAuthEntity.setUrl(trimUrl(postMapping.value()[0]));}else if (putMapping!=null&& putMapping.value()!=null&& putMapping.value().length==1&& StringUtils.isNotEmpty(putMapping.value()[0])) {accessAuthEntity = new AccessAuthEntity();accessAuthEntity.setHttpMethodEnum(HttpMethodEnum.PUT);accessAuthEntity.setUrl(trimUrl(putMapping.value()[0]));}else if (deleteMapping!=null&& deleteMapping.value()!=null&& deleteMapping.value().length==1&& StringUtils.isNotEmpty(deleteMapping.value()[0])) {accessAuthEntity = new AccessAuthEntity();accessAuthEntity.setHttpMethodEnum(HttpMethodEnum.DELETE);accessAuthEntity.setUrl(trimUrl(deleteMapping.value()[0]));}// 解析@Login 和 @Permissionif (accessAuthEntity!=null) {accessAuthEntity = getLoginAndPermission(method, accessAuthEntity);accessAuthEntity.setMethodName(method.getName());}return accessAuthEntity;
} 

该方法首先会获取当前Method上的XXXMapping四个注解,通过解析这些注解能够获取到当前接口的访问路径和请求方式,并将这两者存储在AccessAuthEntity对象中。

然后通过getLoginAndPermission方法,解析当前Method对象中的@Login 和@Permission信息,其代码如下所示:

/**
 * 获取指定方法上的@Login的值和@Permission的值
 * @param method 目标方法
 * @param accessAuthEntity
 * @return
 */
private AccessAuthEntity getLoginAndPermission(Method method, AccessAuthEntity accessAuthEntity) {// 获取@Permission的值Permission permission = AnnotationUtil.getAnnotationValueByMethod(method, Permission.class);if (permission!=null && StringUtils.isNotEmpty(permission.value())) {accessAuthEntity.setPermission(permission.value());accessAuthEntity.setLogin(true);return accessAuthEntity;}// 获取@Login的值Login login = AnnotationUtil.getAnnotationValueByMethod(method, Login.class);if (login!=null) {accessAuthEntity.setLogin(true);}accessAuthEntity.setLogin(false);return accessAuthEntity;
} 

该注解的解析过程由注解工具包AnnotationUtil.getAnnotationValueByMethod完成,具体的解析过程这里就不再赘述,感兴趣的同学请参阅项目源码。

到此为止,接口的访问路径、请求方式、是否需要登录、权限信息都已经解析成一个个AccessAuthEntity对象,并以“请求方式+访问路径”作为key,存储在Redis中。接口权限信息的初始化过程也就完成了!

2.2.3 用户鉴权

当用户请求所有接口前,系统都应该拦截这些请求,只有在权限校验通过的情况下才运行调用接口,否则直接拒绝请求。

基于上述需求,我们需要给Controller中所有方法执行前增加切面,并将用于权限校验的代码织入到该切面中,从而在方法执行前完成权限校验。下面就详细介绍在SpringBoot中AOP的使用。

  • 首先,我们需要在项目的pom中引入AOP的依赖:
<!-- AOP -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency> 
  • 创建切面类:* 在类上必须添加@Aspect注解,用于标识当前类是一个AOP切面类* 该类也必须添加@Component注解,让Spring初始化完成后创建本类的对象,并加入IoC容器中* 然后需要使用@Pointcut注解定义切点;切点描述了哪些类中的哪些方法需要织入权限校验代码。我们这里将所有Controller类中的所有方法作为切点。* 当完成切点的定义后,我们需要使用@Before注解声明切面织入的时机;由于我们需要在方法执行前拦截所有的请求,因此使用@Before注解。* 当完成上述设置之后,所有Controller类中的函数在被调用前,都会执行权限校验代码。权限校验的详细过程在authentication()方法中完成。
/**
 * @author 大闲人柴毛毛
 * @date 2017/11/2 下午7:06
 *
 * @description 访问权限处理类(所有请求都要经过此类)
 */
@Aspect
@Component
public class AccessAuthHandle {/** 定义切点 */@Pointcut("execution(public * com.gaoxi.controller..*.*(..))")public void accessAuth(){}/** * 拦截所有请求 */@Before("accessAuth()")public void doBefore() {// 访问鉴权authentication();}
} 
  • 权限校验过程* 该方法首先会获取当前请求的访问路径和请求方法;* 然后获取HTTP请求头中的SessionID,并从Redis中获取该SessionID对应的用户信息;* 然后根据接口访问路径和访问方法,从Redis中获取该接口的权限信息;到此为止,权限校验前的准备工作都已完成,下面就要进入权限校验过程了;
/**
 * 检查当前用户是否允许访问该接口
 */
private void authentication() {// 获取 HttpServletRequestHttpServletRequest request = getHttpServletRequest();// 获取 method 和 urlString method = request.getMethod();String url = request.getServletPath();// 获取 SessionIDString sessionID = getSessionID(request);// 获取SessionID对应的用户信息UserEntity userEntity = getUserEntity(sessionID);// 获取接口权限信息AccessAuthEntity accessAuthEntity = getAccessAuthEntity(method, url);// 检查权限authentication(userEntity, accessAuthEntity);
} 
  • authentication():* 首先判断当前接口是否需要登录后才允许访问,如果无需登录,那么直接允许访问;* 若当前接口需要登录后才能访问,那么判断当前用户是否已经登录;若尚未登录,则直接拒绝请求(通过抛出throw new CommonBizException(ExpCodeEnum.NO_PERMISSION)异常来拒绝请求,这由SpringBoot统一异常处理机制来完成,稍后会详细介绍);若已经登录,则开始检查权限信息;* 权限检查由checkPermission()方法完成,它会将用户所具备的权限和接口要求的权限进行比对;如果用户所具备的权限包含接口要求的权限,那么权限校验通过;反之,则通过抛异常的方式拒绝请求。
/**
 * 检查权限
 * @param userEntity 当前用户的信息
 * @param accessAuthEntity 当前接口的访问权限
 */
private void authentication(UserEntity userEntity, AccessAuthEntity accessAuthEntity) {// 无需登录if (!accessAuthEntity.isLogin()) {return;}// 检查是否登录checkLogin(userEntity, accessAuthEntity);// 检查是否拥有权限checkPermission(userEntity, accessAuthEntity);
}

/**
 * 检查当前用户是否拥有访问该接口的权限
 * @param userEntity 用户信息
 * @param accessAuthEntity 接口权限信息
 */
private void checkPermission(UserEntity userEntity, AccessAuthEntity accessAuthEntity) {// 获取接口权限String accessPermission = accessAuthEntity.getPermission();// 获取用户权限List<PermissionEntity> userPermissionList = userEntity.getRoleEntity().getPermissionList();// 判断用户是否包含接口权限if (CollectionUtils.isNotEmpty(userPermissionList)) {for (PermissionEntity permissionEntity : userPermissionList) {if (permissionEntity.getPermission().equals(accessPermission)) {return;}}}// 没有权限throw new CommonBizException(ExpCodeEnum.NO_PERMISSION);
}

/**
 * 检查当前接口是否需要登录
 * @param userEntity 用户信息
 * @param accessAuthEntity 接口访问权限
 */
private void checkLogin(UserEntity userEntity, AccessAuthEntity accessAuthEntity) {// 尚未登录if (accessAuthEntity.isLogin() && userEntity==null) {throw new CommonBizException(ExpCodeEnum.UNLOGIN);}
} 
  • 全局异常处理 为了是得代码具备良好的可读性,这里使用了SpringBoot提供的全局异常处理机制。我们只需抛出异常即可,这些异常会被我们预先设置的全局异常处理类捕获并处理。全局异常处理本质上借助于AOP完成。* 我们需要定义全局异常处理类,它只是一个普通类,我们只要用@ControllerAdvice注解声明即可* 我们还需要在这个类上增加@ResponseBody注解,它能够帮助我们当处理完异常后,直接向用户返回JSON格式的错误信息,而无需我们手动处理。* 在这个类中,我们根据异常类型不同,定义了两个异常处理函数,分别用于捕获业务异常、系统异常。并且需要使用@ExceptionHandler注解告诉Spring,该方法用于处理什么类型的异常。* 当我们完成上述配置后,只要项目中任何地方抛出异常,都会被这个全局异常处理类捕获,并根据抛出异常的类型选择相应的异常处理函数。
/**
 * @Author 大闲人柴毛毛
 * @Date 2017/10/27 下午11:02
 * REST接口的通用异常处理
 */
@ControllerAdvice
@ResponseBody
public class ExceptionHandle {private final Logger logger = LoggerFactory.getLogger(this.getClass());/** * 业务异常处理 * @param exception * @param <T> * @return */@ExceptionHandler(CommonBizException.class)public <T> Result<T> exceptionHandler(CommonBizException exception) {return Result.newFailureResult(exception);}/** * 系统异常处理 * @param exception * @return */@ExceptionHandler(Exception.class)public <T> Result<T> sysExpHandler(Exception exception) {logger.error("系统异常 ",exception);return Result.newFailureResult();}
} 

ss)public Result exceptionHandler(CommonBizException exception) {return Result.newFailureResult(exception);}/** * 系统异常处理 * @param exception * @return */@ExceptionHandler(Exception.class)public Result sysExpHandler(Exception exception) {logger.error("系统异常 ",exception);return Result.newFailureResult();}
}


<img src="https://i-blog.csdnimg.cn/blog_migrate/68a6771cd510be0626050bab5b499a7c.png" style="margin: auto" />## 题外话

初入计算机行业的人或者大学计算机相关专业毕业生,很多因缺少实战经验,就业处处碰壁。下面我们来看两组数据:

2023届全国高校毕业生预计达到1158万人,就业形势严峻;

国家网络安全宣传周公布的数据显示,到2027年我国网络安全人员缺口将达327万。

一方面是每年应届毕业生就业形势严峻,一方面是网络安全人才百万缺口。

6月9日,麦可思研究2023年版就业蓝皮书(包括《2023年中国本科生就业报告》《2023年中国高职生就业报告》)正式发布。

2022届大学毕业生月收入较高的前10个专业

本科计算机类、高职自动化类专业月收入较高。2022届本科计算机类、高职自动化类专业月收入分别为6863元、5339元。其中,本科计算机类专业起薪与2021届基本持平,高职自动化类月收入增长明显,2022届反超铁道运输类专业(5295元)排在第一位。

具体看专业,2022届本科月收入较高的专业是信息安全(7579元)。对比2018届,电子科学与技术、自动化等与人工智能相关的本科专业表现不俗,较五年前起薪涨幅均达到了19%。数据科学与大数据技术虽是近年新增专业但表现亮眼,已跻身2022届本科毕业生毕业半年后月收入较高专业前三。五年前唯一进入本科高薪榜前10的人文社科类专业——法语已退出前10之列。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdministrator%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20230809162658551.png&pos_id=img-fev4XA1i-1715732747931)

“没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。

### **网络安全行业特点**

1、就业薪资非常高,涨薪快 2022年猎聘网发布网络安全行业就业薪资行业最高人均33.77万!

![img](https://img-blog.csdnimg.cn/img_convert/d5f06d6b9945fd6e8a5f92a0198e5446.png)

###### 2、人才缺口大,就业机会多

2019年9月18日《中华人民共和国中央人民政府》官方网站发表:我国网络空间安全人才 需求140万人,而全国各大学校每年培养的人员不到1.5W人。猎聘网《2021年上半年网络安全报告》预测2027年网安人才需求300W,现在从事网络安全行业的从业人员只有10W人。
![img](https://img-blog.csdnimg.cn/img_convert/9cf857398f52a97ff49d437ac5fe690a.png)

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

###### 1.学习路线图

行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

#### 1.学习路线图

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdministrator%5CDesktop%5C%E7%BD%91%E5%AE%89%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE%5C%E4%BA%AB%E5%AD%A6%E9%A6%96%E5%88%9B%E5%B9%B4%E8%96%AA40W%2B%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8%E5%B7%A5%E7%A8%8B%E5%B8%88%20%E9%9D%92%E9%93%9C%E5%88%B0%E7%8E%8B%E8%80%85%E6%8A%80%E6%9C%AF%E6%88%90%E9%95%BF%E8%B7%AF%E7%BA%BFV4.0.png&pos_id=img-bCkN7leq-1715732747934)

攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

#### 2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdministrator%5CDesktop%5C%E7%BD%91%E5%AE%89%E8%B5%84%E6%96%99%E6%88%AA%E5%9B%BE%5C%E8%A7%86%E9%A2%91%E8%AF%BE%E4%BB%B6.jpeg&pos_id=img-s9Ug6I0U-1715732747935)

(都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里**👉**[网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!](https://mp.weixin.qq.com/s/BWb9OzaB-gVGVpkm161PMw)

#### **3.技术文档和电子书**

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5CAdministrator%5CDesktop%5C%E7%BD%91%E5%AE%89%E8%B5%84%E6%96%99%E6%88%AA%E5%9B%BE%5C%E6%8C%96%E6%8E%98%E6%96%87%E6%A1%A3%20(1&pos_id=img-5JEBuxk9-1715732747936).png)

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里**👉**[网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!](https://mp.weixin.qq.com/s/BWb9OzaB-gVGVpkm161PMw)

#### 4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。

还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取
如果你对网络安全入门感兴趣,那么你需要的话可以点击这里**👉**[网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!](https://mp.weixin.qq.com/s/BWb9OzaB-gVGVpkm161PMw)

最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…


![img](https://img-blog.csdnimg.cn/img_convert/da1274937756ef025cecc0439519a3d4.png)

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里**👉**[网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!](https://mp.weixin.qq.com/s/BWb9OzaB-gVGVpkm161PMw)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值