Springboot与SpringSecurity使用(1):介绍、登录验证

news2024/11/19 19:30:47

一、介绍

        Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 SpringSecurity 重要核心功能。

        Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。

        如图所示,一个请求想要访问到API就会从左到右经过虚线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。这里面重点关注两个过滤器:UsernamePasswordAuthenticationFilter负责登录认证,FilterSecurityInterceptor负责权限授权。

二、SpringSecurity入门

1、在项目中添加依赖

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

2、启动项目测试

        在浏览器访问:http://localhost:8080/doc.html,自动跳转到了登录页面。

        默认的用户名:user,密码在项目启动的时候在控制台会打印,注意每次启动的时候密码都会发生变化。

        当输入错误的帐号密码时,页面会出现提示。

        如果正确的话,则能正常访问:

        说明Spring Security默认安全保护生效。在实际开发中,这些默认的配置是不能满足我们需要的,我们需要扩展Spring Security组件,完成自定义配置,实现我们的项目需求。

三、用户认证

        用户认证流程:

  • Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
  • AuthenticationManager接口:定义了认证Authentication的方法
  • UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。
  • UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

1、用户认证核心组件

        我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。我们在程序中如何获取并使用它呢?我们需要通过 SecurityContext 来获取Authentication,SecurityContext就是我们的上下文对象。这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

   Authentication中的信息:

  • Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象
  • Credentials:用户凭证,一般是密码
  • Authorities:用户权限

2、用户认证

        AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证。Spring Security默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。Spring Security用户认证关键代码如下:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);

        AuthenticationManager的校验逻辑非常简单:根据用户名先查询出用户对象(没有查到则抛出异常)将用户对象的密码和传递过来的密码进行校验,密码不匹配则抛出异常。其中使用了三个组件:

  • UserDetialsService接口只有一个方法loadUserByUsername(String username),通过用户名查询用户对象,默认实现是在内存中查询。
  • Spring Security中的用户数据则是由UserDetails来体现,该接口中提供了账号、密码等通用属性。
  • PasswordEncoder负责密码加密与校验。

        UserDetialsService、UserDetails、PasswordEncoder,这三个组件Spring Security都有默认实现,这一般是满足不了我们的实际需求的,所以这里我们自己来实现这些组件。

加密器PasswordEncoder

package com.ywz.security;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.DigestUtils;

import java.util.Arrays;

/**
 * 类描述 -> 自定义md5加密
 *
 * @Author: ywz
 * @Date: 2024/07/28
 */
public class CustomMd5PasswordEncoder implements PasswordEncoder {
    /**
     * 方法描述 -> 对密码进行md5加密
     *
     * @param rawPassword 未加密密码
     * @Return: @return {@link String }
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes()));
    }

    /**
     * 方法描述 -> 判断密码是否匹配
     *
     * @param rawPassword 未加密密码
     * @param encodedPassword 加密后的密码
     * @Return: @return boolean
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(Arrays.toString(DigestUtils.md5Digest(rawPassword.toString().getBytes())));
    }

}

用户对象UserDetails

该接口是用户对象,它提供了用户的一些通用属性,实际开发中我们的用户属性各种各样,这些默认属性可能是满足不了,所以我们一般会自己实现该接口,然后设置好我们实际的用户实体对象。实现此接口要重写很多方法比较麻烦,我们可以继承Spring Security提供的org.springframework.security.core.userdetails.User类,该类实现了UserDetails接口帮我们省去了重写方法的工作:

package com.ywz.security;

import com.ywz.pojo.SysUser;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

/**
 * 类描述 -> 自定义用户
 *
 * @Author: ywz
 * @Date: 2024/07/28
 */
@Setter
@Getter
public class CustomUser extends User {
    private SysUser sysUser;

    public CustomUser(SysUser sysUser, Collection<? extends GrantedAuthority> authorities) {
        super(sysUser.getUsername(), sysUser.getPassword(), authorities);
        this.sysUser = sysUser;
    }

}

业务对象UserDetailsService

