【业务功能篇58】Springboot + Spring Security 权限管理 【中篇】

spring boot —— Spring Security定制权限管理 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。提供了完善的认证机制和方法级的授权功能。是一款非常优秀的权限管理框架。它的核心是一组过滤。类似于启动spring boot项目后,会先验证,如果没有登陆,会跳转到框架默认登录页面(封装好的,不用自己写)如果没有配置登录用户,那么终端会自动生成用户并打印随机生成的用户密码,如果有配置,就不会生成。... 阅读详情

4.2.3 认证

4.2.3.1 什么是认证(Authentication)
  • 通俗地讲就是验证当前用户的身份,证明“你是你自己”(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系统里录入的指纹相匹配时,就打卡成功)
  • 互联网中的认证
    • 用户名密码登录
    • 邮箱发送登录链接
    • 手机号接收验证码
    • 只要你能收到邮箱/验证码,就默认你是账号的主人
4.2.3.2 两种认证方式
1) 基于Session的认证方式

session 认证流程:

image.png

  1. 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
  2. 请求返回时将此 Session 的唯一标识 SessionID 返回给浏览器
  3. 浏览器接收到服务器返回的 SessionID 后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
  4. 当用户第二次访问服务器的时候,请求会自动把此域名下的 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。

session 认证存在的问题

  • 在分布式的环境下,基于session的认证会出现一个问题,每个应用服务都需要在session中存储用户身份信息,通过负载均衡将本地的请求分配到另一个应用服务需要将session信息带过去,否则会重新认证。我们可以使用Session共享、Session黏贴等方案。
2) 基于Token的认证方式

什么是Token? (令牌)

  • 访问资源接口(API)时所需要的资源凭证
  • 简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)

服务器对 Token 的存储方式:

  1. 存到数据库中,每次客户端请求的时候取出来验证(服务端有状态)
  2. 存到 redis 中,设置过期时间,每次客户端请求的时候取出来验证(服务端有状态)
  3. 不存,每次客户端请求的时候根据之前的生成方法再生成一次来验证(JWT,服务端无状态)

Token特点:

  • 服务端无状态化、可扩展性好
  • 支持移动端设备
  • 安全
  • 支持跨程序调用

token 的身份验证流程:

image.png

  1. 客户端使用用户名跟密码请求登录
  2. 服务端收到请求,去验证用户名与密码
  3. 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
  4. 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
  6. 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据

每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里

注意:

登录时 token 不宜保存在 localStorage,被 XSS 攻击时容易泄露。所以比较好的方式是把 token 写在 cookie 里。为了保证 xss 攻击时 cookie 不被获取,还要设置 cookie 的 http-only。这样,我们就能确保 js 读取不到 cookie 的信息了。再加上 https,能让我们的请求更安全一些。

token认证方式的优缺点

  • 优点: 基于token的认证方式,服务端不用存储认证数据,易维护扩展性强, 客户端可以把token 存在任意地方,并且可以实现web和app统一认证机制。
  • 缺点: token由于自包含信息,因此一般数据量较大,而且每次请求都需要传递,因此比较占带宽。另外,token的签名验签操作也会给cpu带来额外的处理负担。
3) Token 和 Session 的区别
  • Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
  • Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重复攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。

如果你的用户数据可能需要和第三方共享,或者允许第三方调用 API 接口,用 Token 。如果永远只是自己的网站,自己的 App,用什么就无所谓了。

4.2.3.3 JWT (JSON Web Token)
(1) JWT简介

什么是JWT

  • JWT是一种基于 Token 的****认证授权机制.
  • JWT (JSON Web Token) 是目前最流行的跨域认证解决方案,是一种基于 Token 的认证授权机制。从 JWT 的全称可以看出,JWT 本身也是 Token,一种规范化之后的 JSON 结构的 Token。

JWT有什么用

  • JWT最常见的场景就是授权认证,用户登录之后,后续的每个请求都将包含JWT, 系统在每次处理用户请求之前,都要先进行JWT的安全校验,通过校验之后才能进行接下来的操作.

JWT认证方式

  • JWT通过数字签名的方式,以JSON对象为载体,在用户和服务器之间传递安全可靠的信息.

image.png

  1. 首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(Token)。形成的JWT就是一个形同lll.zzz.xxx的字符串。 token head.payload.singurater
  3. 后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。
  4. 前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题) HEADER
  5. 后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。
  6. 验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。
(2) JWT的组成部分

头部(Header)

  • 描述 JWT 的元数据,定义了生成签名的算法以及 Token 的类型。
 {"typ":"JWT","alg":"HS256"}
 
 //typ(Type):令牌类型,也就是 JWT。
 //alg(Algorithm) :签名算法,比如 HS256。

JWT签名算法中,一般有两个选择,一个采用HS256,另外一个就是采用RS256

进行BASE64编码https://base64.us/,编码后的字符串如下:eyJhbGciOiJIUzI1NiJ9

image.png

载荷(payload)

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

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

将上面的JSON数据进行base64编码,得到Jwt第二部分: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

image.png

字段说明,下面的字段都是由 JWT的标准所定义的

 iss: jwt签发者
 sub: jwt所面向的用户
 aud: 接收jwt的一方
 exp: jwt的过期时间,这个过期时间必须要大于签发时间
 nbf: 定义在什么时间之前,该jwt都是不可用的.
 iat: jwt的签发时间
 jti: jwt的唯一身份标识,主要用来作为一次性token。

签名(signature)

服务器通过 Payload、Header 和一个密钥(Secret) 使用 Header 里面指定的签名算法(默认是 HMAC SHA256)生成。

Signature 部分是对前两部分的签名,作用是防止 Token(主要是 payload) 被篡改。

