安全框架SpringSecurity-2(集成thymeleaf集成验证码JWT)

news2024/11/19 19:25:46

一、SpringSecurity 集成thymeleaf

①:复制并修改工程

  • 复制04_spring_security并重命名为05_spring_security_thymeleaf

在这里插入图片描述

②:添加配置和依赖

  • 添加thymeleaf依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
  • 修改application.yml
spring:
  thymeleaf:
    cache: false # 不使用缓存
	check-template: true  # 检查thymeleaf模板是否存在

③:添加thymeleaf模板和插件

  1. 添加thymeleaf模板

在这里插入图片描述

  • 模板名称thymeleaf ,扩展名html,具体内容如下:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>#[[$Title$]]#</title>        
</head>
<body>
#[[$END$]]#
</body>
</html>
  • 简要说明:
    #[[$Title$]]# #[[$END$]]# 这两处的作用是,当你新建一个模板页面时,在<title>标签中输入标题内容后,只需要点击回车键,光标就会直接跳到<body>内,省去了你挪动鼠标,或者挪动方向键的步骤,也可以给你节省一点点时间。

2.idea安装html转thymeleaf的插件JBLHtmlToThymeleaf

  • 安装JBLHtmlToThymeleaf插件
    在这里插入图片描述

④:新建Controller层代码

1.新建LoginController

@Controller
@RequestMapping("/login")
public class LoginController {
    
    /*
     * 跳转到登录页面
     * @DateTime: 2023/11/11 17:57
     *
     * @return String
     * @author: Coke
     */
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }
}

2.新建IndexController

@Controller
@RequestMapping("/index")
public class IndexController {
    
    
    /*
     * 登录成功后进入页面
     * @DateTime: 2023/11/11 17:58
     *
     * @return String
     * @author: Coke
     */
    @RequestMapping("/toIndex")
    public String toIndex(){
        return "main";
    }
}

⑤:创建静态页面

1.创建login.html

在这里插入图片描述

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户登陆</title>
</head>
<body>
<h2>登录页面</h2>
<form action="/login/toLogin" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="uname" value="thomas"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="pwd"></td>
            <span th:if="${param.error}">用户名或者密码错误</span>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>
</body>

2.创建main.html

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>系统首页</title>
</head>
<body>
<h1 align="center">系统首页</h1>
<a href="/student/query">查询学生</a>
<br>
<a href="/student/add">添加学生</a>
<br>
<a href="/student/update">更新学生</a>
<br>
<a href="/student/delete">删除学生</a>
<br>
<a href="/student/export">导出学生</a>
<br>
<br><br><br>
<h2><a href="/logout">退出</a></h2>
<br>
</body>
</html>

⑥:修改安全配置文件WebSecurityConfig

在这里插入图片描述

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 对密码进行编码
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure (HttpSecurity http) throws Exception {
        // 任何请求 都需要登录,注意:没有配置mV℃匹配器的只要登录成功就可以访问
        http.authorizeRequests().anyRequest().authenticated();
        http.formLogin()
              .loginPage("/login/toLogin") // 配置自定义的登录页面
              .usernameParameter("uname") // 指定登录页面的用户名字段
              .passwordParameter("pwd") // 指定登录页面的密码字段
              .loginProcessingUrl("/login/toLogin") // 配置点击登录时的请求的url
              .successForwardUrl("/index/toIndex") // 登录成功后跳转的页面
              .failureForwardUrl("/login/toLogin") // 登录失败后跳转的页面
              .permitAll();
        
        // 配置登出方式
        http.logout()
              .logoutUrl("/logout")
              .logoutSuccessUrl("/login/toLogin")
              .permitAll();
        //  禁用csrf跨域请求攻击
        http.csrf().disable();
    }
}

⑦:修改Studentcontroller

@Controller
@Slf4j
@RequestMapping("/student")
public class StudentController {
    @GetMapping("/query")
    @PreAuthorize("hasAnyAuthority('student:query')")
    public String queryInfo(){
        return "user/query";
    }
    
    @GetMapping("/add")
    @PreAuthorize("hasAnyAuthority('student:add')")
    public String addInfo(){
        return "user/add";
    }
    
    @GetMapping("/update")
    @PreAuthorize("hasAnyAuthority('student:update')")
    public String updateInfo(){
        return "user/update";
    }

    @GetMapping("/delete")
    @PreAuthorize("hasAnyAuthority('student:delete')")
    public String deleteInfo(){
        return "user/delete";
    }
    
    @GetMapping("/export")
    @PreAuthorize("hasAnyAuthority('student:export')")
    public String exportInfo(){
        return "user/export";
    }
}

⑧:在templates/user下面创建学生管理的各个页面

在这里插入图片描述

1.创建export.html 导出页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
	    <meta charset="UTF-8">
	    <title>系统首页-学生管理</title>
	</head>
	<body>
	    <h1 align="center">系统首页-学生管理-导出</h1>
	        <a href="/index/toIndex">返回</a>
	    <br>
	</body>
</html>

