java springboot3.x jwt+spring security6.x实现用户登录认证
springboot3.x jwt+spring security6.x实现用户登录认证
什么是JWT
JWT(JSON Web Token)是一种开放标准(RFC 7519),它用于在网络应用环境中传递声明。通常,JWT用于身份验证和信息交换。JWT的一个典型用法是在Web应用中实现基于Token的身份验证。
JWT 的特点
自包含(Self-contained):JWT 包含了用户的认证信息和其他相关的数据,因此无需在服务器端存储会话信息。它是通过加密或签名的方式来保证数据的完整性和防止篡改。
JWT的结构
JWT由三部分组成:
Header(头部):包含令牌的类型(通常是JWT)和加密算法(如HMAC SHA256或RSA)。
Payload(负载):包含声明(Claims)。这些声明可以是关于实体(通常是用户)和附加数据的断言。例如,可以包含用户的ID、角色、权限等。
Signature(签名):用来验证消息的完整性以及发送者的身份。通过Header中的算法和密钥生成签名。
什么是spring Security
Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架,通常用于保护 Spring 应用程序。它提供了各种安全功能,如:
认证(Authentication):用于确认用户的身份。例如,检查用户名和密码。
授权(Authorization):用于控制用户是否有权限访问某些资源。通常通过角色或权限进行授权。
防护功能:提供 CSRF 防护、会话管理、跨站点请求伪造防护等安全功能。
Spring Security 是一个全面的安全框架,可以与多种身份验证机制集成,如基于表单的登录、OAuth2、LDAP、JWT 等。
有了spring Security为什么还需要JWT
Spring Security 本身并不负责管理用户登录后如何保持会话的状态,这部分通常是通过 会话Session(以前的方式) 或 JWT 来完成的。
JWT 和 spring Security关系
Spring Security 和 JWT 之间并没有直接的关系,但是它们可以很好地结合使用。在一个典型的现代 Web 应用中,Spring Security 负责保护应用的安全性,而 JWT 通常用于实现基于 token 的无状态认证。
Spring Boot 中的安全验证和存储用户信息
在Spring Boot应用中,常用的安全验证方式是结合 Spring Security 和 JWT。Spring Security提供了多种身份验证机制,而JWT则用于实现无状态的认证方案。JWT通常在用户登录成功后生成,并作为后续请求的认证凭证。
一般流程
- 用户登录:用户通过用户名和密码发送请求给后端,后端验证用户身份。
- 生成JWT:身份验证通过后,后端生成JWT,并将其发送给前端。JWT通常在HTTP响应的Authorization头中返回。
- 存储JWT:前端将JWT存储在本地(例如localStorage或sessionStorage)或Cookie中。
- 后续请求:每次前端向后端发起请求时,JWT都会包含在请求头的Authorization字段中,后端通过JWT验证用户身份。
- 验证JWT:后端通过Spring Security中的过滤器解码JWT,并验证签名。如果JWT有效,用户身份就会被确认。
为什么说JWT是无状态登录
是因为它的使用方式不需要在服务器端存储会话信息,也就是说,服务器不需要保存任何关于客户端的状态信息
有状态和无状态
有状态认证(Stateful Authentication)
在传统的认证机制中,服务器会维护一个会话(session)来记录客户端的身份信息。例如,在基于会话的认证中,当用户成功登录时,服务器会创建一个会话并生成一个唯一的会话 ID。这个会话 ID 会存储在服务器端的内存或数据库中,且会在客户端的浏览器中以 Cookie 或其他方式保存(例如,JSESSIONID)。每次客户端发起请求时,都会携带会话 ID,服务器根据会话 ID 查找会话信息来确认用户身份。
问题:服务器需要维护会话状态。每当用户发送请求时,服务器需要查询存储的会话数据。这会增加服务器的负担,尤其是在分布式系统中,需要将会话数据共享或同步到不同的服务器节点。
无状态认证(Stateless Authentication)
与会话认证不同,无状态认证不需要在服务器上存储任何客户端的会话信息。服务器验证身份时,通过客户端携带的信息(如 JWT)来验证请求,而不依赖于服务器端存储的会话状态。JWT 本身包含了足够的信息来完成身份验证和授权,服务器只需解析和验证这个 token,而无需查询任何存储的信息。
为什么不手动封装json存储
数据完整性与安全性
JWT: JWT 的一个核心特性是 签名,它确保了数据的 完整性和安全性。JWT 中的签名是由服务器的私钥生成的,任何人如果修改了 JWT 的内容,签名就会变得无效,服务器就能发现数据被篡改。
例如,如果你将用户信息存储为 JSON,前端将其发送给服务器,那么攻击者可能会篡改这个 JSON 数据(如修改用户角色、权限等),而服务器无法判断数据是否被篡改,除非你做额外的安全处理(比如加密)。
自定义 JSON: 如果你让前端封装 JSON 并发送到服务器,服务器将无法知道数据是否被篡改。没有类似 JWT 签名的机制,前端发送的 JSON 数据就很容易被篡改。为了解决这个问题,你需要自己实现一些安全机制(如加密或自定义签名),但这会增加实现的复杂度。
无状态性与有效期管理
JWT:JWT 是自包含的,它不仅仅包含用户信息,还可以包含 过期时间(exp)等信息。这样,服务器可以非常容易地判断一个 JWT 是否过期,无需查询数据库或任何状态。
自定义 JSON:如果前端仅仅发送一个 JSON 数据包,服务器就无法知道该数据是否已经过期。为了实现这一点,你需要自己维护一个机制,比如在 JSON 中加入时间戳,或者在服务器端保存用户的会话过期时间。这会导致服务器变得有状态,因为服务器需要管理每个用户的会话生命周期。
服务器性能与分布式架构支持
JWT:由于 JWT 是自包含的,所有的信息都在 token 中,服务器不需要存储任何会话信息,这意味着它是 无状态的。无状态性在 分布式架构 中特别有优势,因为多个微服务可以独立地验证 JWT 而无需共享会话数据。
自定义 JSON:如果你选择自己封装 JSON,并由前端存储和发送,每次请求时,服务器就需要检查和验证这个 JSON 信息。尤其是在分布式环境中,你可能需要通过共享会话存储(如 Redis)来确保各个服务都能正确验证用户身份,增加了管理的复杂性。
可扩展性
JWT:由于 JWT 是标准化的,支持的库和工具非常多,你可以很容易地实现不同平台(如前端、后端、移动端等)之间的认证,而且不需要太多的额外工作。JWT 自带的灵活性和规范使得它可以适应多种需求。
自定义 JSON:如果你手动封装 JSON 并且实现自己的认证机制,虽然在小范围内可以工作,但这种方法缺乏标准化和统一性,扩展性差。在系统规模扩大或需求变化时,可能会遇到很多维护和兼容性问题。
实现
1.引入依赖
引入JWT依赖
<!-- JWT library --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.11.2</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope></dependency>
引入spring Security依赖
<!-- Spring Security for Authentication --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
2.实现 UserDetailsService接口
UserDetailsService 是 Spring Security 中一个非常重要的接口,负责从数据库或其他存储中加载用户的详细信息,通常用于身份验证和授权过程。
在 Spring Security 中,用户信息通常存储在数据库或其他外部系统中。为了进行认证和授权,Spring Security 需要从这些存储中获取用户的详细信息,如用户名、密码、角色、权限等。为了实现这一点,Spring Security 提供了 UserDetailsService 接口,它定义了如何加载这些用户信息。
package com.example.demonew.service.loginService;import com.example.demonew.entity.UserInfo;
import com.example.demonew.mapper.loginMapper.LoginMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;import java.util.Map;@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate LoginMapper loginMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {UserInfo user = loginMapper.getUserInfo(username);if (user == null) {throw new UsernameNotFoundException("User not found");}//将信息添加到User也可以说是UserDetails 对象中return User.withUsername(user.getUsername()).password(user.getPassword()).roles(user.getRole()).build();}}
总结
实现 UserDetailsService 接口的主要目的是将用户的认证信息(包括用户名、密码、角色等)从数据库等存储中提取出来,并转化为 Spring Security 能够理解并使用的 UserDetails 对象。在 Spring Security 的认证和授权流程中,UserDetailsService 是用来提供用户信息的核心组件。
当用户尝试登录时,Spring Security 会调用 loadUserByUsername 方法来查找用户,并使用返回的 UserDetails 来进行身份验证和授权。因此,实现 UserDetailsService 是将自定义的用户数据源(如数据库)与 Spring Security 进行集成的关键步骤。
3.实现JWT工具类
这里工具类实现了,生成jwt token,解密jwt, 以及通过jwt获取用户名,验证令牌等功能,方便后续调用
package com.example.demonew.util;import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;@Component
@Slf4j
public class JwtTokenProviderUtil {@Value("${app.jwt-secret}")private String jwtSecret;@Value("${app.jwt-expiration-milliseconds}")private long jwtExpirationDate;private Key key;// 使用懒加载方式获取密钥private Key getKey() {if (key == null) {synchronized (this) {if (key == null) {key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));}}}return key;}// 生成 JWT tokenpublic String generateToken(Authentication authentication){String username = authentication.getName();Date currentDate = new Date();Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);String token = Jwts.builder().setSubject(username).setIssuedAt(new Date()).setExpiration(expireDate).signWith(getKey(),SignatureAlgorithm.HS256).compact();return token;}private Key key(){return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));}//解密tokenpublic String resolveToken(HttpServletRequest request) {String bearerToken = request.getHeader("Authorization");if (bearerToken != null && bearerToken.startsWith("Bearer ")) {return bearerToken.substring(7);}return null;}// 解析 JWT 令牌并返回其主体private Claims getClaimsFromToken(String token) {try {return Jwts.parserBuilder().setSigningKey(getKey()).build().parseClaimsJws(token).getBody();} catch (JwtException e) {log.error("Error parsing JWT token: {}", e.getMessage());throw new RuntimeException("JWT token parsing failed", e);}}// 验证 JWT 令牌public boolean validateToken(String token) {try{Jwts.parserBuilder().setSigningKey(getKey()).build().parseClaimsJws(token);return true;} catch (MalformedJwtException e) {log.error("Invalid JWT token: {}", e.getMessage());} catch (ExpiredJwtException e) {log.error("JWT token is expired: {}", e.getMessage());} catch (UnsupportedJwtException e) {log.error("JWT token is unsupported: {}", e.getMessage());} catch (IllegalArgumentException e) {log.error("JWT claims string is empty: {}", e.getMessage());}return false;}// 获取用户认证信息public Authentication getAuthentication(String token) {String username = getUsername(token);List<GrantedAuthority> authorities = getAuthorities(token);return new UsernamePasswordAuthenticationToken(username, "", authorities);}//通过jwt信息获取用户名public String getUsername(String token) {Claims claims = getClaimsFromToken(token);String username = claims.getSubject();return username;}public List<GrantedAuthority> getAuthorities(String token) {Claims claims = getClaimsFromToken(token);List<String> roles = (List<String>)claims.get("roles");return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());}}
设置密钥和过期时间
app:jwt-secret: 60fosjWhlsy6bxLjSv5P/8fvmvanEEAjUQm3KLkSuMc= # 密钥 不唯一自己设置jwt-expiration-milliseconds: 604800000
随机生成密钥
@Testvoid jwtSecretGenerator (){SecureRandom random = new SecureRandom();byte[] secret = new byte[32]; // 256 位random.nextBytes(secret);String jwtSecret = Base64.getEncoder().encodeToString(secret); // 将字节数组转换为 Base64 编码字符串System.out.println(jwtSecret);}
4.spring Security配置类
Spring Security 配置类主要用于设置安全策略,过滤器链以及禁用默认的 CSRF 等。
spring boot3.0废弃了extends WebSecurityConfigurerAdapter的方式,所以这里采用添加@Bean新方式
SecurityConfig 是 Spring Security 的配置类,作用是配置 Spring Security 的安全策略,进行身份认证和授权管理
可以看出spring Security就是通过http来做跨域,权限等控制
package com.example.demonew.config;import com.example.demonew.service.loginService.CustomUserDetailsService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity // 确保添加这个注解来启用 Spring Security 配置
public class SecurityConfig {@Resourceprivate CustomUserDetailsService userDetailsService;@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {// 配置 HTTP 安全策略http.csrf(csrf -> csrf.disable()) // 由于你使用的是前后端分离的架构,CSRF 防护通常是不需要的。禁用 CSRF 防护可以避免 Spring Security 对跨站请求伪造攻击的保护,从而简化 API 调用。//授权配置.authorizeHttpRequests(authz -> authz.requestMatchers("/login").permitAll() // 允许所有用户访问 /user/login 路径下的资源(通常是登录接口)。其他接口需要认证后访问。.requestMatchers("/test/**").hasAuthority("ROLE_ADMIN") // 仅允许具有 ADMIN 角色的用户访问 /test/** 路径下的资源。.anyRequest().authenticated() //所有其他请求都需要进行身份认证才能访问。)// jwtAuthenticationFilter是一个自定义的过滤器,它负责处理请求中的 JWT 认证。此过滤器被配置为在 Spring Security 自带的 UsernamePasswordAuthenticationFilter 之前执行,这意味着在 Spring Security 处理用户名和密码认证之前,会先进行 JWT 校验。.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).userDetailsService(userDetailsService) //配置自定义的 UserDetailsService 用于加载用户信息.formLogin(form -> form.disable()) // Spring Security 默认启用基于表单的登录认证。如果你使用的是前后端分离的方式,通常不会使用传统的表单登录,因此禁用它。.logout(logout -> logout.permitAll()); // 配置登出功能,允许所有用户进行登出。return http.build(); // 返回配置好的 SecurityFilterChain}/*这个方法配置了 Spring Security 的 AuthenticationManager,它是进行用户认证的核心组件。配置了一个 DaoAuthenticationProvider,它使用 CustomUserDetailsService 来加载用户信息,使用 BCryptPasswordEncoder 进行密码验证。DaoAuthenticationProvider:是 Spring Security 用于基于数据库的用户认证提供程序,它会从 UserDetailsService 中加载用户信息,并使用密码编码器对用户密码进行校验。UserDetailsService:CustomUserDetailsService 实现了 UserDetailsService 接口,用于加载用户信息(例如,用户名、密码、角色等)。它通常会从数据库中查询用户信息。PasswordEncoder:BCryptPasswordEncoder 用于对用户输入的密码进行加密,然后与存储在数据库中的加密密码进行对比。BCrypt 是一种常见的密码加密算法。返回 AuthenticationManager:该管理器负责执行认证流程,返回经过认证的 Authentication 对象。*/@Beanpublic AuthenticationManager authenticationManager(UserDetailsService userDetailsService,PasswordEncoder passwordEncoder) {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();authenticationProvider.setUserDetailsService(userDetailsService); // 配置 UserDetailsService,用于加载用户信息authenticationProvider.setPasswordEncoder(passwordEncoder); // 配置密码编码器,用于密码校验return new ProviderManager(authenticationProvider); // 返回一个 ProviderManager,管理多个认证提供者}/*该方法返回一个 BCryptPasswordEncoder 实例,用于对用户的密码进行加密和验证。BCryptPasswordEncoder 是一种安全的密码加密算法,Spring Security 默认推荐使用这种加密方式。*/@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// @Bean
// public SecurityContextRepository securityContextRepository() {
// return new HttpSessionSecurityContextRepository();
// }}
5.创建JWT 过滤器
创建一个过滤器来拦截每个请求,提取 JWT,并验证它。过滤器会检查请求头是否带有 JWT,如果有,它会验证 JWT 并通过 Spring Security 认证上下文设置用户身份。
JwtAuthenticationFilter 是一个用于处理 JWT(JSON Web Token)身份验证的过滤器类,继承自 Spring Security 的 OncePerRequestFilter。它的作用是在每次请求时,检查请求头中的 JWT token,并进行校验,如果 token 验证通过,则根据 token 中的信息设置 Spring Security 的 Authentication,确保用户能够通过 JWT 认证来访问受保护的资源。
package com.example.demonew.config;import com.example.demonew.service.loginService.LoginService;
import com.example.demonew.util.JwtTokenProviderUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate JwtTokenProviderUtil jwtTokenProvider;@Autowiredprivate UserDetailsService userDetailsService;/*这是过滤器的核心方法,它会在每次 HTTP 请求时被调用。方法中的处理流程如下:*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 通过 getTokenFromRequest(request) 方法,尝试从 HTTP 请求的 Authorization 头中提取 JWT。如果 token 存在且以 Bearer 开头,则提取出 token 部分。String token = getTokenFromRequest(request);/* 校验 token使用 jwtTokenProvider.validateToken(token) 校验 JWT 的合法性。例如,检查 token 是否过期、是否篡改等。如果 token 无效或过期,认证过程会被跳过,后续请求会被拒绝。*/if(StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)){try {// 从 token 获取 usernameString username = jwtTokenProvider.getUsername(token);// 加载与 token 关联的用户UserDetails userDetails = userDetailsService.loadUserByUsername(username);/** 创建一个 UsernamePasswordAuthenticationToken 实例,传入 userDetails 和该用户的权限(userDetails.getAuthorities())。* UsernamePasswordAuthenticationToken 是 Spring Security 用来封装用户身份信息的一个对象,它包含了用户的身份信息和权限。* */UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));/** 使用 SecurityContextHolder.getContext().setAuthentication(authenticationToken) 将身份认证信息(即 authenticationToken)存储在 Spring Security 的上下文中。* 这样,Spring Security 在处理后续请求时,就能基于这个认证信息对请求进行授权控制。* */SecurityContextHolder.getContext().setAuthentication(authenticationToken);}catch (Exception e){// 记录日志并清除认证上下文log.warn("JWT Token validation failed: " + e.getMessage());SecurityContextHolder.clearContext();}}else {// 无 token 或 token 无效,清除认证上下文logger.warn("JWT Token is missing or invalid");SecurityContextHolder.clearContext();}/*最后,调用 filterChain.doFilter(request, response) 让请求继续传递给下一个过滤器或最终的目标(如 Controller)。这一步是确保请求能够正常进入应用的下一阶段*/filterChain.doFilter(request, response);}// 从请求头获取 JWT 格式为:Authorization: Bearer <token>private String getTokenFromRequest(HttpServletRequest request) {String bearerToken = request.getHeader("Authorization");if (bearerToken != null && bearerToken.startsWith("Bearer ")) {return bearerToken.substring(7); // 去掉 "Bearer " 前缀}return null;}
}
为什么使用 OncePerRequestFilter
OncePerRequestFilter 是 Spring 提供的一个过滤器基类,确保每次请求只会调用一次过滤器的 doFilterInternal 方法,而不是每个过滤器链都执行一次。这可以有效避免重复执行相同的逻辑,保证 JWT 校验只执行一次。
创建登录接口
这样用户先通过用户名,密码进行登录,然后校验信息,返回userDetail对象
package com.example.demonew.controller.login;import com.example.demonew.common.Result;
import com.example.demonew.config.SecurityConfig;
import com.example.demonew.service.loginService.LoginService;
import com.example.demonew.util.JwtTokenProviderUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
@RequestMapping("/user")
public class LoginController {@Autowiredprivate LoginService loginService;@Autowiredprivate JwtTokenProviderUtil jwtTokenProviderUtil;@Autowiredprivate AuthenticationManager authenticationManager;@PostMapping("/login")public Result Login(@RequestBody LoginRequest loginRequest){loginService.login(loginRequest.getUsername(),loginRequest.getPassword());// 登录授权Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword()));// 将登录用户信息交给SpringSecurity管理SecurityContextHolder.getContext().setAuthentication(authentication);// 利用用户授权信息生成JWT令牌String token = jwtTokenProviderUtil.generateToken(authentication);return Result.success(token);};@PostMapping("/register")public Result registerUser(@RequestBody LoginRequest loginRequest) {BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder();String encodedPassword = bCryptPasswordEncoder.encode(loginRequest.password);String password=encodedPassword;loginService.register(loginRequest.getUsername(),password);return new Result();}// 登录请求体的封装类public static class LoginRequest {private String username;private String password;// getters and setterspublic String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}}
准备数据

密码这里先手动生成一个
@Testvoid testBCryptPasswordEncoder(){BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String encode = encoder.encode("123456");System.out.println(encode);}
postman测试
登录
登录不需要带Authorization信息,登录后jwt会生成token返回,后续请求需要带上

其他接口
没权限 未带认证

带上认证成功访问

错误总结
执行filterChain.doFilter(request, response);后无法进入controller
解决
检查securityFilterChain方法中的requestMatchers方法,url是否正确
requestMatchers(“/user/login”).permitAll() // 开放登录接口
Caused by: java.lang.IllegalArgumentException: A UserDetailsService must be set
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Unsatisfied dependency expressed through method 'setFilterChains' parameter 0: Error creating bean with name 'securityFilterChain' defined in class path resource [com/example/demonew/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'securityFilterChain' threw exception with message: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider@2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:896)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:849)at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:146)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:509)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1441)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:289)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1122)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1093)at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1030)at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987)at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752)at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439)at org.springframework.boot.SpringApplication.run(SpringApplication.java:318)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350)at com.example.demonew.DemoNewApplication.main(DemoNewApplication.java:16)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityFilterChain' defined in class path resource [com/example/demonew/config/SecurityConfig.class]: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'securityFilterChain' threw exception with message: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider@2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:657)at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:645)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1357)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1187)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:289)at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1883)at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1847)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeanCollection(DefaultListableBeanFactory.java:1737)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveMultipleBeans(DefaultListableBeanFactory.java:1705)at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1580)at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1519)at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.resolveMethodArguments(AutowiredAnnotationBeanPostProcessor.java:888)... 22 common frames omitted
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.security.web.SecurityFilterChain]: Factory method 'securityFilterChain' threw exception with message: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider@2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:199)at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiateWithFactoryMethod(SimpleInstantiationStrategy.java:88)at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:168)at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)... 39 common frames omitted
Caused by: java.lang.RuntimeException: Could not postProcess org.springframework.security.authentication.dao.DaoAuthenticationProvider@2a4e939a of type class org.springframework.security.authentication.dao.DaoAuthenticationProviderat org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.postProcess(AutowireBeanFactoryObjectPostProcessor.java:71)at org.springframework.security.config.annotation.SecurityConfigurerAdapter$CompositeObjectPostProcessor.postProcess(SecurityConfigurerAdapter.java:130)at org.springframework.security.config.annotation.SecurityConfigurerAdapter.postProcess(SecurityConfigurerAdapter.java:82)at org.springframework.security.config.annotation.authentication.configurers.userdetails.AbstractDaoAuthenticationConfigurer.configure(AbstractDaoAuthenticationConfigurer.java:96)at org.springframework.security.config.annotation.authentication.configurers.userdetails.AbstractDaoAuthenticationConfigurer.configure(AbstractDaoAuthenticationConfigurer.java:36)at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.configure(AbstractConfiguredSecurityBuilder.java:398)at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:352)at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38)at org.springframework.security.config.annotation.web.builders.HttpSecurity.beforeConfigure(HttpSecurity.java:3301)at org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder.doBuild(AbstractConfiguredSecurityBuilder.java:351)at org.springframework.security.config.annotation.AbstractSecurityBuilder.build(AbstractSecurityBuilder.java:38)at com.example.demonew.config.SecurityConfig.securityFilterChain(SecurityConfig.java:49)at com.example.demonew.config.SecurityConfig$$SpringCGLIB$$0.CGLIB$securityFilterChain$2(<generated>)at com.example.demonew.config.SecurityConfig$$SpringCGLIB$$FastClass$$1.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258)at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:348)at com.example.demonew.config.SecurityConfig$$SpringCGLIB$$0.securityFilterChain(<generated>)at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)at java.base/java.lang.reflect.Method.invoke(Method.java:580)at org.springframework.beans.factory.support.SimpleInstantiationStrategy.lambda$instantiate$0(SimpleInstantiationStrategy.java:171)... 42 common frames omitted
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.authentication.dao.DaoAuthenticationProvider@2a4e939a': A UserDetailsService must be setat org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:413)at org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.initializeBeanIfNeeded(AutowireBeanFactoryObjectPostProcessor.java:98)at org.springframework.security.config.annotation.configuration.AutowireBeanFactoryObjectPostProcessor.postProcess(AutowireBeanFactoryObjectPostProcessor.java:67)... 61 common frames omitted
Caused by: java.lang.IllegalArgumentException: A UserDetailsService must be setat org.springframework.util.Assert.notNull(Assert.java:181)at org.springframework.security.authentication.dao.DaoAuthenticationProvider.doAfterPropertiesSet(DaoAuthenticationProvider.java:99)at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.afterPropertiesSet(AbstractUserDetailsAuthenticationProvider.java:119)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855)at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)... 64 common frames omitted
Disconnected from the target VM, address: '127.0.0.1:59351', transport: 'socket'Process finished with exit code 1
解决
2025-01-14 11:59:16.898 WARN — [nio-8080-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
解决
密码格式不正确,确保数据库中的密码使用 BCrypt 加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String encode = encoder.encode("123456");
TemplateInputException
详细信息如下
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.thymeleaf.exceptions.TemplateInputException: Error resolving template [user/login], template might not exist or might not be accessible by any of the configured Template Resolvers] with root cause
org.thymeleaf.exceptions.TemplateInputException: Error resolving template [user/login], template might not exist or might not be accessible by any of the configured Template Resolvers
在返回return时候报错
解决
在 pom.xml 中删除 Thymeleaf 依赖(如果不使用 Thymeleaf)
原因
Spring Boot 默认渲染视图:
当你使用 Spring Boot 启动项目时,如果没有明确声明返回类型,Spring Boot 会默认尝试将响应返回为视图(view)页面,而不是直接返回 JSON 或其他格式的数据。
在你的代码中,login 方法返回的是 Result.success(token),这个 token 是你生成的 JWT,应该作为 API 的响应数据返回,而不是试图通过 Thymeleaf 渲染 user/login 模板。
返回的是数据,而非视图:
如果你的 login 是一个 RESTful 风格的 API,应该返回 JSON 数据而不是试图渲染页面。Spring 默认会将没有指定视图的请求作为视图解析,导致它去找 user/login.html。
相关文章:
java springboot3.x jwt+spring security6.x实现用户登录认证
springboot3.x jwtspring security6.x实现用户登录认证 什么是JWT JWT(JSON Web Token)是一种开放标准(RFC 7519),它用于在网络应用环境中传递声明。通常,JWT用于身份验证和信息交换。JWT的一个典型用法是…...
YOLOv5训练长方形图像详解
文章目录 YOLOv5训练长方形图像详解一、引言二、数据集准备1、创建文件夹结构2、标注图像3、生成标注文件 三、配置文件1、创建数据集配置文件2、选择模型配置文件 四、训练模型1、修改训练参数2、开始训练 五、使用示例1、测试模型2、评估模型 六、总结 YOLOv5训练长方形图像详…...
【2025最新】Poe保姆级订阅指南,Poe订阅看这一篇就够了!最方便使用各类AI!
1.Poe是什么? Poe, 全称Platform for Open Exploration。 Poe本身并不提供基础的大语言模型,而是整合多个来自不同科技巨头的基于不同模型的AI聊天机器人,其中包括来自OpenAI的ChatGPT,Anthropic的Claude、Google的PaLM…...
type1-100,2 words
dish n.餐具、碟,盘子;菜肴、饭菜(指一顿餐食中的一道菜) kind of 稍微;有点 sort of 稍微;有点儿 smallish adj.有点小的 crack 敲碎/裂,敲开,砸开,砸碎;裂开…...
Leetcode 377. 组合总和 Ⅳ 动态规划
原题链接:Leetcode 377. 组合总和 Ⅳ 可参考官解 class Solution { public:int combinationSum4(vector<int>& nums, int target) {vector<int> dp(target 1);dp[0] 1;// 总和为 i 的元素组合的个数for (int i 1; i < target; i) {// 每次都…...
计算机网络(五)——传输层
一、功能 传输层的主要功能是向两台主机进程之间的通信提供通用的数据传输服务。功能包括实现端到端的通信、多路复用和多路分用、差错控制、流量控制等。 复用:多个应用进程可以通过同一个传输层发送数据。 分用:传输层在接收数据后可以将这些数据正确分…...
【SQL】进阶知识 -- 删除表的几种方法(包含表内单个字段的删除方法)
大家好!欢迎来到本篇SQL进阶博客。如果你已经掌握了基础的SQL操作,接下来就让我们一起探索删除表的几种方法。删除表可能听起来有点危险,事实也是如此,所以在我们实际开发过程中,大多数时候我们都有数据的使用权限&…...
【搭建JavaEE】(3)前后端交互,请求响应机制,JDBC数据库连接
前后端交互 Apache Tomat B/S目前主流。 tomat包含2部分: apache容器 再认识servlet 抽象出的开发模式 项目创建配置 maven javaeetomcat 忽略一些不用的文件 webapp文件夹 HiServlet 这里面出现了webinfo,这个别删因为这个呢,是这这个这…...
项目概述、开发环境搭建(day01)
软件开发整体介绍 软件开发流程 第1阶段: 需求分析 需求规格说明书, 一般来说就是使用 Word 文档来描述当前项目的各个组成部分,如:系统定义、应用环境、功能规格、性能需求等,都会在文档中描述。产品原型,一般是通过…...
车联网安全--TLS握手过程详解
目录 1. TLS协议概述 2. 为什么要握手 2.1 Hello 2.2 协商 2.3 同意 3.总共握了几次手? 1. TLS协议概述 车内各ECU间基于CAN的安全通讯--SecOC,想必现目前多数通信工程师们都已经搞的差不多了(不要再问FvM了);…...
【python】OpenCV—Extract Horizontal and Vertical Lines—Morphology
文章目录 1、功能描述2、代码实现3、效果展示4、完整代码5、参考 更多有趣的代码示例,可参考【Programming】 1、功能描述 基于 opencv-python 库,利用形态学的腐蚀和膨胀,提取图片中的水平或者竖直线条 2、代码实现 导入基本的库函数 im…...
Redis十大数据类型详解
Redis(一) 十大数据类型 redis字符串(String) string是redis最基本的类型,一个key对应一个value string类型是二进制安全的,意思是redis的string可以包含任何数据。例如说是jpg图片或者序列化对象 一个re…...
Open FPV VTX开源之betaflight配置
Open FPV VTX开源之betaflight配置 1. 源由2. 配置3. 总结4. 参考资料5. 补充 - 飞控固件版本 1. 源由 飞控嵌入式OSD - ardupilot配置使用betaflight配套OSD图片。 Choose correct font depending on Flight Controller SW. ──> /usr/share/fonts/├──> font_btfl…...
AT32 bootloader程序与上位机程序
从8051到stm32, 从串口下载到JLINK调试,从keil到arm-none-eabi-gcc,从"Hello wrold"到通信协议,一路起来已学会很多,是时候写一下bootloader了。 基本原理 单片机代码编译完后可以生成".hex"和".bin"文件&…...
数据结构与算法之栈: LeetCode 151. 反转字符串中的单词 (Ts版)
反转字符串中的单词 https://leetcode.cn/problems/reverse-words-in-a-string/ 描述 给你一个字符串 s ,请你反转字符串中 单词 的顺序单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开 返回 单词 顺序颠倒且 单词 之间用单个空…...
使用 configparser 读取 INI 配置文件
使用 configparser 读取 INI 配置文件 适合于读取 .ini 格式的配置文件。 配置文件示例 (config.ini): [DEFAULT] host localhost port 3306 [database] user admin password secret import configparser# 创建配置解析器 config configparser.ConfigParser()# 读取配…...
idea 自动导包,并且禁止自动导 *(java.io.*)
自动导包配置 进入 idea 设置,可以按下图所示寻找位置,也可以直接输入 auto import 快速定位到配置。 Add unambiguous imports on the fly:自动帮我们优化导入的包Optimize imports on the fly:自动去掉一些没有用到的包 禁止导…...
RK3588-NPU pytorch-image-models 模型编译测试
RK3588-NPU pytorch-image-models 模型编译测试 一.背景二.操作步骤1.下载依赖2.创建容器3.安装依赖4.创建脚本A.生成模型名列表B.生成ONNX模型C.生成RKNN模型D.批量测试脚本 一.背景 测试RK3588-NPU对https://github.com/huggingface/pytorch-image-models.git中模型的支持程…...
低代码从“产品驱动”向“场景驱动”转型,助力数字化平台构建
一、前言 在数字化时代的大潮中,从宏观层面来看,新技术的落地速度不断加快,各行各业的数字化进程呈现出如火如荼的态势。而从微观层面剖析,企业面临着行业格局快速变化、市场竞争日益激烈以及成本压力显著增强等诸多挑战。 据专…...
相加交互效应函数发布—适用于逻辑回归、cox回归、glmm模型、gee模型
在统计分析中交互作用是指某因素的作用随其他因素水平变化而变化,两因素共同作用不等于两因素单独作用之和(相加交互作用)或之积(相乘交互作用)。相互作用的评估是尺度相关的:乘法或加法。乘法尺度上的相互作用意味着两次暴露的综合效应大于(…...
接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