这个签名的生成需要用到:

  • Header + Payload。
  • 存放在服务端的密钥(一定不要泄露出去)。
  • 签名算法。

例如,如果要使用HMAC SHA256算法,则将通过以下方式创建签名:

 String encodeString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
 String secret = HMACSHA256(encodeString,secret);

签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份。

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

(3) 签名的目的

最后一步签名的过程,实际上是对头部以及载荷内容进行签名。

image.png

一般而言,加密算法对于不同的输入产生的输出总是不一样的。对于两个不同的输入,产生同样的输出的概率极其地小(有可能比我成世界首富的概率还小)。所以,我们就把“不一样的输入产生不一样的输出”当做必然事件来看待吧。

所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。

服务器应用在接受到JWT后,会首先对头部和载荷的内容用同一算法再次签名。那么服务器应用是怎么知道我们用的是哪一种算法呢?别忘了,我们在JWT的头部中已经用 alg字段指明了我们的加密算法了。

如果服务器应用对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个Token的内容被别人动过的,我们应该拒绝这个Token,返回一个HTTP 401 Unauthorized响应。

image.png

(4) JWT与Token的区别

Token 和 JWT (JSON Web Token) 都是用来在客户端和服务器之间传递身份验证信息的一种方式。但是它们之间有一些区别。

  • Token 是一个通用术词,可以指代任何用来表示身份的字符串。它可以是任何形式的字符串,并不一定是 JWT。
  • JWT 是一种特殊的 Token,它是一个 JSON 对象,被编码成字符串并使用秘密密钥进行签名。JWT 可以用来在身份提供者和服务提供者之间安全地传递身份信息,因为它可以被加密,并且只有拥有秘密密钥的方能解密。

总的来说,JWT 是一种特殊的 Token,它具有更强的安全性和可靠性。

(5) JWT的优势
  • 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
  • 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
  • 不需要在服务端保存会话信息,特别适用于分布式微服务。
4.2.3.4 JJWT签发与验证token

使用jjwt实现jwt的签发和解析获取payload中的数据.

JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0)。

官方文档:https://github.com/jwtk/jjwt

(1) 引入依赖
 <!--jwt依赖-->
 <dependency>
     <groupId>io.jsonwebtoken</groupId>
     <artifactId>jjwt</artifactId>
     <version>0.9.1</version>
 </dependency>
(2) 创建 Token
 @SpringBootTest
 class SpringsecurityExampleApplicationTests {
 
     @Test
     void contextLoads() {
     }
 
     @Test
     public void testJJWT(){
 
         JwtBuilder builder = Jwts.builder()
                 .setId("9527")  //设置唯一ID
                 .setSubject("hejiayun_community")   //设置主体
                 .setIssuedAt(new Date())    //设置签约时间
                 .signWith(SignatureAlgorithm.HS256, "mashibing");//设置签名 使用HS256算法,并设置SecretKey
 
         //压缩成String形式,签名的JWT称为JWS
         String jws = builder.compact();
         System.out.println(jws);
         
         /**
          * eyJhbGciOiJIUzI1NiJ9.
          * eyJqdGkiOiI5NTI3Iiwic3ViIjoiaGVqaWF5dW5fY29tbXVuaXR5IiwiaWF0IjoxNjgxMTM1ODY2fQ.
          * ybkDJLVj1Fsi8m3agyxtyd0wxv7lHDqCWNOLN-eOxC8
          */
     }
 
 }

运行打印结果:

 eyJhbGciOiJIUzI1NiJ9.
 eyJqdGkiOiI5NTI3Iiwic3ViIjoiaGVqaWF5dW5fY29tbXVuaXR5IiwiaWF0IjoxNjgxMTM1ODY2fQ.
 ybkDJLVj1Fsi8m3agyxtyd0wxv7lHDqCWNOLN-eOxC
(3) 解析Token

我们刚才已经创建了token ,在web应用中这个操作是由服务端进行然后发给客户端,客户端在下次向服务端发送请求时需要携带这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果。

解析JJWS的方法如下:

  1. 使用该 Jwts.parser()方法创建 JwtParserBuilder实例。
  2. setSigningKey() 与builder中签名方法signWith()对应,parser中的此方法拥有与signWith()方法相同的三种参数形式,用于设置JWT的签名key,用户后面对JWT进行解析。
  3. 最后,parseClaimsJws(String)用您的jws调用该方法,生成原始的JWS。
  4. 如果解析或签名验证失败,则整个调用将包装在try / catch块中。
 @Test
 public void parserJWT(){
 
     String JWS = "eyJhbGciOiJIUzI1NiJ9." +
         "eyJqdGkiOiI5NTI3Iiwic3ViIjoiaGVqaWF5dW5fY29tbXVuaXR5IiwiaWF0IjoxNjgxMTM1ODY2fQ." +
         "ybkDJLVj1Fsi8m3agyxtyd0wxv7lHDqCWNOLN-eOxC8";
 
     //claims = 载荷 (payload)
 
     try {
         Claims claims = Jwts.parser().setSigningKey("mashibing")
             .parseClaimsJws(JWS)
             .getBody();
         System.out.println(claims);
     } catch (Exception e) {
         System.out.println("Token验证失败! !");
         e.printStackTrace();
     }
 }

运行打印结果:

 {jti=9527, sub=hejiayun_community, iat=1681135866}
 iat: jwt的签发时间
 jti: jwt的唯一身份标识,主要用来作为一次性token。
 sub: jwt所面向的用户
(4) 设置过期时间

