Spring Boot整合Spring Security

news2024/11/15 23:42:59

Spring Boot 专栏:Spring Boot 从零单排
Spring Cloud 专栏:Spring Cloud 从零单排
GitHub:SpringBootDemo
Gitee:SpringBootDemo

Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块的默认技术选型,仅需引入spring-boot-starter-security模块,进行少量配置,即可实现强大的Web安全控制。

Spring Security的两个主要目标是认证授权(访问控制)

官方文档:https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/

0 开发环境

  • JDK:1.8
  • Spring Boot:2.7.18

Spring Boot 版本升级为2.7.18,专栏中其他Spring Boot相关环境同步升级

1 引入依赖

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

2 测试

2.1 新建Controller

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "query")
    public String query() {
        return "用户查询成功";
    }
}

2.2 测试

启动服务,浏览器访问 127.0.0.1:8090/user/query,页面自动跳转到授权登录页

在这里插入图片描述

默认用户名为user,控制台上会打印默认密码,默认密码每次启动服务都会刷新

在这里插入图片描述

登录成功后,就可以正常访问了

在这里插入图片描述

3 自定义密码

该部分会使用到Spring Security的几个关键类,如下:

  • WebSecurityConfigurerAdapter 自定义Security策略
  • AuthenticationManagerBuilder 自定义认证策略
  • @EnableWebSecurity 开启WebSecurity模式

3.1 通过application.yml 配置

spring:
  security:
    user:
      name: admin
      password: 123456

3.2 自定义配置类配置

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码加密
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //配置用户名、密码,该配置方式下,用户名和密码保存在内存中
        auth.inMemoryAuthentication()
                //密码加密方式
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(passwordEncoder.encode("123456")).roles("admin");
    }
}

3.3 自定义实现类配置

3.3.1 编写UserDetailsService实现类

这里我们就直接固定写死用户名和密码,实际生产中可以从数据库中获取

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //设置角色,角色的概念后续介绍
        List<GrantedAuthority> roles = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");

        return new User("admin", new BCryptPasswordEncoder().encode("123456"), roles);
    }
}

3.3.2 编写配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用UserDetailsServiceImpl 查询用户名、密码
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

分别测试,都通过

4 用户认证和授权/基于角色和权限的访问控制

实际生产中,需要根据用户角色的权限来控制可访问的页面、可执行的操作等

4.1 新建4个页面

level-1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: red">这是用户等级1可访问的页面</h1>
</body>
</html>

level-2.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: green">这是用户等级2可访问的页面</h1>
</body>
</html>

level-3.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: blue">这是用户等级3可访问的页面</h1>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="level-1.html">等级1</a><br><br>
<a href="level-2.html">等级2</a><br><br>
<a href="level-3.html">等级3</a>
</body>
</html>

4.2 修改配置类

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //密码加密
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        //配置用户名、密码,该配置方式下,用户名和密码保存在内存中
        auth.inMemoryAuthentication()
                //密码加密方式
                .passwordEncoder(new BCryptPasswordEncoder())
                .withUser("admin").password(passwordEncoder.encode("123456")).roles("admin")
                .and().withUser("admin1").password(passwordEncoder.encode("123456")).roles("level1")
                .and().withUser("admin2").password(passwordEncoder.encode("123456")).roles("level2")
                .and().withUser("admin3").password(passwordEncoder.encode("123456")).authorities("level3")
                .and().withUser("admin0").password(passwordEncoder.encode("123456")).authorities("ROLE_level1", "ROLE_level2", "level3");
    }
}

**hasRole()hasAuthority()**用法是类似的,只不过hasRole()方法会给自定义的角色名前加上 ROLE_ 前缀

在这里插入图片描述

因此在自定义用户时,如果使用**authorities()给用户设置角色时,需要自行添加上ROLE_**前缀。

roles()authorities()设置的角色或权限,最终都存放在authorities参数中,且这两个方法会互相覆盖彼此的值。

在这里插入图片描述

4.3 测试

浏览器访问

在这里插入图片描述

依次点击等级1、等级2、等级3,均自动跳转到授权登录页面,登录对应权限的用户后,可成功访问。

其中,登录admin用户,无法访问任何页面,登录admin0,可访问所有页面

在这里插入图片描述

登录权限不匹配的用户,拒绝访问

在这里插入图片描述

4.4 使用UserDetailsService类实现

UserDetailsServiceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        List<GrantedAuthority> roles;
        if ("admin1".equals(s)) {
            roles = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_level1");
        } else if ("admin2".equals(s)) {
            roles = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_level2");
        } else if ("admin3".equals(s)) {
            roles = AuthorityUtils.commaSeparatedStringToAuthorityList("level3");
        } else if ("admin0".equals(s)) {
            roles = AuthorityUtils.createAuthorityList("ROLE_level1", "ROLE_level2", "level3");
        } else {
            roles = AuthorityUtils.createAuthorityList("admin");
        }

        return new User(s, new BCryptPasswordEncoder().encode("123456"), roles);
    }
}

SecurityConfig

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //使用UserDetailsServiceImpl 查询用户名、密码
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

浏览器访问,测试,通过

5 常用注解

可控制用户认证访问接口

5.1 @Secured()

校验用户具有某个角色,才可访问接口

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "query")
    @Secured("ROLE_level1")
    public String query() {
        return "用户查询成功";
    }

    @GetMapping(value = "update")
    @Secured({"ROLE_level1", "ROLE_level2"})
    public String update() {
        return "用户更新成功";
    }
}

5.2 @PreAuthorize()

在进入方法前校验用户具有某个权限或角色

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "delete")
    @PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    public String delete() {
        return "用户删除成功";
    }
}

5.3 @PostAuthorize()

在进入方法后校验用户具有某个权限或角色

需在启动类开启注解

@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)

然后在接口方法上配置注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "delete")
    @PostAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    public String delete() {
        return "用户删除成功";
    }
}

5.4 @PostFilter()

校验权限后对数据进行过滤,只返回满足条件的数据

新建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserModel {

    private String username;
    private String password;
}

然后在方法上加上注解

@RestController
@RequestMapping("user")
public class UserController {

    @GetMapping(value = "queryList")
    @PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    @PostFilter("filterObject.username == 'test'")
    public List<UserModel> queryList() {
        List<UserModel> userList = new ArrayList<>();
        userList.add(new UserModel("test", "qwerty"));
        userList.add(new UserModel("test2", "asdfgh"));
        userList.add(new UserModel("test3", "zxcvbn"));

        return userList;
    }
}

测试,权限验证通过后

在这里插入图片描述

5.5 @PreFilter()

校验权限后对数据进行过滤,只有满足条件的数据才能传入接口方法中

@RestController
@RequestMapping("user")
public class UserController {

    @PostMapping(value = "queryUser")
    @PreAuthorize("hasAnyAuthority('ROLE_level1','level3')")
    @PreFilter("filterObject.username == 'test2'")
    public List<UserModel> queryUser(@RequestBody List<UserModel> userModels) {
        return userModels;
    }
}

测试,权限验证通过后

在这里插入图片描述

6 记住我

配置类中开启记住我

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();

        //记住我
        http.rememberMe();
    }

启动服务,访问页面,登录页面增加了记住我选择框

在这里插入图片描述

登录成功后,cookie中已保存用户信息,默认时间为2周

在这里插入图片描述

7 注销

7.1 配置类中开启注销

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        http.formLogin();

        //记住我
        http.rememberMe();

        //开启注销,注销成功后回首页
        http.logout().logoutSuccessUrl("/");
    }

7.2 level-* 页面增加注销按钮

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 style="color: red">这是用户等级1可访问的页面</h1>
<br><br>
<a href="/logout">注销</a>
</body>
</html>

其他两个页面做相同修改

启动服务,登录成功后点击注销按钮,注销成功,返回首页,访问页面需再次登录

8 自定义登录页

8.1 新建登录页login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta charset="UTF-8">
    <title>Login Page</title>

    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
        }

        .container {
            width: 300px;
            margin: auto;
            padding: 40px;
            border: 1px solid #ccc;
            background-color: white;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        h2 {
            text-align: center;
        }

        label {
            display: block;
            margin-bottom: 10px;
        }

        input[type="text"],
        input[type="password"] {
            width: 100%;
            padding: 6px;
            border: 1px solid #ccc;
            outline: none;
        }

        button {
            width: 100%;
            padding: 10px;
            color: white;
            background-color: #4CAF50;
            cursor: pointer;
            border: none;
            outline: none;
        }

        button:hover {
            opacity: 0.9;
        }
    </style>
</head>
<body>
<div class="container">
    <h2>登录</h2>
    <form action="/login" method="post">
        <label for="username">用户名</label>
        <input type="text" id="username" name="username"><br><br>

        <label for="password">密码</label>
        <input type="password" id="password" name="password"><br><br>

        <input type="checkbox" name="remember-me" title="记住我">记住我<br><br>

        <button type="submit">登 录</button>
    </form>
</div>
</body>
</html>

8.2 配置类中开启自定义登录页

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //请求授权的规则
        //开启认证
        http.authorizeRequests()
                //首页所有人可访问
                .antMatchers("/").permitAll()
                //功能页对应角色或权限才能访问
                //hasRole 为角色授权,表示用户拥有指定角色
                //hasAuthority 为权限授权,表示用户拥有指定权限
                .antMatchers("/level-1.html").hasRole("level1")
                .antMatchers("/level-2.html").hasRole("level2")
                .antMatchers("/level-3.html").hasAuthority("level3");

        //开启登录,无权限时进入登录页面
        //自定义登录页
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login");
        //关闭csrf防护
        http.csrf().disable();

        //记住我
        http.rememberMe();

        //开启注销,注销成功后回首页
        http.logout().logoutSuccessUrl("/");
    }