        该接口很简单只有一个方法,我们实现该接口,就完成了自己的业务:

package com.ywz.security.service;

import com.ywz.pojo.SysUser;
import com.ywz.security.CustomUser;
import com.ywz.service.SysUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.Objects;

/**
 * 类描述 -> 实现UserDetailsService接口,重写方法
 *
 * @Author: ywz
 * @Date: 2024/07/28
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService{
    @Resource
    private SysUserService sysUserService;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserService.queryByUsername(username);
        if (Objects.isNull(sysUser)){
            throw new UsernameNotFoundException("用户名不存在!");
        }

        if(sysUser.getStatus() == 0) {
            throw new RuntimeException("账号已停用");
        }
        return new CustomUser(sysUser, Collections.emptyList());
    }
}

登录接口

        接下需要自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。​ 认证成功的话要生成一个jwt,放入响应中返回。

package com.ywz.controller;

import com.ywz.pojo.LoginVo;
import com.ywz.pojo.Result;
import com.ywz.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Map;

/**
 * 类描述 -> 登录控制器
 *
 * @Author: ywz
 * @Date: 2024/07/28
 */
@Api(tags = "系统管理-登录管理")
@RequestMapping("/admin/system/index")
@RestController
public class LoginController {
    @Resource
    private SysUserService sysUserService;

    /**
     * 方法描述 -> 登录接口
     *
     * @param loginVo -> 登录对象
     * @Return: @return {@link Result }<{@link Map }<{@link String },{@link Object }>>
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @ApiOperation("登录接口")
    @PostMapping("/login")
    public Result<Map<String,Object>> login(@RequestBody LoginVo loginVo){
        return sysUserService.login(loginVo);
    }
}

SecurityConfig配置

package com.ywz.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

/**
 * 类描述 -> SpringSecurity配置类
 *
 * @Author: ywz
 * @Date: 2024/07/28
 */
@Configuration
@EnableWebSecurity // 是开启SpringSecurity的默认行为
public class SecurityConfig {