有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间。

  • 创建token 并设置过期时间
     @Test
     public void testJJWT2(){
 
         long currentTimeMillis = System.currentTimeMillis();
         Date expTime = new Date(currentTimeMillis);
 
         JwtBuilder builder = Jwts.builder()
                 .setId("9527")  //设置唯一ID
                 .setSubject("hejiayun_community")   //设置主体
                 .setIssuedAt(new Date())    //设置签约时间
                 .setExpiration(expTime)     //设置过期时间
                 .signWith(SignatureAlgorithm.HS256, "mashibing");//设置签名 使用HS256算法,并设置SecretKey
 
         //压缩成String形式,签名的JWT称为JWS
         String jws = builder.compact();
         System.out.println(jws);
 
         /**
          * eyJhbGciOiJIUzI1NiJ9.
          * eyJqdGkiOiI5NTI3Iiwic3ViIjoiaGVqaWF5dW5fY29tbXVuaXR5IiwiaWF0IjoxNjgxMTM3MjI0LCJleHAiOjE2ODExMzcyMjR9.
          * evc01MRxLjpbksbMLdVPM9sJGYGhpC3UYOfm4-0sMGE  
          */
     }
  • 解析TOKEN
 打印效果: 异常信息: JWT签名与本地计算的签名不匹配。JWT有效性不能断言,也不应该被信任
 
 Token验证失败! !
 io.jsonwebtoken.MalformedJwtException: JWT strings must contain exactly 2 period characters. Found: 
(5) 自定义claims

我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims。

创建测试类,并设置测试方法:

     @Test
     public void testJJWT3(){
 
         long currentTimeMillis = System.currentTimeMillis()+100000000L;
         Date expTime = new Date(currentTimeMillis);
 
         JwtBuilder builder = Jwts.builder()
                 .setId("9527")  //设置唯一ID
                 .setSubject("hejiayun_community")   //设置主体
                 .setIssuedAt(new Date())    //设置签约时间
                 .setExpiration(expTime)     //设置过期时间
                 .claim("roles","admin")       //设置角色
                 .signWith(SignatureAlgorithm.HS256, "mashibing");//设置签名 使用HS256算法,并设置SecretKey
 
         //压缩成String形式,签名的JWT称为JWS
         String jws = builder.compact();
         System.out.println(jws);
 
         /**
          * eyJhbGciOiJIUzI1NiJ9.
          * eyJqdGkiOiI5NTI3Iiwic3ViIjoiaGVqaWF5dW5fY29tbXVuaXR5IiwiaWF0IjoxNjgxMTM3MjI0LCJleHAiOjE2ODExMzcyMjR9.
          * evc01MRxLjpbksbMLdVPM9sJGYGhpC3UYOfm4-0sMGE
          */
     }

解析TOKEN,打印结果

 {jti=9527, sub=hejiayun_community, iat=1681137464, exp=1681237464, roles=admin}
4.2.3.5 入门案例认证流程分析
(1) 入门案例认证流程图

image.png

image.png

1) AbstractAuthenticationProcessingFilter

  • AbstractAuthenticationProcessingFilter的职责也就非常明确: 处理所有HTTP Request和Response对象,并将其封装成AuthenticationMananger可以处理的Authentication。
  • 它的实现类 UsernamePasswordAuthenticationFilter 表示当前访问系统的用户,封装了用户相关信息。

2) AuthenticationManager

  • AuthenticationManager 定义了认证Authentication的方法 , 用来尝试对传入的Authentication对象进行认证。用于处理身份验证的核心逻辑;

    image.png

ProviderManager

  • ProviderManager是Authentication的一个实现,并将具体的认证操作委托给一系列的AuthenticationProvider来完成,从而可以实现支持多种认证方式。

3) AbstractUserDetailsAuthenticationProvider

  • ProviderManager 本身并不直接处理身份认证请求,它会委托给内部配置的Authentication Provider列表providers。该列表会进行循环遍历,依次对比匹配以查看它是否可以执行身份验证

    image.png

  • providers集合的泛型是AuthenticationProvider接口,AuthenticationProvider接口有多个实现子类

    image.png

4) DaoAuthenticationProvider

  • AuthenticationProvider接口的一个直接子类是AbstractUserDetailsAuthenticationProvider,该类又有一个直接子类DaoAuthenticationProvider.
  • Spring Security中默认就是使用Dao Authentication Provider来实现基于数据库模型认证授权工作的!

5) UserDetailsService

  • DaoAuthenticationProvider 在进行认证的时候,需要调用 UserDetailsService 对象的loadUserByUsername() 方法来获取用户信息 UserDetails,其中包括用户名、密码和所拥有的权限等。
    • 如果我们需要改变认证方式,可以实现自己的 AuthenticationProvider;
    • 如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。

6) InMemoryUserDetailsManager

  • 它是UserDetailsService接口的实现类, 在内存中维护用户信息。使用方便,但是数据只保存在内存中,重启后数据丢失.
(2) 认证流程中对象之间的关系

image.png

虽然 Spring Security 看似很复杂,但是其核心思想和以前那种简单的认证流程依然是一样的。只不过,Spring Security 将其中的关键部分抽象了处理,又提供了相应的扩展接口。

我们在使用时,便可以实现自己的 UserDetailsService 和 UserDetails 来获取保存用户信息,实现自己的 Authentication 来保存特定的用户认证信息, 实现自己的 AuthenticationProvider 使用自己的 UserDetailsService 和 Authentication 来对用户认证信息进行效验。

4.2.3.6 重构入门案例-准备工作
(1) 需求分析

登录操作

  • 自定义登录接口
    • 调用ProviderManager的方法进行认证 如果认证通过生成jwt
    • 把用户信息存入redis中
  • 自定义UserDetailsService
    • 在这个实现类中去查询数据库
(2) 添加依赖
       <!--redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--fastjson依赖-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>

        <!--jwt依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!-- Mysql驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
(3 )SpringBoot Redis缓存序列化处理