2.创建add.html 添加页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
	    <meta charset="UTF-8">
	    <title>系统首页-学生管理</title>
	</head>
	<body>
	    <h1 align="center">系统首页-学生管理-新增</h1>
	        <a href="/index/toIndex">返回</a>
	    <br>
	</body>

</html>

3创建delete.html 删除页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
	<head>
	    <meta charset="UTF-8">
	    <title>系统首页-学生管理</title>
	</head>
    <body>
        <h1 align="center">系统首页-学生管理-删除</h1>
            <a href="/index/toIndex">返回</a>
        <br>
    </body>
</html>

4.创建update.htm 修改l页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>系统首页-学生管理</title>
    </head>
    <body>
        <h1 align="center">系统首页-学生管理-更新</h1>
            <a href="/index/toIndex">返回</a>
        <br>
    </body>
</html>

5.创建query.html 查询页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>系统首页-学生管理</title>
    </head>
    <body>
        <h1 align="center">系统首页-学生管理-查询</h1>
            <a href="/index/toIndex">返回</a>
        <br>
    </body>
</html>

⑨:创建403页面 启动测试

1.在static/error下面创建403.html

在这里插入图片描述

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>403</title>
    </head>
    <body>
        <h2>403:你没有权限访问此页面</h2>
        <a href="/index/toIndex">去首页</a>
    </body>
</html>

2.启动测试

  • 01.使用thomas 用户登录 并查看用户该用户的权限

在这里插入图片描述

  • 测试学生查询权限

在这里插入图片描述

  • 测试学生的导出权限

在这里插入图片描述

⑩:当用户没有某权限时,页面不展示该按钮

我们之前创建的项目里面是当用户点击页面上的链接请求到后台之后没有权限会跳转到403,那么如果用户没有权限,对应的按钮就不显示出来,这样岂不是更好吗我们接着上一个项目来改造

1.引入下面的依赖

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

2.修改main.html即可

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <head>
        <meta charset="UTF-8">
        <title>系统首页</title>
    </head>
    <body>
        <h1 align="center">系统首页</h1>
        <a href="/student/query" sec:authorize="hasAuthority('student:query')" >查询用户</a>
        <br>
            <a href="/student/add" sec:authorize="hasAuthority('student:add')" >添加用户</a>
        <br>
            <a href="/student/update" sec:authorize="hasAuthority('student:update')" >更新用户</a>
        <br>
            <a href="/student/delete" sec:authorize="hasAuthority('student:delete')" >删除用户</a>
        <br>
            <a href="/student/export" sec:authorize="hasAuthority('student:export')" >导出用户</a>
        <br>
        <br><br><br>
            <h2><a href="/logout">退出</a></h2>
        <br>
    </body>
</html>

3.重启启动登录后查看效果

在这里插入图片描述

二、springsecurity 集成图片验证码

  • 复制上一个工程05_spring_security_thymeleaf,修改名字06_spring_security_captcha

①:原理、存在问题、解决思路

我们知道Spring Security是通过过滤器链来完成了,所以它的解决方案是创建一个过滤器放到Security的过滤器链中,在自定义的过滤器中比较验证码

②:添加依赖(用于生成验证码)

        <!--引入hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.3.9</version>
        </dependency>

③:添加一个获取验证码的接口

@Controller
@Slf4j
public class CaptchaController {
    