    /**
     * 方法描述 -> 密码明文加密方式配置
     *
     * @Return: @return {@link PasswordEncoder }
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new CustomMd5PasswordEncoder();
    }

    /**
     * 方法描述 -> 获取AuthenticationManager(认证管理器),登录时认证使用
     *
     * @param authenticationConfiguration 认证配置
     * @Return: @return {@link AuthenticationManager }
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }


    /**
     * 方法描述 -> 获取SecurityFilterChain(过滤器链),配置过滤器
     *
     * @param http httpSecurity
     * @Return: @return {@link SecurityFilterChain }
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return  http
                // 基于 token,不需要 csrf
                .csrf().disable()
                // 开启跨域以便前端调用接口
                .cors().and()
                .authorizeRequests()
                // 指定某些接口不需要通过验证即可访问。登录接口肯定是不需要认证的
                .antMatchers("/admin/system/index/login").permitAll()
                // 静态资源,可匿名访问
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
                .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**","/doc.html").permitAll()
                // 这里意思是其它所有接口需要认证才能访问
                .anyRequest().authenticated()
                .and()
                // 基于 token,不需要 session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // cors security 解决方案
                .cors().configurationSource(corsConfigurationSource())
                .and()
                .build();
    }

    /**
     * 方法描述 -> 配置跨源访问(CORS)
     *
     * @Return: @return {@link CorsConfigurationSource }
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.setAllowedMethods(Collections.singletonList("*"));
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setMaxAge(3600L);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}

执行登录

controller通过login方法调用实际业务

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    @Resource
    private SysMenuService sysMenuService;

    //通过AuthenticationManager的authenticate方法来进行用户认证,
    @Resource
    private AuthenticationManager authenticationManager;
    
	@Override
    public Result<Map<String, Object>> login(LoginVo loginVo) {
        // 将表单数据封装到 UsernamePasswordAuthenticationToken
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());
        // authenticate方法会调用loadUserByUsername
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("用户名或密码错误");
        }
        // 校验成功,强转对象
        CustomUser customUser = (CustomUser) authenticate.getPrincipal();
        SysUser sysUser = customUser.getSysUser();
        // 校验通过返回token
        String token = JwtUtil.createToken(sysUser.getId(), sysUser.getUsername());
        Map<String, Object> map = new HashMap<>();
        map.put("token",token);
        return Result.ok(map);
    }
}

认证过滤器

        我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的信息,获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder。

/**
 * 类描述 -> Jwt认证过滤器
 *
 * @Author: ywz
 * @Date: 2024/07/28
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    /**
     * 方法描述 -> 在请求之前进行过滤
     *
     * @param request 请求
     * @param response 响应
     * @param filterChain 过滤器链
     * @Return:
     * @Author: ywz
     * @Date: 2024/07/28
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userid;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1955096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux网络:传输层协议TCP(三)滑动窗口及流量控制

目录 一、关于滑动窗口在TCP中的应用 1.1什么是滑动窗口&#xff0c;为什么要有滑动窗口 1.2滑动窗口的实现 1.3滑动窗口针对丢包重传的处理机制 二、流量控制 一、关于滑动窗口在TCP中的应用 1.1什么是滑动窗口&#xff0c;为什么要有滑动窗口 在上一篇博文中博主阐述了…

植物神经紊乱拜拜啦! 6招让你心情美美哒,放松到飞起~

Hey宝贝们&#xff0c;是不是有时候觉得心里的小宇宙&#x1f30c;乱糟糟的&#xff0c;像是有一群小精灵&#x1f9da;‍♀️在跳舞&#xff0c;却偏偏踩不到点上&#xff1f;没错&#xff0c;这可能就是植物神经紊乱在作祟啦&#xff01;别怕&#xff0c;我这就给你支几招&am…

Nginx系列-12 Nginx使用Lua脚本进行JWT校验

背景 本文介绍Nginx中Lua模块使用方式&#xff0c;并结合案例进行介绍。案例介绍通过lua脚本提取HTTP请求头中的token字段&#xff0c;经过JWT校验并提取id和name信息&#xff0c;设置到http请求头中发向后段服务器。 默认情况下&#xff0c;Nginx自身不携带lua模块&#xff0…

Transformer中的Multi-head Attention机制解析——从单一到多元的关注效益最大化

Transformer中的Multi-head Attention机制解析——从单一到多元的关注效益最大化 Multi-head Attention的核心作用 组件/步骤描述多头注意力机制&#xff08;Multi-head Attention&#xff09;Transformer模型中的关键组件&#xff0c;用于处理序列数据功能允许模型同时关注到…

Vue2从基础到实战(指令篇)

Vue中的常用指令&#xff01; 概念&#xff1a;指令&#xff08;Directives&#xff09;是 Vue 提供的带有 v- 前缀 的 特殊 标签属性。 vue 中的指令按照不同的用途可以分为如下 6 大类&#xff1a; 内容渲染指令&#xff08;v-html、v-text&#xff09; 条件渲染指令&…

昇思25天学习打卡营第1天|快速入门-构建基于MNIST数据集的手写数字识别模型

非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从今天起&#xff0c;我将以打卡的方式&#xff0c;结合原文搬运和个人思考&#xff0c;分享25天的学习内容与成果。为了提升文章质量和阅读体验&#xff0c;我会将思考部分放在最后&#xff0c;供大家探索讨论…

04 | 深入浅出索引(上)

此系列文章为极客时间课程《MySQL 实战 45 讲》的学习笔记&#xff01; 索引的常见模型 可以提供查询效率的数据结构有很多&#xff0c;常见的有三种&#xff1a;哈希表、有序数组、搜索数。 哈希表是一种以 key-value 形式存储的数据结构。输入一个 key&#xff0c;通过固定…

Markdown使用~~pandoc插件安装

目录 1.两大秘密武器 2.vscode创作 3.Typora的安装 4.pandoc安装 4.1百度网盘 4.2按照说明安装 4.3到达github里面下载 4.4选择对应版本 4.5进入偏好设置 4.6对于导出的路径进行配置 5.Typora和vscode的对比 6.如何正确的学习这个Typora软件 7.一点相关的说明 1.两…

做一个能和你互动玩耍的智能机器人之三

内容节选自英特尔的开源项目openbot的body目录下diy下的readme&#xff0c;这是一个组装和连线方式的说明文档&#xff0c;接线需要配合firmware固件使用&#xff0c;固件代码的接线柱是对应的。 body目录内部十分丰富&#xff0c;主要介绍了这个项目的背景和硬件以及如何让他…

datawhale逻辑推理赛题01

跟着datawhale参加了逻辑推理赛题&#xff0c;这个是一个大模型比赛入门非常好的比赛&#xff0c;可以帮助我们更快的上手大模型相关的业务 我参加的是天池的这个比赛&#xff0c;跟着datawhale官方的baseline01已经上分0.6498&#xff0c;是一个非常好的开始 后续我讲继续跟着…

java项目中添加SDK项目作为依赖使用(无需上传Maven)

需求&#xff1a; 当需要多次调用某个函数或算法时&#xff0c;不想每次调用接口都自己编写&#xff0c;可以将该项目打包&#xff0c;以添加依赖的方式实现调用 适用于&#xff1a; 无需上线的项目&#xff0c;仅公司或团队内部使用的项目 操作步骤&#xff1a; 以下面这…

Linux---进程(2)

目录 查看进程 查看进程pid 系统目录查看 理解当前工作目录用途 fork创建进程 存在问题 问题解决 问题一 问题二 本文介绍进程标识符的相关知识以及创建子进程。 查看进程 指令就是可执行程序&#xff0c;每次运行时&#xff0c;都需要被加载到内存&#xff0c;运行…

动物之森-小红书2024笔试(codefun2000)

题目链接 动物之森-小红书2024笔试(codefun2000) 题目内容 塔子哥最近在玩一款叫做“动物之森”的四字开放游戏世界。由于塔子哥氪金了&#xff0c;所以他在游戏中拥有很多个宝箱&#xff0c;每个宝箱里都装着一些他收集的宝石。每一种类型的宝石都有不同的作用。 有一天&…

java--jvm虚拟机(都是要点)

请带着以下问题&#xff0c;学习并理解jvm 问题一&#xff1a; 为什么fullGC会对系统性能有影响&#xff1f;youngGC却几乎没有&#xff1f; 问题二&#xff1a; outofmemory是什么异常&#xff1f;什么时候会出现&#xff1f;如何处理&#xff1f; 问题三&#xff1a; 线程…

鸿蒙 HarmonyOS NEXT端云一体化开发-云函数篇

前言 TODO&#xff1a;新建项目和应用&#xff0c;开通云函数服务&#xff08;AGC&#xff09;端侧开发&#xff1a;Arkts界面开发云测开发&#xff1a;云函数、认证服务、云存储、云数据库 一、登录 地址&#xff1a;https://id1.cloud.huawei.com/CAS/portal/loginAuth.htm…

Nginx周末部署

背景 Nginx是本人学习的一类中间件&#xff0c;上次完成了vue的搭建&#xff0c;所以顺便把项目加入Nginx吧 1. 镜像拉取与测试 查询dockerHub&#xff0c;选择最新最稳定的版本 docker pull nginx:stable-perl 执行下载 docker run -d --name mynginx -p 8080:80 -v D:\IM…

【Kubernetes】配置管理(一):ConfigMap

配置管理&#xff08;一&#xff09;&#xff1a;ConfigMap 1.配置管理2.使用 ConfigMap 管理 Pod 的配置信息2.1 创建 ConfigMap2.1.1 在命令行中通过指定 ConfigMap 的参数进行创建2.1.2 通过指定的配置文件创建 ConfigMap2.1.3 通过一个文件内的多个键值对创建 ConfigMap2.1…

C++数据结构重要知识点(3)(红黑树及其插入操作)

1.红黑树和AVL树的区别 红黑树和AVL树都是平衡树&#xff0c;都是为了解决二叉搜索树的劣势。其中&#xff0c;AVL树的左右子树的高度差不超过1&#xff0c;而红黑树的最长路径不超过最短路径的二倍&#xff0c;也就是说&#xff0c;红黑树是一种近似平衡&#xff0c;而AVL树是…

【设计模式】(万字总结)深入理解Java中的创建型设计模式

1. 前言 在软件开发的世界里&#xff0c;设计模式是一种被广泛接受并应用的解决方案。它们不仅仅是代码的设计&#xff0c;更是对问题的思考和解决的方法论。在Java开发中&#xff0c;特别是在面向对象的编程中&#xff0c;设计模式尤为重要。创建型设计模式&#xff0c;作为设…

JavaScript安全编程宝典【万字详解】

文章目录 简介基本说明特点两种使用方式在script中写使用script标签引入JS文件 数据类型介绍特殊值 运算符算数运算符赋值运算符逻辑运算符&#xff1a;条件运算符 数组的定义基本使用数组的遍历 函数含义函数定义方式基本语法代码示例 细节和注意事项 自定义对象Object形式{} …