Spring Data Redis为我们封装了Redis客户端的各种操作,简化使用。

  • 当Redis当做数据库或者消息队列来操作时,我们一般使用RedisTemplate来操作
  • 当Redis作为缓存使用时,我们可以将它作为Spring Cache的实现,直接通过注解使用

SpringBoot RedisTemplate的序列化问题

  • SpringBoot RedisTemplate用来操作Key-Value为对象类型,默认采用JDK序列化类型,JDK序列化性能差,而且存储到Redis服务端是二进制不便查询,JDK序列化要求实体实现 Serializable 接口.

① 添加序列化工具类,让Redis使用FastJson序列化,提高序列化效率, 将存储在Redis中的value值,序列化为JSON格式便于查看

 /**
  * Redis使用FastJson进行序列化
  * @date 2023/4/10
  **/
 public class FastJsonJsonRedisSerializer<T> implements RedisSerializer<T> {
 
     @SuppressWarnings("unused")
     private ObjectMapper objectMapper = new ObjectMapper();
 
     public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
     private Class<T> clazz;
 
     static
     {
         ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
     }
 
     public FastJsonJsonRedisSerializer(Class<T> clazz)
     {
         super();
         this.clazz = clazz;
     }
 
     @Override
     public byte[] serialize(T t) throws SerializationException
     {
         if (t == null)
         {
             return new byte[0];
         }
         return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
     }
 
     @Override
     public T deserialize(byte[] bytes) throws SerializationException
     {
         if (bytes == null || bytes.length <= 0)
         {
             return null;
         }
         String str = new String(bytes, DEFAULT_CHARSET);
 
         return JSON.parseObject(str, clazz);
     }
 
     public void setObjectMapper(ObjectMapper objectMapper)
     {
         Assert.notNull(objectMapper, "'objectMapper' must not be null");
         this.objectMapper = objectMapper;
     }
 
     protected JavaType getJavaType(Class<?> clazz)
     {
         return TypeFactory.defaultInstance().constructType(clazz);
     }
 }

② 添加Redis配置类

 @Configuration
 public class RedisConfig {
 
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
     {
         RedisTemplate<Object, Object> template = new RedisTemplate<>();
 
         //配置连接工厂
         template.setConnectionFactory(connectionFactory);
 
         //使用FastJson2JsonRedisSerializer 来序列化和反序列化redis的value值
         FastJsonJsonRedisSerializer serializer = new FastJsonJsonRedisSerializer(Object.class);
 
         ObjectMapper mapper = new ObjectMapper();
 
         //指定要序列化的域: field,get和set,以及修饰符范围,ANY表示包括private和public
         mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 
         //指定序列化输入的类型,类必须是非final修饰的, final修饰的类会报异常.
         mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
         serializer.setObjectMapper(mapper);
 
         //redis中存储的value值,采用json序列化
         template.setValueSerializer(serializer);
 
         //redis中的key值,使用StringRedisSerializer来序列化和反序列化
         template.setKeySerializer(new StringRedisSerializer());
 
         //初始化RedisTemplate的一些参数设置
         template.afterPropertiesSet();
 
         return template;
     }
 }