    @GetMapping("/code/image")
    public void getCaptcha(HttpServletRequest request, HttpServletResponse response){
        // 创建一个验证码  width – 图片宽 height – 图片高 codeCount – 字符个数 circleCount – 干扰圆圈条数
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
        // 放到session中
        String code = circleCaptcha.getCode();
        request.getSession().setAttribute("CAPTCHA_CODE",code);
        log.info("图片验证码为:{}", code);
    
        try {
            ImageIO.write(circleCaptcha.getImage(),"JPEG",response.getOutputStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

④:创建验证码过滤器

在这里插入图片描述

@Component
@Slf4j
public class ValidateCodeFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        log.info("请求路径为:{}",requestURI);
        
        // 判断请求的是否为登录页面
        if (!"/login/doLogin".equals(requestURI)){ // 不是登录请求,直接放行
            doFilter(request,response,filterChain); //直接下一个
            return;
        }
        //校验验证码
        validateCode(request,response,filterChain);
    }
    
    private void validateCode (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
       // 获取用户输入的验证码
        String code = request.getParameter("code");
        // 获取session中存储的验证码
        HttpSession session = request.getSession();
        String login_captcha_code = (String) session.getAttribute("CAPTCHA_CODE");
        log.info("用户输入的验证码: {}, session中存储的验证码: {}", code, login_captcha_code);
        
        // 移除错误信息
        session.removeAttribute("captchaCodeErrorMsg");
        if (!StringUtils.hasText(code)){
            request.getSession().setAttribute("captchaCodeErrorMsg", "请输入验证码!");
            // 重定向到登录页面
            response.sendRedirect("/login/toLogin");
            return;
        }
        
        if (!StringUtils.hasText(login_captcha_code)){
            request.getSession().setAttribute("captchaCodeErrorMsg", "验证码错误!");
            // 重定向到登录页面
            response.sendRedirect("/login/toLogin");
        }
        
        if (!code.equalsIgnoreCase(login_captcha_code)){
            request.getSession().setAttribute("captchaCodeErrorMsg", "验证码输入错误!");
            // 重定向到登录页面
            response.sendRedirect("/login/toLogin");
        }
        // 删除session中的验证码
        session.removeAttribute("code");
        this.doFilter(request,response,filterChain);
    }
}

⑤: 修改WebSecurityConfig(重点)

@Configuration
@EnableGlobalMethodSecurity (prePostEnabled = true)
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private ValidateCodeFilter validateCodeFilter;
    
    // 对密码进行编码
    @Bean
    public PasswordEncoder passwordEncoder () {
        return new BCryptPasswordEncoder();
    }
    
    
    @Override
    protected void configure (HttpSecurity http) throws Exception {
        // 配置登录之前添加一个验证码的过滤器
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);
        // 配置路径拦截 的 url的匹配规则
        //所有请求,都需要认证
        http.authorizeRequests()
              .mvcMatchers("/code/image")
              .permitAll() //放开验证码的请求
              .anyRequest().authenticated();
        http.formLogin()
              .loginPage("/login/toLogin") // 配置自定义的登录页面
              .usernameParameter("uname") // 指定登录页面的用户名字段
              .passwordParameter("pwd") // 指定登录页面的密码字段
              .loginProcessingUrl("/login/toLogin") // 配置点击登录时的请求的url
              .successForwardUrl("/index/toIndex") // 登录成功后跳转的页面
              .failureForwardUrl("/login/toLogin") // 登录失败后跳转的页面
              .permitAll();
        
        // 配置登出方式
        http.logout()
              .logoutUrl("/logout")
              .logoutSuccessUrl("/login/toLogin")
              .permitAll();
        //  禁用csrf跨域请求攻击
        http.csrf().disable();
    }
    
    
    /**
     * 资源服务匹配放行【静态资源文件】
     *
     * @param web
     * @throws Exception
     */
    // @Override
    //public void configure(WebSecurity web) throws Exception {
    //  web.ignoring().mvcMatchers("/resources/**");
    //}
    
}

⑥:修改login.html 后 进行测试

1.修改login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>用户登陆</title>
</head>
<body>
<h2>登录页面</h2>
<!--${param.error}这个如果有值,就显示帐号或密码错误-->
<h4 th:if="${param.error}" style="color: #FF0000;">帐号或密码错误,请重新输入</h4>
<form action="/login/doLogin" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="uname" value="thomas"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="pwd"></td>
            <span th:if="${param.error}">用户名或者密码错误</span>
        </tr>
        <tr>
            <td>验证码:</td>
            <td><input type="text" name="code"> <img src="/code/image" style="height:33px;cursor:pointer;" onclick="this.src=this.src">
                <span th:text="${session.captchaCodeErrorMsg}" style="color: #FF0000;" >username</span>
            </td>
        </tr>

        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>
</body>

2.测试

在这里插入图片描述

1.故意输入错误

在这里插入图片描述

2.登录成功

在这里插入图片描述

三、base64编码和JWT

①:什么是Base64

所谓Base64,就是说选出64个字符:小写字母a-z、大写字母A-Z、数字0-9、符号"+“、”/“(再加上作为垫字的”=",实际上是使用65个字符),作为一个基本字符集。然后,其他所有符号都转换成这个字符集中的字符。

1.Base64和Base64Url 的区别

Base64Url是一种在Base64的基础上编码形成新的编码方式,为了编码能在网络中安全顺畅传输,需要对Base64进行的编码,特别是互联网中。

  • Base64Url 编码的流程:
 1、明文使用BASE64进行编码
 2、在Base64编码的基础上进行以下的处理:
    1)去除尾部的"="
    2)把"+"替换成"-"
    3)斜线"/"替换成下划线"_"

②:跨域认证问题和JWT 实现登录原理图

1.跨域认证问题

互联网服务离不开用户认证。一般流程是下面这样。

  • 用户向服务器发送用户名和密码。
  • 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  • 服务器向用户返回一个 jsession_id,写入用户的 Cookie。
  • 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  • 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。 服务器不存数据,客户端存,服务器解析就行了

2.JWT 实现登录原理图

在这里插入图片描述
说明:
JWT只通过算法实现对Token合法性的验证,不依赖数据库,Memcached的等存储系统,因此可以做到跨服务器验证,只要密钥和算法相同,不同服务器程序生成的Token可以互相验证。

③:JWT学习

1.简介

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。 此信息可以通过数字签名进行验证和信任。 JWT可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

  • 官方网址:https://jwt.io/
  • 调试页面:https://jwt.io/
  • 学习文档:https://jwt.io/introduction/

2.用途