这里,自定义登录,默认用户参数是username,默认密码参数是password,默认记住我参数是remember-me,如果需要自定义登录表单的参数,做如下修改

        //开启登录,无权限时进入登录页面
        http.formLogin().loginPage("/login.html").loginProcessingUrl("/login")
                .usernameParameter("username")
                .passwordParameter("password");
        //关闭csrf防护
        http.csrf().disable();

        //记住我
        http.rememberMe().rememberMeParameter("remember-me");

启动服务,访问地址,跳转到自定义登录页

在这里插入图片描述

9 自定义403页面

9.1 新建403.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
权限不足,无法访问
</body>
</html>

9.2 配置类中开启自定义403页面

    @Override
    protected void configure(HttpSecurity http) throws Exception {
		//...其他代码...

        //自定义403页面
        http.exceptionHandling().accessDeniedPage("/403.html");
    }

启动服务,浏览器访问,登录无权限用户后提示

在这里插入图片描述

至此,Spring Boot整合Spring Security实现用户认证和授权基本用法已讲解完毕,且测试通过。

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

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

相关文章

基于springboot+vue+Mysql的留守儿童爱心网站

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

学习周报:文献阅读+Fluent案例+水力学理论学习

目录 摘要 Abstract 文献阅读&#xff1a; 文献摘要 现有问题 研究目的及方法 PINN的设置 NS方程介绍 损失函数 训练方法 实验设置 对照组设置 实验结果展示 点云数、隐藏层数和每个隐藏层的节点数对PINN精度的影响 点云数对PINN的影响&#xff1a; 隐藏层数的影…

Kruskal最小生成树【详细解释+动图图解】【sort中的cmp函数】 【例题:洛谷P3366 【模板】最小生成树】

文章目录 Kruskal算法简介Kruskal算法前置知识sort 中的cmp函数 算法思考样例详细示范与解释kruskal模版code↓ 例题&#xff1a;洛谷P3366 【模板】最小生成树code↓完结撒花QWQ Kruskal算法简介 K r u s k a l Kruskal Kruskal 是基于贪心算法的 M S T MST MST 算法&#xff…

探索国内ip切换App:打破网络限制

在国内网络环境中&#xff0c;有时我们会遇到一些限制或者屏蔽&#xff0c;使得我们无法自由访问一些网站或服务。而国内IP切换App的出现&#xff0c;为解决这些问题提供了非常便捷的方式。这些App可以帮助用户切换IP地址&#xff0c;让用户可以轻松地访问被限制或屏蔽的网站&a…

【计算机考研】 跨考408全年复习规划+资料分享

跨专业备考计算机考研408&#xff0c;确实是一项挑战。在有限的时间内&#xff0c;我们需要合理安排时间&#xff0c;制定有效的学习计划&#xff0c;做到有效地备考。回顾我之前对408的经验&#xff0c;我想分享一些备考计划和方法。 要认清自己的起点。作为跨专业考生&#…

AI Infra论文阅读之《在LLM训练中减少激活值内存》

写了一个Megatron-LM的3D Parallel进程组可视化的Playground&#xff0c;界面长下面这样&#xff1a; 可以直接访问&#xff1a;https://huggingface.co/spaces/BBuf/megatron-lm-parallel-group-playground 脚本也开源在&#xff1a;https://github.com/BBuf/megatron-lm-par…

Linux部署seata-2.x整合SpringCloud使用(Nacos实现配置与注册中心)

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; Linux部署seata-2.x整合SpringCloud使用(Nacos实现配置与注册中心) ⏱️…

Request请求参数----中文乱码问题

一: GET POST获取请求参数: 在处理为什么会出现中文乱码的情况之前, 首先我们要直到GET 以及 POST两种获取请求参数的不同 1>POST POST获取请求参数是通过输入流getReader来进行获取的, 通过字符输入流来获取响应的请求参数, 并且在解码的时候, 默认的情况是 ISO_885…