(4) 导入工具类
  • Redis工具类
 /**
  * spring redis 工具类
  */
 @SuppressWarnings(value = { "unchecked", "rawtypes" })
 @Component
 public class RedisCache
 {
     @Autowired
     public RedisTemplate redisTemplate;
 
     /**
      * 缓存基本的对象,Integer、String、实体类等
      *
      * @param key 缓存的键值
      * @param value 缓存的值
      */
     public <T> void setCacheObject(final String key, final T value)
     {
         redisTemplate.opsForValue().set(key, value);
     }
 
     /**
      * 缓存基本的对象,Integer、String、实体类等
      *
      * @param key 缓存的键值
      * @param value 缓存的值
      * @param timeout 时间
      * @param timeUnit 时间颗粒度
      */
     public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
     {
         redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
     }
 
     /**
      * 设置有效时间
      *
      * @param key Redis键
      * @param timeout 超时时间
      * @return true=设置成功;false=设置失败
      */
     public boolean expire(final String key, final long timeout)
     {
         return expire(key, timeout, TimeUnit.SECONDS);
     }
 
     /**
      * 设置有效时间
      *
      * @param key Redis键
      * @param timeout 超时时间
      * @param unit 时间单位
      * @return true=设置成功;false=设置失败
      */
     public boolean expire(final String key, final long timeout, final TimeUnit unit)
     {
         return redisTemplate.expire(key, timeout, unit);
     }
 
     /**
      * 获得缓存的基本对象。
      *
      * @param key 缓存键值
      * @return 缓存键值对应的数据
      */
     public <T> T getCacheObject(final String key)
     {
         ValueOperations<String, T> operation = redisTemplate.opsForValue();
         return operation.get(key);
     }
 
     /**
      * 删除单个对象
      *
      * @param key
      */
     public boolean deleteObject(final String key)
     {
         return redisTemplate.delete(key);
     }
 
     /**
      * 删除集合对象
      *
      * @param collection 多个对象
      * @return
      */
     public long deleteObject(final Collection collection)
     {
         return redisTemplate.delete(collection);
     }
 
     /**
      * 缓存List数据
      *
      * @param key 缓存的键值
      * @param dataList 待缓存的List数据
      * @return 缓存的对象
      */
     public <T> long setCacheList(final String key, final List<T> dataList)
     {
         Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
         return count == null ? 0 : count;
     }
 
     /**
      * 获得缓存的list对象
      *
      * @param key 缓存的键值
      * @return 缓存键值对应的数据
      */
     public <T> List<T> getCacheList(final String key)
     {
         return redisTemplate.opsForList().range(key, 0, -1);
     }
 
     /**
      * 缓存Set
      *
      * @param key 缓存键值
      * @param dataSet 缓存的数据
      * @return 缓存数据的对象
      */
     public <T> long setCacheSet(final String key, final Set<T> dataSet)
     {
         Long count = redisTemplate.opsForSet().add(key, dataSet);
         return count == null ? 0 : count;
     }
 
     /**
      * 获得缓存的set
      *
      * @param key
      * @return
      */
     public <T> Set<T> getCacheSet(final String key)
     {
         return redisTemplate.opsForSet().members(key);
     }
 
     /**
      * 缓存Map
      *
      * @param key
      * @param dataMap
      */
     public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
     {
         if (dataMap != null) {
             redisTemplate.opsForHash().putAll(key, dataMap);
         }
     }
 
     /**
      * 获得缓存的Map
      *
      * @param key
      * @return
      */
     public <T> Map<String, T> getCacheMap(final String key)
     {
         return redisTemplate.opsForHash().entries(key);
     }
 
     /**
      * 往Hash中存入数据
      *
      * @param key Redis键
      * @param hKey Hash键
      * @param value 值
      */
     public <T> void setCacheMapValue(final String key, final String hKey, final T value)
     {
         redisTemplate.opsForHash().put(key, hKey, value);
     }
 
     /**
      * 获取Hash中的数据
      *
      * @param key Redis键
      * @param hKey Hash键
      * @return Hash中的对象
      */
     public <T> T getCacheMapValue(final String key, final String hKey)
     {
         HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
         return opsForHash.get(key, hKey);
     }
 
     /**
      * 获取多个Hash中的数据
      *
      * @param key Redis键
      * @param hKeys Hash键集合
      * @return Hash对象集合
      */
     public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
     {
         return redisTemplate.opsForHash().multiGet(key, hKeys);
     }
 
     /**
      * 获得缓存的基本对象列表
      *
      * @param pattern 字符串前缀
      * @return 对象列表
      */
     public Collection<String> keys(final String pattern)
     {
         return redisTemplate.keys(pattern);
     }
 }
 
  • JWT工具类
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.JwtBuilder;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
 
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 import java.util.Base64;
 import java.util.Date;
 import java.util.UUID;
 
 /**
  * JWT工具类
  */
 public class JwtUtil {
 
     //有效期为
     public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000  一个小时
     
     //设置秘钥明文
     public static final String JWT_KEY = "mashibing";
 
     public static String getUUID(){
         String token = UUID.randomUUID().toString().replaceAll("-", "");
         return token;
     }
     
     /**
      * 生成jtw
      * @param subject token中要存放的数据(json格式)
      * @return
      */
     public static String createJWT(String subject) {
         JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
         return builder.compact();
     }
 
     /**
      * 生成jtw
      * @param subject token中要存放的数据(json格式)
      * @param ttlMillis token超时时间
      * @return
      */
     public static String createJWT(String subject, Long ttlMillis) {
         JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
         return builder.compact();
     }
 
     private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
         SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
         SecretKey secretKey = generalKey();
         long nowMillis = System.currentTimeMillis();
         Date now = new Date(nowMillis);
         if(ttlMillis==null){
             ttlMillis=JwtUtil.JWT_TTL;
         }
         long expMillis = nowMillis + ttlMillis;
         Date expDate = new Date(expMillis);
         return Jwts.builder()
                 .setId(uuid)              //唯一的ID
                 .setSubject(subject)   // 主题  可以是JSON数据
                 .setIssuer("sg")     // 签发者
                 .setIssuedAt(now)      // 签发时间
                 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                 .setExpiration(expDate);
     }
 
     /**
      * 创建token
      * @param id
      * @param subject
      * @param ttlMillis
      * @return
      */
     public static String createJWT(String id, String subject, Long ttlMillis) {
         JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
         return builder.compact();
     }
 
     public static void main(String[] args) throws Exception {
         String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg";
         Claims claims = parseJWT(token);
         System.out.println(claims);
     }
 
     /**
      * 生成加密后的秘钥 secretKey
      * @return
      */
     public static SecretKey generalKey() {
         byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
         SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
         return key;
     }
     
     /**
      * 解析
      *
      * @param jwt
      * @return
      * @throws Exception
      */
     public static Claims parseJWT(String jwt) throws Exception {
         SecretKey secretKey = generalKey();
         return Jwts.parser()
                 .setSigningKey(secretKey)
                 .parseClaimsJws(jwt)
                 .getBody();
     }
 
 
 }

JWT工具类使用相关问题

  1. 秘钥长度不合理,将秘钥明文长度设置为 6位.

     异常信息: Exception in thread "main" java.lang.IllegalArgumentException: Last unit does not have enough valid bits
    
     //设置秘钥明文(长度为6位)
     public static final String JWT_KEY = "msbhjy"; 
    
  2. 1.8 以上版本,需要引入 JAXB API 相关依赖

     异常信息:  java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
    
     <dependency>
         <groupId>javax.xml.bind</groupId>
         <artifactId>jaxb-api</artifactId>
         <version>2.3.0</version>
     </dependency>
     <dependency>
         <groupId>com.sun.xml.bind</groupId>
         <artifactId>jaxb-impl</artifactId>
         <version>2.3.0</version>
     </dependency>
     <dependency>
         <groupId>com.sun.xml.bind</groupId>
         <artifactId>jaxb-core</artifactId>
         <version>2.3.0</version>
     </dependency>
     <dependency>
         <groupId>javax.activation</groupId>
         <artifactId>activation</artifactId>
         <version>1.1.1</version>
     </dependency>
    
  • 字符串渲染工具类
 public class WebUtils{
     /**
      * 将字符串渲染到客户端
      * 
      * @param response 渲染对象
      * @param string 待渲染的字符串
      * @return null
      */
     public static String renderString(HttpServletResponse response, String string) {
         try
         {
             response.setStatus(200);
             response.setContentType("application/json");
             response.setCharacterEncoding("utf-8");
             response.getWriter().print(string);
         }
         catch (IOException e)
         {
             e.printStackTrace();
         }
         return null;
     }
 }