授权: 这是我们使用JWT最广泛的应用场景。一次用户登录,后续请求将会包含JWT,对于那些合法的token,允许用户连接路由,服务和资源。目前JWT广泛应用在SSO(Single Sign On)(单点登录)上。因为他们开销很小并且可以在不同领域轻松使用。

信息交换: JSON Web Token是一种在各方面之间安全信息传输的好的方式 因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。 此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改

3.JWT组成

一个JWT由三部分组成,各部分以点分隔:

Header(头部)-----base64Url编码的Json字符串
Payload(载荷)---base64url编码的Json字符串
Signature(签名)---使用指定算法,通过Header和Playload加盐计算的字符串

一个JWT看起来像下面这样:

xxxxx.yyyyy.zzzzz

下面这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

01.Header

此部分有两部分组成:

  • 一部分是token的类型,目前只能是JWT
  • 另一部分是签名算法,比如HMAC 、 SHA256 、 RSA

示例:

{
  "alg":"HS256",
  "typ":"JWT"
}

base64编码命令:

echo -n '{"alg":"HS256","typ":"JWT"}' | base64
02.Payload

token的第二部分是payload(有效负载),其中包含claims(声明)。Claims是关于一个实体(通常是用户)和其他数据类型的声明。

claims有三种类型:registered,public,and private claims。

Registered(已注册的声明):这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行人),exp(到期时间),sub(主题),aud(观众)and others。(请注意,声明名称只有三个字符,因为JWT意味着紧凑。)

JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息(密码,手机号等)放在这个部分。
这个 JSON 对象也要使用 Base64URL 算法转成字符串。

Public(公开声明):这些可以由使用JWT的人随意定义。 但为避免冲突,应在IANA JSON Web Token Registry中定义它们,或者将其定义为包含防冲突命名空间的URI。

private (私人声明):这些声明是为了在同意使用它们的各方之间共享信息而创建的,并且既不是注册声明也不是公开声明

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

03.Signature(保证数据安全性的)

Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + “.” +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.)分隔,就可以返回给用户。

示例:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

4. JWT 的使用方式【重点】

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
Authorization: Bearer jwt
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面

5.JWT 的几个特点

JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
JWT 不加密的情况下,不能将秘密数据写入 JWT。
JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑(JWT的登出问题)。就是因为服务端无状态了
正常情况下 修改了密码后就会跳转到登录页面 :修改成功后清空浏览器保存的token了
后端怎么玩? 因为服务端不保留token 我用之前的token 还是可以继续访问的
从有状态(后端也会存一个)的变成无状态的了
我们就要把它从无状态再变成有状态了
JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
为了减少盗用,JWT 不应该使用 HTTP 80 协议明码传输,要使用 HTTPS 443 协议传输。

我们颁发一个令牌 用户名称 用户的权限信息 这个令牌2个小时有效
Jwt只要能解析 就认为你是可用的 做不了 登出 后端不存储用户信息了 后端无状态了

④:Java类库

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

⑤:java中使用jwt

1.新建maven工程jwt-learn1

2.引入依赖

<!-- 添加jwt的依赖 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
</dependency>

3.编写功能类

package com.it.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * @Author: CaoYouGen
 * @DateTime: 2023/11/13/12:03
 * @注释: 用于生成和解析JWT
 **/
public class UtilJWT {
    
    // 声明一个密钥
    private static final String SECRET = "Coke_Anne";

    /*
     * TODO
     * 
     * @param userId: 用户编号
     * @param userName: 用户名
     * @param auth: 用户权限  
     * @return String
     * @author: CaoYouGen
     * @DateTime: 2023/11/13 12:06
     */
    public static String createToken(Integer userId, String userName, List<String> auth){
        // 得到当前的系统时间
        Date currentDate = new Date();
        // 根据当前时间计算出过期时间 定死为5分钟
        Date expTime = new Date(currentDate.getTime() + (1000 * 60 * 5));
        // 组装头数据
        HashMap<String, Object> header = new HashMap<>();
        header.put("alg","HS256");
        header.put("typ","JWT");
        return JWT.create()
                .withHeader(header) // 头
                .withIssuedAt(currentDate) // 创建时间
                .withExpiresAt(expTime) // 过期时间
                .withClaim("userId",userId) // 自定义数据
                .withClaim("userName",userName) // 自定义数据
                .withClaim("auth",auth) // 自定义数据
                .sign(Algorithm.HMAC256(SECRET));
    }
    