Elasticsearch:虚拟形象辅助和对话驱动的语音到 RAG 搜索

作者&#xff1a;来自 Elastic Sunile Manjee 搜索的演变 搜索已经从产生简单结果的简单文本查询发展成为容纳文本、图像、视频和问题等各种格式的复杂系统。 如今的搜索结果通过生成式人工智能、机器学习和交互式聊天功能得到增强&#xff0c;提供更丰富、更动态且与上下文相…

一张表看懂阿里云服务器优惠价格表(CPU内存价格+带宽费用+磁盘价格)

2024年腾讯云服务器优惠价格表&#xff0c;一张表整理阿里云服务器最新报价&#xff0c;阿里云服务器网整理云服务器ECS和轻量应用服务器详细CPU内存、公网带宽和系统盘详细配置报价单&#xff0c;大家也可以直接移步到阿里云CLUB中心查看 aliyun.club 当前最新的云服务器优惠券…

【Redis】Redis特性

Redis 认识redisRedis特性在内存中存储数据可编程可扩展性持久化Clustering高可用性 认识redis Redis&#xff0c;英文全称是Remote Dictionary Server&#xff08;远程字典服务&#xff09;&#xff0c;是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志…

Window全网解析网站下载视频

全网解析网站下载视频 介绍m3u8格式cbox格式 解析视频下载的方法方法一解析视频下载视频 方法二老王浏览器下载使用浏览器解析下载视频 总结 介绍 今天分享一下如何解析网页中的视频进行下载。通常情况下我们打开的某某网站的视频是不提供下载接口的&#xff0c;甚至说你下载了…

Verilog刷题笔记45

题目&#xff1a;Given the finite state machine circuit as shown, assume that the D flip-flops are initially reset to zero before the machine begins. Build this circuit. 解题&#xff1a; module top_module (input clk,input x,output z ); wire [2:0]size;dtou…

性能测试丨GreatSQL TPC-H 性能测试报告正式发布!

1、测试背景概述 本次测试针对GreatSQL开源数据库基于标准 TPC-H 场景的测试。 TPC-H&#xff08;商业智能计算测试&#xff09;是美国交易处理效能委员会&#xff08;TPC&#xff0c;TransactionProcessing Performance Council&#xff09;组织制定的用来模拟决策支持类应用…

StarRocks 助力金融营销数字化进化之路

作者&#xff1a;平安银行 数据资产中心数据及 AI 平台团队负责人 廖晓格 平安银行五位一体&#xff0c;做零售金融的领先银行&#xff0c;五位一体是由开放银行、AI 银行、远程银行、线下银行、综合化银行协同构建的数据化、智能化的零售客户经营模式&#xff0c;这套模式以数…

37、Linux中Xsync数据同步备份工具

37、Linux中Xsync数据同步备份工具 一、介绍二、配置集群hostname三、修改xsync文件四、赋权五、安装Rsync六、验证一七、配置免密登录1、生成rsa密钥2、copy机器自身公钥到目标机器3、.ssh/文件目录赋权 八、验证二 ⚠️ 注&#xff1a;本文全程在普通用户下操作&#xff0c;…

设计模式之建造者模式详解

建造者模式 1&#xff09;概述 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 1.复杂对象 复杂对象是指包含多个成员属性的对象。 2.结构图 Builder&#xff08;抽象建造者&#xff09;&#xff1a;它为创建一个产品Product对象…

项目2-用户登录

1.创建项目 2.引入前端代码并检查是否有误 3.定义接口 需求分析 对于后端开发⼈员⽽⾔, 不涉及前端⻚⾯的展⽰, 只需要提供两个功能 1. 登录⻚⾯: 通过账号和密码, 校验输⼊的账号密码是否正确, 并告知前端 2. ⾸⻚: 告知前端当前登录⽤⼾. 如果当前已有⽤⼾登录, 返回登录的账…

看看Java Web怎么上传文件到服务器

旁白不多说了直接上主题了。 1、新建上传文件夹 在eclipse中&#xff0c;在我们前面文章中用到的项目HelloJSP&#xff0c;在webapp目录下新建uploadfiles文件夹&#xff0c;如下所示&#xff1a; 2、修改HelloWorld.jsp文件 <body><h1>文件上传</h1><…

稻飞虱在线监测仪的工作原理

TH-DF122随着现代农业科技的快速发展&#xff0c;智能化、精准化的农业管理工具日益成为农业生产的得力助手。其中&#xff0c;稻飞虱在线监测仪作为一种创新的农业监测设备&#xff0c;正以其独特的工作原理和显著的应用效果&#xff0c;成为保障稻田生态安全和提高稻米产量的…