4.2.3.7 重构入门案例-具体实现
(1) 通过数据库校验用户

通过前面的分析,我们得出结论:可以自定义一个UserDetailsService,并让Spring Security使用它。我们的UserDetailsService可以从数据库中获取用户名和密码。

  • 创建数据库及用户表
 CREATE TABLE `sys_user` (
   `user_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
   `user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
   `nick_name` VARCHAR(30) NOT NULL COMMENT '用户昵称',
   `password` VARCHAR(100) DEFAULT '' COMMENT '密码',
   `phonenumber` VARCHAR(11) DEFAULT '' COMMENT '手机号码',
   `sex` CHAR(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
   `status` CHAR(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
   PRIMARY KEY (`user_id`) USING BTREE
 ) ENGINE=INNODB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户信息表'
  • 引入MybatisPuls和mysql驱动的依赖
 <dependency>
     <groupId>com.baomidou</groupId>
     <artifactId>mybatis-plus-boot-starter</artifactId>
     <version>3.4.1</version>
 </dependency>
 <!-- Mysql驱动包 -->
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>8.0.32</version>
 </dependency>
  • 配置数据库信息
     spring:
       datasource:
         url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8&serverTimezone=UTC
         username: root
         password: root
         driver-class-name: com.mysql.cj.jdbc.Driver
  • 创建实体类
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 @TableName("sys_user")
 public class SysUser implements Serializable {
 
     /**
      * 主键
      */
     @TableId
     private Long userId;
 
     /**
      * 用户名
      */
     private String userName;
 
     /**
      * 昵称
      */
     private String nickName;
 
     /**
      * 密码
      */
     private String password;
 
     /**
      * 手机号
      */
     private String phonenumber;
 
     /**
      * 用户性别(0男,1女,2未知)
      */
     private String sex;
 
     /**
      * 账号状态(0正常 1停用)
      */
     private String status;
 }
 
  • 定义Mapper接口
 public interface UserMapper extends BaseMapper<User> {
     
 }
  • 配置Mapper扫描
 @SpringBootApplication
 @MapperScan("com.mashibing.springsecurity_example.mapper")
 public class SpringsecurityExampleApplication {
 
     public static void main(String[] args) {
         ConfigurableApplicationContext run = SpringApplication.run(SpringsecurityExampleApplication.class, args);
         System.out.println("123456");
     }
 }
  • 测试
 @SpringBootTest
 public class MapperTest {
 
     @Autowired
     private UserMapper userMapper;
 
     @Test
     public void testUserMapper(){
         List<User> users = userMapper.selectList(null);
         System.out.println(users);
     }
 }
(2) 引入SpringSecurity

第一步: 编写一个类,实现UserDetailsService接口,并重写其中的loadUserByUsername方法。在该方法中,使用用户名从数据库中检索用户信息。

 /**
  * 根据用户名检索用户信息
  * @date 2023/4/14
  **/
 @Service
 public class UserDetailsServiceImpl implements UserDetailsService {
 
     @Autowired
     private UserMapper userMapper;
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
         //根据用户名查询用户信息
         LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(SysUser::getUserName,username);
         SysUser user = userMapper.selectOne(wrapper);
 
         //如果查询不到数据,抛出异常 给出提示
         if(Objects.isNull(user)){
             throw new RuntimeException("用户名或密码错误");
         }
 
         //方法的返回值是 UserDetails接口类型,需要返回自定义的实现类
         return new LoginUser(user);
     }
 }

第二步 为了将用户信息转换为UserDetails类型的对象,需要创建一个类来实现UserDetails接口,并将用户信息封装在其中。

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class LoginUser implements UserDetails {
 
     private SysUser sysUser;
 
 
     /**
      *  用于获取用户被授予的权限,可以用于实现访问控制。
      */
     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
         return null;
     }
 
     /**
      * 用于获取用户的密码,一般用于进行密码验证。
      */
     @Override
     public String getPassword() {
         return sysUser.getPassword();
     }
 
     /**
      * 用于获取用户的用户名,一般用于进行身份验证。
      */
     @Override
     public String getUsername() {
         return sysUser.getUserName();
     }
 
     /**
      * 用于判断用户的账户是否未过期,可以用于实现账户有效期控制。
      */
     @Override
     public boolean isAccountNonExpired() {
         return true;
     }
 
     /**
      * 用于判断用户的账户是否未锁定,可以用于实现账户锁定功能。
      */
     @Override
     public boolean isAccountNonLocked() {
         return true;
     }
 
     /**
      * 用于判断用户的凭证(如密码)是否未过期,可以用于实现密码有效期控制。
      */
     @Override
     public boolean isCredentialsNonExpired() {
         return true;
     }
 
     /**
      * 用于判断用户是否已激活,可以用于实现账户激活功能。
      */
     @Override
     public boolean isEnabled() {
         return true;
     }
 }
Spring Security Authorization 官方文档分析 阅读详情

相关推荐

Spring Boot 集成权限管理Spring Security框架

Spring Security是Spring项目的一部分,专门为Java应用程序提供全面的安全服务。它可以帮助开发者实现各种安全需求,如身份验证、角色和权限管理、会话管理等。Spring Security提供了一种强大的和可高度自定义的权限管理方式,开发者可以根据业务需求定制权限规则,实现精细化的权限控制。要自定义Spring Security配置,需要创建一个配置类,该类需要继承WebSecurityConfigurerAdapter类,并覆盖其中的方法。