    public static Boolean verifyToken(String token){
        try {
            // 使用密码创建一个解析对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            // 验证 JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            return true;
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return false;
    }
    
    /*
     * 获取JWT里面相前的用户编号
     * 
     * @param token:  
     * @return Integer
     * @author: CaoYouGen
     * @DateTime: 2023/11/13 12:19
     */
    public static Integer getIUserId(String token){
        try {
            // 使用密码创建一个解析对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            // 验证 JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            Claim userId = decodedJWT.getClaim("userId");
            return userId.asInt();
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取JWT里面相前的用户名
     */
    public static String getUsername(String token){
        try{
            // 使用秘钥创建一个解析对象
            JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            //验证JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            Claim username = decodedJWT.getClaim("userName");
            return username.asString();
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取JWT里面相前权限
     */
    public static List<String> getAuth(String token){
        try{
            // 使用秘钥创建一个解析对象
            JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            //验证JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            Claim auth = decodedJWT.getClaim("auth");
            return auth.asList(String.class);
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return null;
    }



}

4.测试一下

package com.it.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * @Author: CaoYouGen
 * @DateTime: 2023/11/13/12:03
 * @注释: 用于生成和解析JWT
 **/
public class UtilJWT {
    
    // 声明一个密钥
    private static final String SECRET = "Coke_Anne";

    /*
     * TODO
     * 
     * @param userId: 用户编号
     * @param userName: 用户名
     * @param auth: 用户权限  
     * @return String
     * @author: CaoYouGen
     * @DateTime: 2023/11/13 12:06
     */
    public static String createToken(Integer userId, String userName, List<String> auth){
        // 得到当前的系统时间
        Date currentDate = new Date();
        // 根据当前时间计算出过期时间 定死为5分钟
        Date expTime = new Date(currentDate.getTime() + (1000 * 60 * 5));
        // 组装头数据
        HashMap<String, Object> header = new HashMap<>();
        header.put("alg","HS256");
        header.put("typ","JWT");
        return JWT.create()
                .withHeader(header) // 头
                .withIssuedAt(currentDate) // 创建时间
                .withExpiresAt(expTime) // 过期时间
                .withClaim("userId",userId) // 自定义数据
                .withClaim("userName",userName) // 自定义数据
                .withClaim("auth",auth) // 自定义数据
                .sign(Algorithm.HMAC256(SECRET));
    }
    
    public static Boolean verifyToken(String token){
        try {
            // 使用密码创建一个解析对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            // 验证 JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            return true;
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return false;
    }
    
    /*
     * 获取JWT里面相前的用户编号
     * 
     * @param token:  
     * @return Integer
     * @author: CaoYouGen
     * @DateTime: 2023/11/13 12:19
     */
    public static Integer getIUserId(String token){
        try {
            // 使用密码创建一个解析对象
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
            // 验证 JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            Claim userId = decodedJWT.getClaim("userId");
            return userId.asInt();
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取JWT里面相前的用户名
     */
    public static String getUsername(String token){
        try{
            // 使用秘钥创建一个解析对象
            JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            //验证JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            Claim username = decodedJWT.getClaim("userName");
            return username.asString();
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取JWT里面相前权限
     */
    public static List<String> getAuth(String token){
        try{
            // 使用秘钥创建一个解析对象
            JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(SECRET)).build();
            //验证JWT
            DecodedJWT decodedJWT = jwtVerifier.verify(token);
            Claim auth = decodedJWT.getClaim("auth");
            return auth.asList(String.class);
        }catch (TokenExpiredException e){
            e.printStackTrace();
        }
        return null;
    }
}

在这里插入图片描述

⑥:JWT的总结

JWT就是一个加密的带用户信息的字符串, 没学习JWT之前,我们在项目中都是返回一个基本的字符串,然后请求时带上这个字符串,再从session或者redis中(共享session)获取当前用户,学过JWT以后我们可以把用户信息直接放在字符串返回给前端,然后用户请求时带过来,我们是在服务器进行解析拿到当前用户,这就是两种登录方式,这两种方式有各自的优缺点。

四、JWT+Spring Security+redis+mysql 实现认证

①:新建模块添加依赖和配置

1.复制工程06_spring_security_captcha,改名字为
08_spring_security_jwt

2.添加jwt依赖

<!-- 添加jwt的依赖 -->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.11.0</version>
</dependency>

3.application.yml 中配置密钥


jwt:
  secretKey: Coke_Anne

②:jwt功能类

package com.it.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * @Author: CaoYouGen
 * @DateTime: 2023/11/13/12:35
 * @注释: TODO
 **/

@Component
@Slf4j
public class UtilJWT {
    @Value ("${jwt.secretKey}")
    private String jwtSecretKey;
    
    /*
     * 创建jwt
     *
     * @param userInfo: 用户信息
     * @param authList:  用户权限列表
     * @return String 返回jwt(Json Web Token)
     * @author: CaoYouGen
     * @DateTime: 2023/11/13 12:39
     */
    public String createToken (String userInfo, List<String> authList) {
        // 创建时间
        Date currentTime = new Date();
        // 过期时间,5分钟后过期
        Date expiretime = new Date(currentTime.getTime() + (1000 * 60 * 5));
        
        // jwt的header信息
        HashMap<String, Object> header = new HashMap<>();
        header.put("alg", "HS256");
        header.put("typ", "JWT");
        return JWT.create()
              .withHeader(header) // 设置头部信息
              .withIssuedAt(currentTime) // 设置签发日期(创建时间)
              .withExpiresAt(expiretime) // 设置过期时间
              .withIssuer("thomas") // 设置签发人
              .withClaim("userInfo", userInfo) // 私有声明 可以自己定义
              .withClaim("authList", authList) // 私有声明 可以自己定义
              .sign(Algorithm.HMAC256(jwtSecretKey)); // 签名 使用HS256算法签名,并使用密钥
        // HS256是一种对称算法,这意味着只有一个密钥,在双方之间共享。 使用相同的密钥生成签名并对其进行验证。 应特别注意钥匙是否保密。
    }
    
    /*
     * 验签jwt的签名简称验签
     * @DateTime: 2023/11/13 19:09
     *
     * @param token: 需要验签的token
     * @return boolean 验签结果
     * @author: Coke
     */
    public boolean verifyToken (String token) {
        // 创建一个验签类对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            // 验签,如果不报错,则说明 jwt 是合法的,而且也没有过期
            DecodedJWT verify = jwtVerifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            // 如果报错说明 jwt 为非法的,或者已过期(已过期也属于非法的)
            e.printStackTrace();
            log.error("验证失败:{}", token);
        }
        return false;
    }
    
    /*
     * 获取用户信息
     * @DateTime: 2023/11/13 19:20
     *
     * @param token: jwt
     * @return String 用户信息
     * @author: Coke
     */
    public String getUserInfo (String token) {
        // 创建一个jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            // 验签
            DecodedJWT verify = jwtVerifier.verify(token);
            String userInfo = verify.getClaim("userInfo").asString();
            return userInfo;
        } catch (JWTCreationException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /*
     * 获取用户的权限列表
     * @DateTime: 2023/11/13 19:25
     *
     * @param token: jwt
     * @return List<String> 权限列表
     * @author: Coke
     */
    public List<String> getUserAuth (String token) {
        // 创建一个jwt验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecretKey)).build();
        try {
            DecodedJWT verify = jwtVerifier.verify(token);
            List<String> authList = verify.getClaim("authList").asList(String.class);
            return authList;
        }catch (JWTCreationException e){
            e.printStackTrace();
            return null;
        }
    }
}

③:添加响应类

package com.it.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * TODO
 *
 * @author: Coke
 * @DateTime: 2023/11/13/19:27
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class HttpResult implements Serializable {
    private Integer code; // 响应码
    private String msg; // 响应消息
    private Object data; // 响应对象
}

④:修改SecurityUser类

加入一个获取SysUser的方法

public SysUser getSysUser() {
    return sysUser;
}

⑤:新建认证成功处理器

package com.it.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.it.entity.SysUser;
import com.it.utils.UtilJWT;
import com.it.vo.HttpResult;
import com.it.vo.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @author: Coke
 * @DateTime: 2023/11/13/19:33
 **/
@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    // 使用此工具进行序列化
    @Autowired
    private ObjectMapper objectMapper;
    
    @Autowired
    private UtilJWT utilJWT;
    
    @Override
    public void onAuthenticationSuccess (HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        // 从认证对象中获取认证用户信息
        SecurityUser securityUser = (SecurityUser) authentication.getPrincipal();
        SysUser sysUser = securityUser.getSysUser();
        String userInfo = objectMapper.writeValueAsString(sysUser);
        List<SimpleGrantedAuthority> authorities = (List<SimpleGrantedAuthority>) securityUser.getAuthorities();
        // 使用stream流
        List<String> authList = authorities.stream().map(SimpleGrantedAuthority :: getAuthority).collect(Collectors.toList());
        // 创建jwt
        String token = utilJWT.createToken(userInfo, authList);
        log.info("token= {}",token);
        // 返回给前端 token
        HttpResult httpResult = HttpResult.builder()
              .code(1)
              .msg("jwt验证成功!")
              .data(token).build();
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(objectMapper.writeValueAsString(httpResult));
        writer.flush();
    }
}

⑥:新建jwt过滤器,用于检查token等

package com.it.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.it.entity.SysUser;
import com.it.utils.UtilJWT;
import com.it.vo.HttpResult;
import com.it.vo.SecurityUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
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 javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @author: Coke
 * @DateTime: 2023/11/12/14:07
 **/
@Component
@Slf4j
public class ValidateCodeFilter extends OncePerRequestFilter {
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Autowired
    private UtilJWT utilJWT;
    
    @Override
    protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        log.info("请求路径为:{}",requestURI);
        
        // 判断请求的是否为登录页面
        if (!"/login/doLogin".equals(requestURI)){ // 不是登录请求,直接放行
            doFilter(request,response,filterChain); //直接下一个
            return;
        }
        //校验验证码
        validateCode(request,response,filterChain);
    }
    
    private void validateCode (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
       // 获取用户输入的验证码
        String code = request.getParameter("code");
        // 获取session中存储的验证码
        HttpSession session = request.getSession();
        String login_captcha_code = (String) session.getAttribute("CAPTCHA_CODE");
        log.info("用户输入的验证码: {}, session中存储的验证码: {}", code, login_captcha_code);
        
        // 移除错误信息
        session.removeAttribute("captchaCodeErrorMsg");
        
        if (!StringUtils.hasText(code)){
            request.getSession().setAttribute("captchaCodeErrorMsg", "请输入验证码!");
            // 重定向到登录页面
            response.sendRedirect("/login/toLogin");
            return;
        }
        
        if (!StringUtils.hasText(login_captcha_code)){
            request.getSession().setAttribute("captchaCodeErrorMsg", "验证码错误!");
            // 重定向到登录页面
            response.sendRedirect("/login/toLogin");
        }
        
        if (!code.equalsIgnoreCase(login_captcha_code)){
            request.getSession().setAttribute("captchaCodeErrorMsg", "验证码输入错误!");
            // 重定向到登录页面
            response.sendRedirect("/login/toLogin");
        }
    
        //获取请求头中的Authorization
        String authorization = request.getHeader("Authorization");
        //如果Authorization为空,那么不允许用户访问,直接返回
        if (!StringUtils.hasText(authorization)) {
            printFront(response, "没有登录!");
            return;
        }
        //Authorization 去掉头部的Bearer 信息,获取token值
        String jwtToken = authorization.replace("Bearer ", "");
        //验签
        boolean verifyTokenResult = utilJWT.verifyToken(jwtToken);
        //验签不成功
        if (!verifyTokenResult) {
            printFront(response, "jwtToken 已过期");
            return;
        }
    
        //从payload中获取userInfo
        String userInfo = utilJWT.getUserInfo(jwtToken);
        //从payload中获取授权列表
        List<String> userAuth = utilJWT.getUserAuth(jwtToken);
        //创建登录用户
        SysUser sysUser = objectMapper.readValue(userInfo, SysUser.class);
        SecurityUser securityUser = new SecurityUser(sysUser);
        //设置权限
        List<SimpleGrantedAuthority> authList = userAuth.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        securityUser.setSimpleGrantedAuthorities(authList);
    
    
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToke = new UsernamePasswordAuthenticationToken(securityUser
              , null, authList);
        //通过安全上下文设置认证信息
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToke);
        //继续访问相应的rul等
        filterChain.doFilter(request, response);
        // 删除session中的验证码
        session.removeAttribute("code");
        this.doFilter(request,response,filterChain);
        
    
    }
    private void printFront(HttpServletResponse response, String message) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        HttpResult httpResult = new HttpResult();
        httpResult.setCode(-1);
        httpResult.setMsg(message);
        
        writer.print(objectMapper.writeValueAsString(httpResult));
        writer.flush();
    }
}

⑦:修改 web安全配置类WebSecurityConfig

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Resource
    private JwtCheckFilter jwtCheckFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(jwtCheckFilter, UsernamePasswordAuthenticationFilter.class);
        http.formLogin().successHandler(myAuthenticationSuccessHandler).permitAll();
http.authorizeRequests()
        .mvcMatchers("/student/**").hasAnyAuthority("student:query","student:update")

                .anyRequest().authenticated(); //任何请求均需要认证(登录成功)才能访问
        http.csrf().disable();
        //禁用session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

⑧:启动测试

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

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

相关文章

岗前酒精检测仪

岗前酒精检测仪&#xff1a;集“酒精检测智能测温人脸考勤”三合一智能检测仪。 酒精检测功能&#xff1a;采用电化学传感器检测检测酒精浓度&#xff0c;具有吹气中断及吹气流量侦测&#xff0c;吹气防欺骗设计&#xff0c;吹气温度及吸气侦测&#xff1b;响应时间≤20毫秒&am…

编程实现ROS话题通信——手撕 turtlesim 节点

一、实验任务 任务要求如下&#xff1a;使用 C 编程实现1个ROS节点&#xff0c;订阅 turtlesim 例程中 turtle_teleop_key 节点发出的消息&#xff0c;并将此消息进行一定的转换后&#xff08;自行定义&#xff0c;如将数值按适当比例缩小、运动方向变换等&#xff09;&#xf…

【CANN】SPI通信、I2C通信

文章目录 一、SPI通信1.SPI通信2.SPI通信原理3.CPOL时钟极性、CPHA时钟相位 二、I2C通信1、I2C通信2、I2C通信原理2.1、I2C的起始和结束条件2.2、数据传送格式 总结 一、SPI通信 1.SPI通信 SPI是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接…

@Async注解的坑

问题描述 一个方法调用另一个方法(该方法使用Async注解)在同一个类文件中&#xff0c;该注解会失效&#xff01; 问题复现 TestAsyncController 类 import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Async; im…

el-table解决数据过少小于高度有留白的问题

问题:给el-table设置个高度,高度为500px,之后就添加如下4条数据,那么底部就没数据,直接就空白了,本文章就是为了解决这个问题,如果底部留白那么就添加几条空数据就行了.如果数据已达到高度了那么就不会留白了 1.效果 这个空列可以根据高度来决定添加几个空格子去铺满列表&…

基于头脑风暴算法优化概率神经网络PNN的分类预测 - 附代码

基于头脑风暴算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于头脑风暴算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于头脑风暴优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

数字三角形模型 笔记

方格取数 走两次的最大值 f[k][i1][i2]来表示 k i1 j1 i2 j2; 每一个状态可由四种状态转换来&#xff0c;分别为 第一条路走下&#xff0c;第二条路走下 第一条路走下&#xff0c;第二条路走右 第一条路走右&#xff0c;第二条路走下 第一条路走右&#xff0c;第二条…

一文了解多线程实现交替执行

本文已收录于专栏 《Java》 目录 背景介绍实现方式总结提升 背景介绍 有一个需求是要求多个线程去执行任务&#xff0c;需要每个线程都执行一次之后再继续执行&#xff0c;也就是说每个线程交替去执行任务。举个例子来说&#xff0c;有两个线程&#xff0c;一个输出字母&#x…

全球步入VUCA时代,FinOps如何成为云计算下一个关键性机会?

科技云报道原创。 俗话说&#xff0c;省下来的就是利润。在全球都处于VUCA的时代&#xff0c;更是如此。 近年来&#xff0c;随着云计算的广泛应用,云成本管理成为企业关注的重点。FinOps&#xff08;云成本优化&#xff09;作为一种新兴的云成本管理方式&#xff0c;吸引了行…

基于蝴蝶算法优化概率神经网络PNN的分类预测 - 附代码

基于蝴蝶算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蝴蝶算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蝴蝶优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

成都爱尔周进院长解析高度近视可能引发哪些疾病

当代各类人群面对电脑、手机屏幕的时长显著增加&#xff0c;导致用眼过度、疲劳&#xff0c;视觉质量下降&#xff0c;近视人群越来越多。而当父母有一方为高度近视甚至可能将近视遗传给孩子。 目前&#xff0c;全球近视人数约25亿&#xff0c;中国近视人群人数多达6亿。据预测…

Unity--互动组件(Button)

1.组件的可交互 2.组件的过渡状态 3.组件的导航 4.组件的Event Button “”组件的可交互&#xff1a;“” Interactable&#xff1a; 该组件是否可点击&#xff08;设置为false时&#xff0c;将禁用交互&#xff0c;并且过渡状态将设置为禁用状态&#xff09;&#xff1b;…

基于RK3568的跑步机方案

I 方案简介 一、跑步机的来历 跑步机是家庭及健身房常备的健身器材&#xff0c;而且是当今家庭健身器材中最简单的一种&#xff0c;是家庭健身器的最佳选择。1965年北欧芬兰唐特力诞生了全球第一台家用的跑步机&#xff0c;设计师根据传速带的原理改变而成。 二、…

对红黑树的理解与实现(C++实现)

认识红黑树 在看到此篇文章之前最好还是先了解一下左右旋也就是AVL树的插入数据该如何处理。AVL树的插入详解-CSDN博客 红黑树&#xff0c;也属于是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是红色&#xff08;red&#xff09;…

小程序中如何设置多门店/多人/多商品价格库存等复杂场景设置

有些商家希望打造小程序平台&#xff0c;在这个平台上有多个商家入驻&#xff0c;他们分别售卖自己的商品。而有些商家有多个连锁店&#xff0c;连锁店的商品都是一样的&#xff0c;但不同的连锁店有不同的库存和价格。这些业务在采云小程序中是怎么支持的呢&#xff1f;下面具…

office365 outlook邮件无法删除

是否遇到过&#xff0c;office365邮件存储满了&#xff0c;删除邮件无法删除&#xff0c;即便用web方式登录到outlook&#xff0c;删除邮件当时是成功的&#xff0c;但一会儿就回滚回来了&#xff0c;已删除的邮件&#xff0c;你想清空&#xff0c;最后清理后还是回到原样。 请…

curl(八)时间和环境变量以及配置

一 时间 ① --connect-timeout 连接超时时间 ② -m | --max-time 数据最大传输时间 -m&#xff1a; 限制curl 完成时间(overall time limit)-m,--max-time <seconds> 整个交互完成的超时时间场景&#xff1a; 通过设置-m参数,可以避免请求时间过长而导致的超时错误…

纯CSS实现魔法渐变边框卡片

如图所示&#xff0c;这是一个很炫酷的卡片效果&#xff0c;关键效果在于卡片的边框呈渐变色变化着&#xff0c;在网页中有这样一个卡片相信可以极大的增强用户体验交互。本次文章将解读如何使用纯CSS实现这个炫酷的卡片效果。 基于上面的动图可以分析出以下是本次实现的主要几…

如何计算掩膜图中多个封闭图形的面积

import cv2def calMaskArea(image,idx):mask cv2.inRange(image, idx, idx)contours, hierarchy cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)for contour in contours:area cv2.contourArea(contour)print("图形的面积为", area) image是…

中国银行模拟器app,用java设计框架,图片网上找的,提供代码,仅供娱乐

回执单生成器的Java程序需要涉及到一些基本的Java编程技能&#xff0c;包括创建类、处理用户输入和格式化输出。下面是一个简单的示例代码&#xff0c;用于生成一个简易的回执单。这个程序将接收用户的输入&#xff0c;然后生成一个格式化的回执单。 请注意&#xff0c;这个示…