(五)Spring Boot学习——spring security +jwt使用(前后端分离模式)

一定要熟悉spring security原理和jwt无状态原理,理解了才知道代码作用。

Spring Security + JWT 认证流程中,通常的做法是:

  1. 用户提交用户名和密码
  2. Spring Security 认证管理器 (AuthenticationManager) 进行认证
  3. 如果认证成功,生成 JWT Token 并返回给用户

更详细一点

  1. 用户首次登录

    • 发送 POST /login 请求,携带 用户名 + 密码
    • authenticationManager.authenticate() 认证成功后,返回 JWT
    • 前端存储 JWT(通常是 localStoragesessionStorage
  2. 用户访问受保护接口

    • 前端在 Authorization 头中附带 Bearer Token
    • 过滤器 JWTFilter 解析 JWT,从 数据库 加载 UserDetails
    • SecurityContextHolder.setAuthentication() 认证成功,继续访问资源。

参考链接有:

spring security 超详细使用教程(接入springboot、前后端分离) - 小程xy - 博客园

SpringSecurity+jwt实现权限认证功能_spring security + jwt-CSDN博客

1.引入相关依赖。我使用的是springboot3.3.5   springsecurity是6.x的  jwt 0.12.6

<dependencies><!--用于数据加密,默认启用--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-crypto</artifactId></dependency>
</dependencies><!--依赖集中管理--><dependencyManagement><dependencies><!-- 使用jwt进行token验证,包括了三个依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.12.6</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency><dependencies></dependencyManagement>

2.配置SecurityConfig.java

package com.x.x.x.config;import com.x.x.x.filter.CustomFilter;
import com.x.x.x.filter.JwtAuthenticationTokenFilter;
import com.x.x.x.security.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.core.userdetails.User;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
public class SecurityConfig {/*** 用户名和密码也可以在application.properties中设置。* @return*/@Beanpublic UserDetailsService userDetailsService() {// 创建基于内存的用户信息管理器InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();// 创建UserDetails对象,用于管理用户名、用户密码、用户角色、用户权限等内容manager.createUser(User.withUsername("admin").password("yourpassword").roles("ADMIN").build());return manager;}/*** 认证管理。     jwt的用户验证* @param authConfig* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {return authConfig.getAuthenticationManager();}/*** 认证的token过滤器* @return*/@Beanpublic JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){return new JwtAuthenticationTokenFilter();}/*** 密码加码* @return*/@Beanpublic PasswordEncoder passwordEncoder() {// 也可用有参构造,取值范围是 4 到 31,默认值为 10。数值越大,加密计算越复杂return new BCryptPasswordEncoder();}/*** 配置过滤链* 配置自动注销功能必须在函数里加UserDetailsService userDetailsService,因为重写了使用数据库认证所以用baseuserserviceimpl* @param http* @return* @throws Exception*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http,  UserDetailsServiceImpl userDetailsService) throws Exception {http// 开启授权保护,配置请求授权规则.authorizeHttpRequests(authorize -> authorize.requestMatchers("/login","/mylogin","/druid/**").permitAll()   // 不需要认证的地址有哪些  ("/blog/**", "/public/**", "/about").anyRequest()      // 对所有请求开启授权保护.authenticated()   // 已认证的请求会被自动授权)// 配置自定义登录页面// 本处禁用前端页面,使用功能RESTful风格前后端分离,就是不用登录页面.formLogin(form -> form.disable()).httpBasic(Customizer -> Customizer.disable())// 启用记住我功能。允许用户关闭浏览器后仍然保持登录状态,直到主动注销或者查出设定过期时间//.rememberMe(Customizer.withDefaults()).rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret") // 设置一个密钥.tokenValiditySeconds(2 * 24 * 60 * 60) // 设置 RememberMe token 的有效期.userDetailsService(userDetailsService) // 显式设置 UserDetailsService)// 配置注销功能.logout(logout -> logout.logoutUrl("/perform_logout") // 自定义注销请求路径//.logoutSuccessUrl("/login?logout=true") // 注销成功后的跳转页面.deleteCookies("JSESSIONID") // 删除指定的 Cookie.permitAll() // 允许所有用户注销).sessionManagement(session -> session.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::changeSessionId)  // 防止会话固定攻击.maximumSessions(1) // 限制每个用户只能有一个活跃会话.maxSessionsPreventsLogin(false)// 如果为 true,禁止新登录;为 false,允许新登录并终止旧会话.expiredUrl("/login?session=expired") // 当会话过期时跳转到的页面);// 关闭 csrf CSRF(跨站请求伪造)是一种网络攻击,攻击者通过欺骗已登录用户,诱使他们在不知情的情况下向受信任的网站发送请求。http.csrf(csrf -> csrf.disable());// 注册自定义的过滤器CustomFilter// 用于jwt 功能确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);//已经在customfilter中重写 http.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);//授权认证,基于角色在 Spring Security 6.x 版本中,antMatchers() 方法已被移除,取而代之的是使用新的基于 请求匹配器 (RequestMatchers) 的方法/*http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/admin/**").hasRole("ADMIN")  // 只有 ADMIN 角色可以访问.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")  // USER 和 ADMIN 角色可以访问.anyRequest().authenticated());  // 其他请求需要认证//基于权限的授权,编辑权限还是只读等http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE")  // 仅具有 EDIT_PRIVILEGE 权限的用户可以访问.anyRequest().authenticated());  // 其他请求需要认证*/return http.build();}
}

3.重写loadUserByUsername的方法。

(1)UserDetailsImpl.java

package com.x.x.x.security.service.impl;import com.x.x.x.entity.BaseUsers;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;
import java.util.List;@Data
@AllArgsConstructor
@NoArgsConstructor    // 这三个注解可以帮我们自动生成 get、set、有参、无参构造函数
public class UserDetailsImpl implements UserDetails {private BaseUsers baseUsers;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return List.of();}@Overridepublic String getPassword() {return baseUsers.getPassword();}@Overridepublic String getUsername() {return baseUsers.getOaId();}@Overridepublic boolean isAccountNonExpired() {  // 检查账户是否 没过期。return true;}@Overridepublic boolean isAccountNonLocked() {   // 检查账户是否 没有被锁定。return true;}@Overridepublic boolean isCredentialsNonExpired() {  //检查凭据(密码)是否 没过期。return true;}@Overridepublic boolean isEnabled() {    // 检查账户是否启用。return true;}// 这个方法是 @Data注解 会自动帮我们生成,用来获取 loadUserByUsername 中最后我们返回的创建UserDetailsImpl对象时传入的User。// 如果你的字段包含 username和password 的话可以用强制类型转换, 把 UserDetailsImpl 转换成 User。如果不能强制类型转换的话就需要用到这个方法了public BaseUsers getUser() {return baseUsers;}
}

(2)UserDetailsServiceImpl.java

package com.x.x.x.security.service.impl;import com.x.x.x.entity.BaseUsers;
import com.x.x.x.service.BaseUsersService;
import org.springframework.beans.factory.annotation.Autowired;
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.List;@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate BaseUsersService baseUsersService;/*** 重写loadUserByUsername方法* @param username the username identifying the user whose data is required.* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {BaseUsers baseUsers = new BaseUsers();baseUsers.setOaId(username);List<BaseUsers> baseUsersList = baseUsersService.queryUsersList(baseUsers);if (baseUsersList == null || baseUsersList.isEmpty()) {System.out.println("------------->       loadUserByUsername验证失败, "+baseUsers.getOaId()+" 不存在!");throw new UsernameNotFoundException(username);}return new UserDetailsImpl(baseUsersList.get(0));	// UserDetailsImpl 是我们实现的类}
}

4.JwtAuthenticationProvider.java继承重新AuthenticationProvider的authenticate方法。这里注意可能未使用我们继承的userDetailsService,所以使用@Qualifier("")指定

package com.x.x.x.security.handler;import io.micrometer.common.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowired@Qualifier("userDetailsServiceImpl")//需要指定注入的是那个类,避免报错。private UserDetailsService userDetailsService;@Overridepublic Authentication authenticate(Authentication authentication) {String username = String.valueOf(authentication.getPrincipal());String password = String.valueOf(authentication.getCredentials());UserDetails userDetails = userDetailsService.loadUserByUsername(username);System.out.println("------------->       JwtAuthenticationProvider:"+userDetails.getUsername()+","+userDetails.getPassword());if(userDetails != null && StringUtils.isNotBlank(userDetails.getPassword())&& userDetails.getPassword().equals(password)){return new UsernamePasswordAuthenticationToken(username,password,authentication.getAuthorities());}try {throw new Exception("RespCodeEnum.NAME_OR_PASSWORD_ERROR");} catch (Exception e) {throw new RuntimeException(e);}}@Overridepublic boolean supports(Class<?> authentication) {return UsernamePasswordAuthenticationToken.class.equals(authentication);}
}

5.拦截器实现。

(1)CustomFilter

package com.x.x.x.filter;import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;import java.io.IOException;/*** OncePerRequestFilter 是 Spring Security 提供的一个抽象类,确保在每个请求中只执行一次特定的过滤逻辑。* 它是实现自定义过滤器的基础,通常用于对请求进行预处理或后处理。(实现 JWT 会用到这个接口)* 提供了一种机制,以确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景。* 通过继承该类,可以轻松实现自定义过滤器适合用于记录日志、身份验证、权限检查等场景。** 本处继承 OncePerRequestFilter 类,并重写 doFilterInternal 方法。* 但是需要再spring security配置类中注册自定义的过滤器*/
public class CustomFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,FilterChain filterChain) throws ServletException, IOException {// 自定义过滤逻辑,例如记录请求日志System.out.println("Request URI: " + request.getRequestURI());// 继续执行过滤链filterChain.doFilter(request, response);}
}

(2)JwtAuthenticationTokenFilter

package com.x.x.x.filter;import com.x.x.x.dao.BaseUsersDao;
import io.jsonwebtoken.Claims;
import java.io.IOException;import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
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.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.x.x.x.until.JwtUtil;@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {/***  用于验证账号密码,本处于数据库交互*/@Autowiredprivate BaseUsersDao baseUsersDao;@Autowired@Qualifier("userDetailsServiceImpl")//需要指定注入的是那个类,避免报错。private UserDetailsService userDetailsService;/*** 重写了 OncePerRequestFilter 类中的抽象方法 doFilterInternal。* OncePerRequestFilter 是 Spring Security 提供的一个基础类* ,设计用来确保过滤器在同一个请求中只执行一次。* @param request* @param response* @param filterChain* @throws ServletException* @throws IOException*/@Overrideprotected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {// 获取请求头的验证信息,即前端传回的tokenString token = request.getHeader("Authorization");System.out.println("----》     JwtAuthenticationTokenFilter,验证token过滤器,获取到的token值:"+token);//为空时候继续下一步过滤链,即进行登录认证。后续进行格式验证,如果以bearer开始去掉前面的前缀if (!StringUtils.hasText(token) ) {System.out.println("----》     JwtAuthenticationTokenFilter,token验证:"+"token为空!");filterChain.doFilter(request, response);return;}if (token.startsWith("Bearer ")) {System.out.println("----》     JwtAuthenticationTokenFilter,token格式验证中:"+"token格式以Bearer开头,去掉开头!");token = token.substring(7);}//验证token是否过期boolean isValid = JwtUtil.validateJwtToken(token);//只在util中只验证是否过期了。if (!isValid) {System.out.println("----》     token验证失败,token过期。");response(response, "验证失败");return;}//获取token载荷中的用户信息Claims claims = JwtUtil.parseClaim(token).getPayload();String userid = claims.get("username").toString();//查询数据库中用户信息System.out.println("----》     数据库验证用户信息。"+"userid:"+userid);UserDetails userDetails = userDetailsService.loadUserByUsername(userid);System.out.println("----》     数据库中数据:"+userDetails.getUsername()+","+userDetails.getPassword());//设置安全上下文//创建一个自定义的 UserDetailsImpl 对象,将查询到的用户信息封装。//创建一个 UsernamePasswordAuthenticationToken 对象,表示用户的认证信息// ,并将其设置到 Spring Security 的 SecurityContextHolder 中,以便后续请求能够访问到用户的认证信息。UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());// 如果是有效的jwt,那么设置该用户为认证后的用户SecurityContextHolder.getContext().setAuthentication(authenticationToken);//继续过滤链System.out.println("----》     jwt过滤器执行完毕!"+authenticationToken);filterChain.doFilter(request, response);}private void response(@NotNull HttpServletResponse response,String error) throws IOException {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定义状态码response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding("UTF-8");response.getWriter().write("{\n" +"  \"states\": \""+error+"\",\n" +"  \"message\": \"无效token!\"\n" +"}");}}

6.jwt实现

package com.x.x.x.until;import com.x.x.x.enums.BaseInfoEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.*;// @Component将这个类标记为 Spring 组件,允许 Spring 管理该类的生命周期,便于依赖注入。
@Component
public class JwtUtil {/*** 过期时间(单位:秒),4小时为14400s*/public static final int ACCESS_EXPIRE = Integer.parseInt(BaseInfoEnum.fiedIdOf("access_expire").getFiedIdInfo());//14400;/*** 加密算法*/private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;/*** 私钥 / 生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取,切记这个秘钥不能外露,只在服务端使用,在任何场景都不应该流露出去。* 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。* 应该大于等于 256位(长度32及以上的字符串),并且是随机的字符串*/private final static String SECRET = BaseInfoEnum.fiedIdOf("secret").getFiedIdInfo();//"Cpj2cc09BRTstcISP5HtEAMxwuFEh-nJiL1mppdsz8k@lzgs";/*** 秘钥实例,相比secretkeyspec方法base64编码指定验证方式,该种方式更加简便安全。*/public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());/*** jwt签发者*/private final static String JWT_ISS = BaseInfoEnum.fiedIdOf("jwt_iss").getFiedIdInfo();/*** jwt主题*/private final static String SUBJECT = "Peripherals";/*** jwt构建器,生成token* 这些是一组预定义的声明,它们 不是强制性的,而是推荐的 ,以 提供一组有用的、可互操作的声明 。* iss: jwt签发者* sub: jwt所面向的用户* aud: 接收jwt的一方* exp: jwt的过期时间,这个过期时间必须要大于签发时间* nbf: 定义在什么时间之前,该jwt都是不可用的.* iat: jwt的签发时间* jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击*/public static String genAccessToken(String username ,String roleId,String company) {// 令牌idString uuid = UUID.randomUUID().toString();Date exprireDate = Date.from(Instant.now().plusSeconds(ACCESS_EXPIRE));//System.out.println("key:"+KEY);return Jwts.builder()// 设置头部信息header.header().add("typ", "JWT").add("alg", "HS256").and()// 设置自定义负载信息payload.claim("username", username )//.claim("roleId",roleId ).claim("company",company )// 令牌ID.id(uuid)// 过期日期.expiration(exprireDate)// 签发时间.issuedAt(new Date())// 主题.subject(SUBJECT)// 签发者.issuer(JWT_ISS)// 签名.signWith(KEY, ALGORITHM).compact();}/*** 解析token* @param token token* @return Jws<Claims>*/public static Jws<Claims> parseClaim(String token) {return Jwts.parser().verifyWith(KEY).build().parseSignedClaims(token);}/*** 获取头部信息* @param token* @return*/public static JwsHeader parseHeader(String token) {return parseClaim(token).getHeader();}/*** 获取载荷信息* @param token* @return*/public static Claims parsePayload(String token) {return parseClaim(token).getPayload();}/*** token验证,token是否过期正确* @param token* @return*/public static boolean validateJwtToken(String token) {try {// 解析 Token,验证签名。验证载荷Claims claims = parseClaim(token).getPayload();//System.out.println("content:---"+claims.get("username"));// 验证声明(例如过期时间)if (claims.getExpiration().before(new Date())) {System.out.println("Token has expired.");return false;}// 在这里可以进行其他自定义验证// 例如检查用户角色、权限等// Token 验证通过return true;} catch (Exception e) {// 验证失败System.out.println("Token validation failed: " + e.getMessage());return false;}}/*** 直接获取到载荷的具体内容* @param token* @return*/public static Map<String, Object> token2userInfo(String token){Map<String, Object> tokenMap = new HashMap<String, Object>();Claims claims = parseClaim(token).getPayload();tokenMap.put("company", claims.get("company"));tokenMap.put("loginName", claims.get("username"));tokenMap.put("roleId", claims.get("roleId"));return tokenMap;}//测试public static void main(String[] args){String token = genAccessToken("123","admin","123");System.out.println("token:"+token);boolean isValid = validateJwtToken(token);System.out.println(isValid);System.out.println(parseHeader(token));System.out.println(parsePayload(token));}}

7.接口实现

/*** 用户登录接口。* 本处调用spring security验证功能。(但本项目是前后端分离的,禁用了security登录页功能,* 因为其重定向默认只能用“GET”方式请求)* @param request* @return* @throws Exception*/@PostMapping("/login")public Map<String, Object> login(HttpServletRequest request) throws Exception{Map<String, Object> modelMap = new HashMap<String, Object>();request.setCharacterEncoding("UTF8");//设置request获取数据的编码方式为utf-8String loginName = HttpServletRequestUtil.getString(request, "loginName");String password = HttpServletRequestUtil.getString(request, "password");if (loginName ==null || loginName.isBlank()  || password == null || password.isBlank()){modelMap.put("success", false);modelMap.put("msg", "用户名和密码均不能为空");logger.error("---->    登录失败,用户名和密码为空!");return modelMap;}//认证设置,在后续的方法中,已经设置了连接数据库认证loadUserByUsername//先设置认证authentication  这一步Authenticated=falseUsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);//自动调用loadUserByUsername验证用户名和密码,从数据库中对比查找,如果找到了会返回一个带有认证的封装后的用户,否则会报错,自动处理。(这里我们假设我们配置的security是基于数据库查找的)try{Authentication authenticate = authenticationManager.authenticate(authenticationToken);SecurityContextHolder.getContext().setAuthentication(authenticate);String token = genAccessToken(loginName,"admin","123");modelMap.put("token",token);modelMap.put("success", true);return modelMap;} catch (Exception e) {modelMap.put("success", false);modelMap.put("msg", "用户名或密码错误");logger.error("---->    登录失败,用户名或密码错误!");return modelMap;}}

这里需要注意:

1.一般是url请求带token,直接验证token,通过则授权,在过滤器JwtAuthenticationTokenFilter中UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());  验证结果是true的。

2.不带token则在控制器中对用户密码进行验证,因为在loadUserByUsername方法中设置了对用户名密码的验证,所以使用UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);后,需要手动使用 Authentication authenticate = authenticationManager.authenticate(authenticationToken);进行验证,验证通过则验证结果是true的。

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

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

相关文章

清华DeepSeek手册:从入门到精通(网页版便于阅读)

目录 一、产品概述二、清华DeepSeek从入门到精通三、PDF文件路径 一、产品概述 DeepSeek是国产领先的人工智能技术平台&#xff0c;提供从数据分析到大型语言模型的全栈解决方案。其核心产品包括网页端数据分析工具[1] 、视觉语言模型(DeepSeek-VL)[2] 和670亿参数大型语言模型…

阿里云百炼初探DeepSeek模型调用

阿里云百炼初探DeepSeek模型调用 阿里云百炼为什么选择百炼开始使用百炼方式一&#xff1a;文本对话方式二&#xff1a;文本调试方式三&#xff1a;API调用 DeepSeek调用1、搜索模型2、查看API调用3、开始调用安装依赖查看API Key运行以下代码 4、流式输出 总结 阿里云百炼 阿…

【网络安全】服务器安装Docker及拉取镜像教程

文章目录 1. 安装 Docker2. 拉取镜像3. 运行 Ubuntu 容器4. 执行相关操作5. 退出并停止容器1. 安装 Docker # 更新软件包索引 sudo apt update# 安装必要的依赖 sudo apt install -y ca-certificates curl gnupg

AI刷题-子数组和的最大值问题

目录 问题描述 输入格式 输出格式 输入样例 输出样例 说明 数据范围 解题思路&#xff1a; 问题理解 数据结构选择 算法步骤 具体步骤 代码实现&#xff1a; 1.特判&#xff1a; 不需要删除元素的时候 2.在前面的判断结束后&#xff1a;k1&#xff0c;&#xff…

【语法】C++的内存管理 模板

内存管理&#xff1a; 在C语言中&#xff0c;动态开辟空间可以用malloc&#xff0c;calloc&#xff0c;realloc这三个函数&#xff0c;下面先来复习一下这三者的区别 malloc和calloc都是用来开辟新空间&#xff0c;calloc在malloc的基础上还会初始化该空间为0&#xff0c;用法…

30~32.ppt

目录 30.导游小姚-介绍首都北京❗ 题目​ 解析 31.小张-旅游产品推广文章 题目 解析 32.小李-水的知识❗ 题目​ 解析 30.导游小姚-介绍首都北京❗ 题目 解析 新建幻灯片-从大纲-重置-检查设计→主题对话框→浏览主题&#xff1a;考生文件夹&#xff08;注意&#x…

uniapp实现人脸识别(不使用三方插件)

uniapp实现人脸识别 内容简介功能实现上传身份证进行人脸比对 遇到的问题 内容简介 1.拍摄/相册将身份证照片上传到接口进行图片解析 2.使用live-pusher组件拍摄人脸照片&#xff0c;上传接口与身份证人脸进行比对 功能实现 上传身份证 先看下效果 点击按钮调用chooseImage…

Evaluating Very Long-Term Conversational Memory of LLM Agents 论文

Abstract : 长期开放域对话的现有作品着重于评估不超过五个聊天会议的上下文中的模型响应。尽管LongContext大语言模型&#xff08;LLM&#xff09;和检索增强发电&#xff08;RAG&#xff09;技术的进步&#xff0c;但在长期对话中的功效仍未得到探索。为了解决这一研究差距&a…

相对收益-固定收益组合归因-Campisi模型

固定收益组合归因-Campisi模型 1 Campisi模型11.1 Campisi归因框架1.2 Campisi模型绝对收益分解1.2.1 票息收益1. 2.2 收敛收益1. 2.3 骑乘收益1. 2.4 平移收益1. 2.5 扭曲收益1. 2.6 利差收益1. 2.7 残差收益 1.3 Campisi模型超额收益分解 2 Campisi模型22.1 分解框架2.2 模型…

IntelliJ IDEA使用经验(十三):使用Git克隆github的开源项目

文章目录 问题背景办法1、设置git代理&#xff1b;2、再次克隆项目&#xff1b;3、再次按常规方式进行git克隆即可。 问题背景 由于github在国外&#xff0c;很多时候我们在使用idea克隆开源项目的时候&#xff0c;没办法检出&#xff0c;提示 连接重置。 办法 1、设置git代…

JAVA安全之Java Agent打内存马

基本介绍 Java Agent是一种特殊的Java程序&#xff0c;它允许开发者在Java虚拟机(JVM)启动时或运行期间通过java.lang.instrument包提供的Java标准接口进行代码插桩&#xff0c;从而实现在Java应用程序类加载和运行期间动态修改已加载或者未加载的类&#xff0c;包括类的属性、…

RabbitMQ 消息顺序性保证

方式一&#xff1a;Consumer设置exclusive 注意条件 作用于basic.consume不支持quorum queue 当同时有A、B两个消费者调用basic.consume方法消费&#xff0c;并将exclusive设置为true时&#xff0c;第二个消费者会抛出异常&#xff1a; com.rabbitmq.client.AlreadyClosedEx…

【MQ】Spring3 中 RabbitMQ 的使用与常见场景

一、初识 MQ 传统的单体架构&#xff0c;分布式架构的同步调用里&#xff0c;无论是方法调用&#xff0c;还是 OpenFeign 难免会有以下问题&#xff1a; 扩展性差&#xff08;高耦合&#xff0c;需要依赖对应的服务&#xff0c;同样的事件&#xff0c;不断有新需求&#xff0…

EasyExcel 导出合并层级单元格

EasyExcel 导出合并层级单元格 一、案例 案例一 1.相同订单号单元格进行合并 合并结果 案例二 1.相同订单号的单元格进行合并2.相同订单号的总数和总金额进行合并 合并结果 案例三 1.相同订单号的单元格进行合并2.相同订单号的商品分类进行合并3.相同订单号的总数和总金额…

cs106x-lecture3(Autumn 2017)

打卡cs106x(Autumn 2017)-lecture3 1、streamErrors Suppose an input file named streamErrors-data.txt contains the following text: Donald Knuth M 76 Stanford U. The code below attempts to read the data from the file, but each section has a bug. Correct th…

C++模板编程——typelist的实现

文章最后给出了汇总的代码&#xff0c;可直接运行 1. typelist是什么 typelist是一种用来操作类型的容器。和我们所熟知的vector、list、deque类似&#xff0c;只不过typelist存储的不是变量&#xff0c;而是类型。 typelist简单来说就是一个类型容器&#xff0c;能够提供一…

windows通过网络向Ubuntu发送文件/目录

由于最近要使用树莓派进行一些代码练习&#xff0c;但是好多东西都在windows里或虚拟机上&#xff0c;就想将文件传输到树莓派上&#xff0c;但试了发现u盘不能简单传送&#xff0c;就在网络上找到了通过windows 的scp命令传送 前提是树莓派先开启ssh服务&#xff0c;且Window…

字节跳动后端一面

&#x1f4cd;1. Gzip压缩技术详解 Gzip是一种流行的无损数据压缩格式&#xff0c;它使用DEFLATE算法来减少文件大小&#xff0c;广泛应用于网络传输和文件存储中以提高效率。 &#x1f680; 使用场景&#xff1a; • 网站优化&#xff1a;通过压缩HTML、CSS、JavaScript文件来…

三维模拟-机械臂自翻车

机械仿真 前言效果图后续 前言 最近在研究Unity机械仿真&#xff0c;用Unity实现其运动学仿真展示的功能&#xff0c;发现一个好用的插件“MGS-Machinery-master”&#xff0c;完美的解决了Unity关节定义缺少液压缸伸缩关节功能&#xff0c;内置了多个场景&#xff0c;讲真的&…

USB子系统学习(四)用户态下使用libusb读取鼠标数据

文章目录 1、声明2、HID协议2.1、描述符2.2、鼠标数据格式 3、应用程序4、编译应用程序5、测试6、其它 1、声明 本文是在学习韦东山《驱动大全》USB子系统时&#xff0c;为梳理知识点和自己回看而记录&#xff0c;全部内容高度复制粘贴。 韦老师的《驱动大全》&#xff1a;商…