m0_71777195的博客 1112

【业务功能篇57】Springboot + Spring Security 权限管理 【上篇】

1) Spring Security 是 Spring 家族中的一个安全管理框架,可以轻松地与 Spring 应用程序集成, 它提供了一系列的安全服务和工具,用于保护企业应用程序的安全性。Spring Security 提供了许多功能,包括身份验证(Authentication)、授权(Authorization)、访问控制、密码管理、单点登录(Single Sign-On)、攻击防范(如跨站点脚本攻击、SQL注入攻击)等。2) 认证和授权也是SpringSecurity作为安全框架的核心功能。

studyday1的博客 6138

spring boot整合spring security实现权限校验和控制(一)--初步入门

引言 ##权限校验框架分类 目前常用的权限校验框架主要有appache的shiro框架和spring的security框架, 这两个各有优缺点, shiro: Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理(: spring boot集成shiro链接.) security: spring security属于spring的几个顶级项目之一, 和他并列的有spring cloud等, 是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解

意田天的博客 1万+

spring boot 权限管理的几种方式

spring boot 权限

orton777的博客 6797

第四次工业革命如何重构产业逻辑:写给技术人的趋势推演

第四次工业革命正在以人工智能、大数据和新能源为核心重构全球产业逻辑。本文从历史规律切入,指出技术革命本质是生产力跃迁和产业链话语权的转移。当前AI基础设施竞赛已形成算力、数据、算法三大支柱,其中算力投资首超石油领域,绿色算力与新型架构成为关键。数据治理带来隐私计算等新技术约束,数据中心正向液冷技术和"东数西算"模式演进。技术人需构建三大竞争力:夯实分布式系统等底层能力、培养跨领域知识融合能力、善用约束条件驱动创新。这场变革将奖励那些深度理解技术本质并能快速适应的实践者。

几十年资深高级架构师,技术专家的技术笔记。专注C、Java、Python、微服务、云原生、AI工程化及高并发系统分布式架构实战,分享源码剖析、性能调优与架构治理经验。 289

保姆级教程:在Davinci Configurator里配置BswM的ECU状态机(附状态切换流程图)

本文提供了一份详细的AUTOSAR BswM模块配置指南,通过Davinci Configurator工具实现ECU状态机的构建与管理。内容涵盖从模式请求端口创建到状态切换规则定义的全流程,特别针对Ecu State Handling进行了深入解析,并附有状态切换流程图,帮助汽车电子工程师高效完成ECU状态管理配置。

weixin_28366475的博客 410

JAVA学习篇——Spring Boot Security

1. 关于Spring Boot Security Spring Boot Security是在Spring Boot中使用的,基于Spring Security的依赖项,其本质就是Spring Security框架加上了应用于Spring Boot工程的自动配置。 Spring Security是一款主要解决了认证和授权相关处理的安全框架。 认证:验证用户的身份,例如在登录过程中验证用户名与密码是否匹配。 授权:使得用户允许访问服务器端的某些资源,或禁止访问某些资源,例如管理员可以执行一些数据删除

zhang19971014的博客 8671

EPS+ArcGIS+CASS三剑客:等高线裁剪的完整工作流与避坑指南

本文详细介绍了EPS、ArcGIS和CASS三款软件在等高线裁剪中的协同工作流程与避坑指南。通过发挥各软件优势,解决数据完整性、拓扑准确性和流程可靠性等核心挑战,提升40%以上的工作效率。特别适合大型测绘项目中复杂地形的处理,涵盖数据准备、核心处理到后期优化的全流程。

weixin_30352645的博客 618

Springboot+Spring-Security+JWT实现restful Api的权限管理以及token管理

前言 其实挺早就想写一篇关于jwt的博文去好好总结一下之前踩过的坑了,但是事情有点太多了,一直没抽出时间来写,刚好现在有点时间可以好好静下来写一遍(可能)有点质量的博文吧,毕竟一直都是看别人的博文去学习,我也好好写一遍吧哈哈。 看完这篇文章之后你可以知道 如何使用springboot,springSecurity,jwt实现基于token的权限管理 统一处理无权限请求的结果 整理一下思路 创建一个新工程时,我们需要思考一下我们接下来需要的一些步骤,需要做什么,怎么做。 搭建springboot工程 导入sp

weixin_42654295的博客 1142

Python实战:用BM3D算法给老照片去噪(附完整代码+数据集)

本文详细介绍了如何使用Python和BM3D算法为老照片进行高效去噪。通过完整的代码实现和数据集,读者可以快速掌握图像去噪技术,包括环境配置、算法原理、参数调优及效果评估,帮助恢复珍贵老照片的清晰度。

weixin_29057237的博客 239

TongWeb微服务部署实战:Nacos注册与网关路由的疑难解析

本文分享了将Spring Cloud微服务迁移至TongWeb应用服务器时,解决Nacos服务注册失败与Spring Cloud Gateway路由失效的实战经验。针对Nacos注册问题,通过实现ApplicationRunner接口并利用JMX获取容器端口,实现了手动注册;针对网关路由问题,建议将基于WebFlux的Gateway模块改为JAR包独立部署,以保障其原生运行环境。文章为在TongWeb上进行微服务部署提供了关键的问题排查思路与混合架构部署的最佳实践。

weixin_29209801的博客 240

spring-boot-security

安全框架 在java开发领域,常见的安全管理框架有apache shiro和spring security。shiro是相对spring security是轻量级的,提供了认证、授权,会话管理,密码管理、缓存管理等功能,spring security相对复杂点,功能比shiro更加强大,权限控制可以更精细,对OAuth2的支持也更友好,与spring框架无缝整合 sp...

weixin_40317062的博客 209

滚动时域优化方法

滚动时域优化(Receding Horizon Optimization, RHO),也被广泛称为模型预测控制(Model Predictive Control, MPC),是一种用于动态系统优化和控制的。其核心思想是“在每个当前时刻k,控制器只考虑未来一个(称为“预测时域”或“优化时域”,长度N)内的系统行为。(通过测量或估计得到)。(描述状态如何随输入和干扰变化)。(如果有,可能基于历史数据或假设)。在这个有限的未来时域[k, k+N-1]上,求解一个。这个优化问题的目标是找到一个。

m0_69522810的博客 2293

Apifox调用Security权限接口

因为项目升级到SpringBoot3.0了,使用Shiro有很多问题目前好像不支持SpringBoot3.0,大部分问题都出在javax这个包改成了jakarta 这个包下面了,所以好多组件我们用不了,需要各大厂家去适配SpringBoot3.0。

Jack魏 1904

Ardupilot EKF3核心算法《开篇》

Ardupilot EKF3核心算法本专栏重点,透彻,详细的分析了ardupilot的EKF3,保证是你看到的最完美的EKF3资料,相信你的选择不会错误。网上EKF资料真是太多,说真的没有一篇讲解好的。!!

陌城烟雨 1413

SpringAI系列 - MCP篇(二) - MCP Server Boot Starter

此处需注意之前的。

罗小爬的技术宝书 1319

Spring boot security权限管理集成cas单点登录

挣扎了两周,Spring security的cas终于搞出来了,废话不多说,开篇! Spring boot集成Spring security本篇是使用spring security集成cas,因此,先得集成spring security新建一个Spring boot项目,加入maven依赖,我这里是用的架构是Spring boot2.0.4+Spring mvc+Spring data jp...

weixin_33781606的博客 1132

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

Spring Security 定制UserDetailsService,动态uri权限,Thymeleaf,限制密码强度、过期、错误密码锁定超时自动解锁、禁用历史密码、新密码和现密码差异要求编辑距离

在本教程中,我将指导您如何编写代码,以使用具有基于表单的身份验证的Spring安全API来保护Spring Boot应用程序中的网页。用户详细信息存储在MySQL数据库中,并使用春季JDBC连接到数据库。我们将从中的 ProductManager 项目开始,向现有的弹簧启动项目添加登录和注销功能。凭据应存储在数据库中,因此让我们创建新表,表间关系ER图如下:接下来,在应用程序属性文件中指定数据库连接信息,如下所示:根据您的MySQL数据库更新URL,用户名和密码。

allway2的博客 1470

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

Spring Boot整合Spring Security--学习笔记

Spring Boot:整合Spring Security(待续中....)

tigerhhzz的博客 695

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

深入Spring Security魔幻山谷-获取认证机制核心原理讲解

文/朱季谦 本文基于Springboot+Vue+Spring Security框架而写的原创学习笔记,demo代码参考《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》一书。 这是一个古老的传说。 在神秘的Web系统世界里,有一座名为Spring Security的山谷,它高耸入云,蔓延千里,鸟飞不过,兽攀不了。这座山谷只有一条逼仄的道路可通。然而,若要通过这条道路前往另一头的世界,就必须先拿到一块名为token的令牌,只有这..

朱季谦 519

UJCMS企业级Java开源CMS完整教程:从零搭建到高效部署

UJCMS是一款基于Java开发的企业级开源内容管理系统,采用SpringBoot、MyBatis、Spring Security、Vue3等现代技术栈构建,支持传统模板渲染与前后端分离双模式开发。本教程将带你从零开始,全面掌握UJCMS的安装部署、功能配置和实战应用技巧,助你快速搭建专业级内容管理平台。 ## 🎯 为什么选择UJCMS构建企业级网站? 在众多CMS系统中,UJCMS凭借其独

gitblog_00698的博客 1175

springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式) 项目security_simple(认证授权项目) 1.新建springboot项目     这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  下面是我引入的依赖...

taihexuelang的专栏 7561

1、如何在SpringBoot 中使用SpringSecurity?

spring boot 引入 spring security

ihaveFilter的专栏 225

Spring Security登录验证,验证码,动态管理uri访问权限,Thymeleaf,限制密码强度、过期、错误密码锁定超时自动解锁、禁用历史密码、新密码和现密码差异要求编辑距离

在本教程中,我将指导您如何编写代码,以使用具有基于表单的身份验证的Spring安全API来保护Spring Boot应用程序中的网页。用户详细信息存储在MySQL数据库中,并使用春季JDBC连接到数据库。我们将从中的 ProductManager 项目开始,向现有的弹簧启动项目添加登录和注销功能。接下来,在应用程序属性文件中指定数据库连接信息,如下所示:根据您的MySQL数据库更新URL,用户名和密码。

allway2的博客 1201

【课程设计】基于springboot的结伴旅游管理系统设计与实现 计算机毕业设计源码20323

系统主要包括用户注册与登录、旅游信息查询、自由组团、旅行团管理、门票购买、线路预定等功能,并针对不同用户角色提供个性化权限管理。采用Spring Security保障用户数据安全,结合Redis缓存优化系统性能。实验结果表明,该系统功能完善、操作便捷,能够有效提升用户的结伴旅游体验,同时提高旅游资源的利用效率,为结伴旅游提供了一种新型的数字化解决方案....

Biye_Design的博客 579

SpingBoot集成OAuth实现认证鉴权

1、Oauth2简介 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。 2、Oauth2服务器 授权服务器 Authorization Service. 资源服务器 Res...

u013916394的博客 1047
上一篇: 【业务功能篇57】Springboot + Spring Security 权限管理 【上篇】
下一篇: 【业务功能篇59】Springboot + Spring Security 权限管理 【下篇】
studyday1
博客等级 码龄9年 6454粉丝 255原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值