Spring Security实现用户认证四:使用JWT与Redis实现无状态认证

Spring Security实现用户认证四:使用JWT与Redis实现无状态认证

  • 1 什么是无状态认证?
  • 2 什么是JWT?
    • 2.1 需要注意的事项
    • 2.2 JWT构成
  • 3 Spring Security + JWT实现无状态认证
    • 3.1 创建一个Spring Boot项目
      • 3.1.1 依赖
      • 3.1.2 Main
      • 3.1.3 application.yml
    • 3.2 Controller
      • 3.2.1 LoginController
      • 3.3.2 IndexController
    • 3.3 User Entity
    • 3.4 Service
      • UserService
      • UserServiceImpl
    • 3.5 UserMapper.java
    • 3.6 UserMapper.xml
    • 3.7 JwtTokenProvider.java
    • 3.8 Redis配置类
    • 3.9 MyRedisSecurityContextRepository.java
    • 3.10 DBUserDetailManager.java
    • 3.11 SpringSecurityConfig配置类
    • 3.12 MyAuthenticationEntryPoint

1 什么是无状态认证?

在基本的通信流程中,我们一般采用Session去存储用户的认证状态。在Spring Security实现用户认证三中讲过,在拿到前端传输过来的用户名和密码之后,会有专门的过滤器UsernamePasswordAuthenticationFilter处理这部分的需求,并且对认证成功的用户生成Token且存储在Session中。在下次发起请求时,直接从Session中取出同用户名的token进行密码哈希的比较要认证用户。

对于无状态认证,则我们的认证不依赖与服务器端存储的Session的状态。所以无状态认证需要我们每次从前端传输一个包含完整认证信息的Token到服务器端进行自定义的认证过程,这使得服务器无需存储和管理会话数据。常见的无状态认证方法包括 JSON Web Token (JWT)、API Key和 OAuth 2.0。

2 什么是JWT?

JWT(JSON Web Token)是一种基于JSON的开放标准(RFC 7519),用于在各方之间传递信息。JWT可以进行数字签名,并且可以选择加密其内容。它定义了一种紧凑和自包含的方式, 可以通过URL、POST参数或HTTP头在各方之间安全地传输信息。此信息可以进行验证和信任,因为它是经过数字签名的,但是签名不能保证数据的机密性。JWT 可以使用 HMAC 算法、RSA 或 ECDSA 的公钥/私钥对进行签名。

JWT最常见的用途是用户身份验证。一旦用户登录成功,服务器会生成一个JWT并返回给客户端。客户端将JWT存储在本地(如localStorage或cookie),并在每次请求时将其发送到服务器,服务器通过验证JWT来验证用户身份。

2.1 需要注意的事项

  • 保密:不要在JWT中存储敏感信息,因为JWT是可以被解码的。
  • 过期处理:设置合理的过期时间,并且在需要时支持刷新令牌机制。
  • 使用HTTPS:确保在传输JWT时使用HTTPS,防止中间人攻击。

2.2 JWT构成

JWT由三个主要部分构成:Header(头部)、Payload(负载)和 Signature(签名)。每个部分都有其特定的作用和结构。

  1. Header(头部)
    头部通常包含两个部分:令牌类型和使用的签名算法。头部数据结构为一个JSON对象,然后进行Base64Url编码。
{"alg": "HS256","typ": "JWT"
}
  1. Payload(负载)
    负载部分包含了声明(claims),即需要传输的数据。这些数据可以是关于用户的信息或者其他的元数据。声明可以分为三类:
  • Registered claims(注册声明):预定义的一些声明,如 iss(签发者),exp(过期时间),sub(主题),aud(受众)。
  • Public claims(公共声明):可以自定义的声明,但为了避免冲突,应使用URI命名。
  • Private claims(私有声明):由双方约定的声明,用于信息交换。
{"sub": "1234567890","name": "John Doe","admin": true
}
  1. Signature(签名)
    签名部分用于验证消息的发送者和确保消息在传递过程中未被篡改。签名的生成过程如下:
  • 将编码后的Header和Payload用句点 (.) 连接起来:
base64UrlEncode(header) + "." + base64UrlEncode(payload)
  • 使用头部中指定的签名算法,并结合一个密钥对上述连接的字符串进行签名。
  • 对于HMAC SHA256算法,签名过程如下:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret
)

最终,JWT的格式为:

header.payload.signature

3 Spring Security + JWT实现无状态认证

登录认证流程
登录认证流程如上。

3.1 创建一个Spring Boot项目

3.1.1 依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.12.3</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.12.3</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- 或者jjwt-gson,如果你更喜欢Gson --><version>0.12.3</version><scope>runtime</scope>
</dependency><!--        redis-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency><!--    数据库    -->
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId>
</dependency>
<dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId>
</dependency>
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.21</version>
</dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId>
</dependency>

3.1.2 Main

package com.song.cloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;@SpringBootApplication
@MapperScan("com.song.cloud.mapper")
public class ServiceSecurityJwt6501 {public static void main(String[] args) {SpringApplication.run(ServiceSecurityJwt6501.class, args);}
}

3.1.3 application.yml

spring:application:name: service-security-jwtdatasource:driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSourceusername: rootpassword: root# 注意修改数据库名字url: jdbc:mysql://localhost:3306/test? characterEncoding=utf8&useSSL=false&serverTimeZone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=truedata:  # 配置redisredis:port: 6379host: 192.168.62.128password: 1234app:jwt-sign-secret: gOk33w29WESOMEx8vUQLb69AsGhlUb7UmrFwu3g2TOo=jwt-expiration-milliseconds: 604800000  # 七天过期server:port: 6501logging:level:web: debugorg.springframework.security: debugmybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.song.cloud.entities  # 注意修改成自己的包名configuration:map-underscore-to-camel-case: true

3.2 Controller

3.2.1 LoginController

用来处理

package com.song.cloud.controller;import com.song.cloud.entities.User;
import com.song.cloud.service.UserService;
import com.song.cloud.utils.JwtTokenProvider;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class LoginController {@Resourceprivate JwtTokenProvider jwtTokenProvider;@Resourceprivate UserService userService;@Resourceprivate RedisTemplate<String, Object> redisTemplate;@PostMapping("/api/auth")public String auth(@RequestBody User user){System.out.println(user);UserDetails userDetails =  userService.loadUserDetail(user);PasswordEncoder delegatingPasswordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();boolean matches = delegatingPasswordEncoder.matches(user.getPasswordHash(), userDetails.getPassword());if(matches){UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated(userDetails.getUsername(), userDetails.getPassword(), userDetails.getAuthorities());token.setDetails(userDetails);System.out.println(token);//保存token到redisredisTemplate.opsForValue().set(userDetails.getUsername(), token);return jwtTokenProvider.generateToken(token);}return "fail";}
}

3.3.2 IndexController

package com.song.cloud.controller;import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Collection;
import java.util.HashMap;
import java.util.Map;@RestController
public class IndexController {@GetMapping("/")public Map index() {SecurityContext context = SecurityContextHolder.getContext();Authentication authentication = context.getAuthentication();Object principal = authentication.getPrincipal();Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities(); //脱敏处理Object credentials = authentication.getCredentials();HashMap<Object, Object> map = new HashMap<>();map.put("username", authentication.getName());map.put("authorities", authorities);map.put("credentials", credentials);map.put("details", authentication.getDetails());map.put("principal", principal);return map;}
}

3.3 User Entity

package com.song.cloud.entities;import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;/*** 表名:t_users_test
*/
@Table(name = "t_users_test")
public class User {/*** id*/@Id@GeneratedValue(generator = "JDBC")private Long id;/*** 用户名*/private String username;/*** 密码hash*/@Column(name = "password_hash")private String passwordHash;/*** 是否启用*/private Boolean enable;/*** 获取id** @return id - id*/public Long getId() {return id;}/*** 设置id** @param id id*/public void setId(Long id) {this.id = id;}/*** 获取用户名** @return username - 用户名*/public String getUsername() {return username;}/*** 设置用户名** @param username 用户名*/public void setUsername(String username) {this.username = username;}/*** 获取密码hash** @return passwordHash - 密码hash*/public String getPasswordHash() {return passwordHash;}/*** 设置密码hash** @param passwordHash 密码hash*/public void setPasswordHash(String passwordHash) {this.passwordHash = passwordHash;}/*** 获取是否启用** @return enable - 是否启用*/public Boolean getEnable() {return enable;}/*** 设置是否启用** @param enable 是否启用*/public void setEnable(Boolean enable) {this.enable = enable;}@Overridepublic String toString() {return "User{" +"enable=" + enable +", id=" + id +", username='" + username + '\'' +", passwordHash='" + passwordHash + '\'' +'}';}
}

3.4 Service

UserService

package com.song.cloud.service;import com.song.cloud.entities.User;
import org.springframework.security.core.userdetails.UserDetails;import java.util.List;public interface UserService {UserDetails loadUserDetail(User user);
}

UserServiceImpl

package com.song.cloud.service.impl;import com.song.cloud.config.DBUserDetailManager;
import com.song.cloud.entities.User;
import com.song.cloud.mapper.UserMapper;
import com.song.cloud.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserServiceImpl implements UserService {@Resourceprivate DBUserDetailManager dbUserDetailsManager;@Resourceprivate UserMapper userMapper;@Overridepublic List<User> list() {return userMapper.selectAll();}@Overridepublic UserDetails loadUserDetail(User user) {return dbUserDetailsManager.loadUserByUsername(user.getUsername());}}

3.5 UserMapper.java

package com.song.cloud.mapper;import com.song.cloud.entities.User;
import tk.mybatis.mapper.common.Mapper;public interface UserMapper extends Mapper<User> {
}

3.6 UserMapper.xml

注意将相应信息改成自己的。包路径、实体名、mapper类名等。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.song.cloud.mapper.UserMapper"><resultMap id="BaseResultMap" type="com.song.cloud.entities.User"><!--WARNING - @mbg.generated--><id column="id" jdbcType="BIGINT" property="id" /><result column="username" jdbcType="VARCHAR" property="username" /><result column="password_hash" jdbcType="VARCHAR" property="passwordHash" /><result column="enable" jdbcType="BIT" property="enable" /></resultMap>
</mapper>

3.7 JwtTokenProvider.java

主要用来创建JwtToken的。

package com.song.cloud.utils;import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;import javax.crypto.SecretKey;
import java.util.Date;@Component
public class JwtTokenProvider {private static final Logger logger = LoggerFactory.getLogger(JwtTokenProvider.class);@Value("${app.jwt-sign-secret}")private String jwtSignSecret;@Value("${app.jwt-expiration-milliseconds}")private long jwtExpirationDate;// 生成 JWT tokenpublic String generateToken(Authentication authentication) {// 构建一个JWT,它的注册声明(Subject)设为 usernameString username = authentication.getName();Date currentDate = new Date();Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);// 使用适合HMAC-SHA-256算法的密钥对JWT进行签名。// 将其紧凑压缩为最终的字符串形式。签名后的JWT称为'token'。String token = Jwts.builder().issuer("backend").subject(username).issuedAt(new Date()).expiration(expireDate).signWith(key()).compact();return token;}private SecretKey key() {return Keys.hmacShaKeyFor(//从base64解码得到byte[]Decoders.BASE64.decode(jwtSignSecret));}// 从 Jwt token 获取用户名public String getUsername(String token) {Claims claims = Jwts.parser().verifyWith(key()).build().parseSignedClaims(token).getPayload();return claims.getSubject();}// 验证 Jwt tokenpublic boolean validateToken(String token) {try {Jwts.parser().verifyWith(key()).build().parse(token);return true;} catch (MalformedJwtException e) {logger.error("Invalid JWT token: {}", e.getMessage());} catch (ExpiredJwtException e) {logger.error("JWT token is expired: {}", e.getMessage());} catch (UnsupportedJwtException e) {logger.error("JWT token is unsupported: {}", e.getMessage());} catch (IllegalArgumentException e) {logger.error("JWT claims string is empty: {}", e.getMessage());} catch (SignatureException e){logger.error("JWT signature validation fails: {}", e.getMessage());}return false;}
}

3.8 Redis配置类

package com.song.cloud.config;import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.jackson2.SecurityJackson2Modules;import java.util.*;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new JdkSerializationRedisSerializer());return template;}}

3.9 MyRedisSecurityContextRepository.java

实现了SecurityContextRepository,使用redis存储和管理SecurityContext

package com.song.cloud.config;import com.song.cloud.utils.JwtTokenProvider;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;@Component
public class MyRedisSecurityContextRepository implements SecurityContextRepository {@Resourceprivate JwtTokenProvider jwtTokenProvider;@Resourceprivate RedisTemplate<String, Object> redisTemplate;private final SecurityContextHolderStrategy contextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();@Overridepublic SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {SecurityContext emptyContext = this.contextHolderStrategy.createEmptyContext();HttpServletRequest request = requestResponseHolder.getRequest();String token = request.getHeader("Authorization");if(!StringUtils.hasText(token)) return emptyContext;String username = jwtTokenProvider.getUsername(token);if(username == null) return emptyContext;UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken)redisTemplate.opsForValue().get(username);emptyContext.setAuthentication(usernamePasswordAuthenticationToken);return emptyContext;}@Overridepublic void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {System.out.println("saveContext-----------------------------------------");}@Overridepublic boolean containsContext(HttpServletRequest request) {System.out.println("containsContext------------------------------------");CharSequence authorization = request.getHeader("Authorization");return StringUtils.hasText(authorization);}
}

3.10 DBUserDetailManager.java

用于实现数据库认证

package com.song.cloud.config;import com.song.cloud.entities.User;
import com.song.cloud.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.stereotype.Component;
import tk.mybatis.mapper.entity.Example;import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;@Component
public class DBUserDetailManager implements UserDetailsManager, UserDetailsPasswordService, Serializable {@Resourceprivate UserMapper userMapper;private Collection<GrantedAuthority> authorities;private DBUserDetailManager(ArrayList<GrantedAuthority> authorities){this.authorities = authorities;}@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {return null;}@Overridepublic void createUser(UserDetails userDetails) {}@Overridepublic void updateUser(UserDetails user) {}@Overridepublic void deleteUser(String username) {}@Overridepublic void changePassword(String oldPassword, String newPassword) {}@Overridepublic boolean userExists(String username) {return false;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {System.out.println("进入:DBUserDetailManager ");//查询数据库根据用户名Example example = new Example(User.class);Example.Criteria criteria = example.createCriteria();criteria.andEqualTo("username", username);User user = userMapper.selectOneByExample(example);authorities.add(() -> "rule");if (user == null) {throw new UsernameNotFoundException(username);}return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPasswordHash(),true,true,true,true,authorities);}
}

3.11 SpringSecurityConfig配置类

配置Spring Security的配置类

package com.song.cloud.config;import com.song.cloud.handler.JwtAuthenticationEntryPoint;
import com.song.cloud.handler.MyAuthenticationEntryPoint;
import com.song.cloud.handler.MyAuthenticationSuccessHandler;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
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.config.Customizer;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
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;@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {@Resourceprivate MyRedisSecurityContextRepository myRedisSecurityContextRepository;@Beanpublic static PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@BeanSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) -> {authorize.requestMatchers("/api/auth/**").permitAll();authorize.anyRequest().authenticated();}).formLogin(Customizer.withDefaults());http.sessionManagement(session->{session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);});http.securityContext(context->{context.securityContextRepository(myRedisSecurityContextRepository);});http.exceptionHandling(exception -> {exception.authenticationEntryPoint(new MyAuthenticationEntryPoint());});http.csrf(AbstractHttpConfigurer::disable);http.cors(Customizer.withDefaults());return http.build();}@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {return configuration.getAuthenticationManager();}
}

3.12 MyAuthenticationEntryPoint

处理认证失败的请求。

package com.song.cloud.handler;import cn.hutool.json.JSONUtil;
import com.song.cloud.resp.ResultData;
import com.song.cloud.resp.ReturnCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;import java.io.IOException;public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {System.out.println("进入:MyAuthenticationEntryPoint");String localizedMessage = authException.getLocalizedMessage();ResultData<Object> fail = ResultData.fail(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), localizedMessage);String jsonStr = JSONUtil.toJsonStr(fail);response.setContentType("application/json;charset=UTF-8");response.getWriter().print(jsonStr);}
}

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

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

相关文章

PGFed: Personalize Each Client’s Global Objective for Federated Learning

ICCV-2023, 文章提出显式隐式的概念,作者通过实验发现显式比隐式的效果好,显式方式通过直接与多个客户的经验风险互动来更新模型,并用泰勒展开式降为 O ( N ) O(N) O(N)通讯成本。 文章地址:arxiv code: 作者开源 贡献 1.我们发现个性化 FL 算法的显式性赋予了其更强的…

【Linux】模拟实现一个简单的日志系统

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

算法体系-20 第二十节暴力递归到动态规划

前言 动态规划模型从尝试暴力递归到傻缓存到动态规划 四种模型和体系班两种模型一共六种模型 0.1 从左往右模型 0.2 范围讨论模型范围尝试模型 &#xff08;这种模型特别在乎讨论开头如何如何 结尾如何如何&#xff09; 玩家博弈问题&#xff0c;玩家玩纸牌只能那左或者右 0.3 …

浅析Vue3 实战笔记(一)

本文是结合实践中和学习技术文章总结出来的笔记(个人使用),如有雷同纯属正常((✿◠‿◠)) 喜欢的话点个赞,谢谢! 有问题欢迎指正!! 前面已经讲了基本的Vue生命周期和入门知识,本篇开始使用Vite构建一个demo 1. 创建项目 1.1. 初始化项目 使用Vite初始化项目 yarn create v…

若依RuoYi-Vue分离版—免登录直接访问

若依RuoYi-Vue分离版—免登录直接访问 如何不登录直接访问前端&#xff1a;后端:方法1&#xff1a;在SecurityConfig.java中设置httpSecurity配置匿名访问方法2&#xff1a;在对应的方法或类上面使用Anonymous注解。 如何不登录直接访问 官网有说明&#xff1a;如何不登录直接…

Swift 序列(Sequence)排序面面俱到 - 从过去到现在(二)

概览 在上篇 Swift 序列(Sequence)排序面面俱到 - 从过去到现在(一)博文中,我们讨论了 Swift 语言中序列和集合元素排序的一些基本知识,我们还给出了以自定义类型中任意属性排序的“康庄大道”。 不过在实际的撸码场景中,我们往往需要的是“多属性”同时参与到排序的考…

279. 完全平方数

解法一、回溯法&#xff1a; class Solution {public int numSquares(int n) {return numSquaresHepler(n);}public int numSquaresHepler(int n){if(n 0) return 0;int count Integer.MAX_VALUE;for(int i 1; i * i < n; i){count Math.min(count,numSquaresHepler(n …

elementPlus 图标不显示 属性模式不显示

问题&#xff1a; elementPlus 属性模式图标不显示 <el-input placeholder"请输入用户名" :suffix-icon"Avatar"> //这个图标不显示 之前在main.ts里全局引入了icons-vue。这里的script里也没引入。 解决&#xff1a; 在当前的script中重新引入a…

【Linux】进程_1

文章目录 五、进程1. 冯---诺依曼体系结构2. 操作系统 未完待续 五、进程 1. 冯—诺依曼体系结构 我们常见的计算机和不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 冯—诺依曼体系结构由&#xff1a;输入设备、输出设备和中央处理器&#xff…

【C++】——继承(详解)

一 继承的定义和概念 1.1 继承的定义 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类&#xff0c;被继承的称为基类…

CentOS7下快速升级至OpenSSH9.7p2安全版本

一、CentOS7服务器上编译生成OpenSSH9.3p2的RPM包 1、编译打包的shell脚本来源于该项目 https://github.com/boypt/openssh-rpms解压zip项目包 unzip openssh-rpms-main.zip -d /opt cd /opt/openssh-rpms-main/ vim pullsrc.sh 修改第23行为source ./version.env 2、sh pull…

C语言,struct 结构体、union共用体的使用

//状态字节&#xff0c;根据数据定义几个标志&#xff0c;标志位依据联合体内部结构体进行变量定义 //目的&#xff0c;节省内存空间&#xff0c;省去特定字节 struct STATDATA {union{unsigned char stat;struct {unsigned stat0:1;unsigned stat1:1;unsigned stat2:1;unsign…

[线程与网络] Java虚拟机常考面试题(线程与网络完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;线程与…

Atlassian企业日技术分享:AI在ITSM中的创新实践与应用、Jira服务管理平台AI功能介绍

2024年5月17日&#xff0c;Atlassian中国合作伙伴企业日活动在上海成功举办。活动以“AI协同 创未来——如何利用人工智能提升团队协作&#xff0c;加速产品交付”为主题&#xff0c;深入探讨了AI技术在团队协作与产品交付中的创新应用与实践&#xff0c;吸引了众多业内专家、企…

光伏项目管理——数字化改革

随着全球对可再生能源的迫切需求以及环保意识的日益增强&#xff0c;光伏产业作为清洁能源的重要组成部分&#xff0c;正迎来快速发展的黄金时期。然而&#xff0c;传统的光伏项目管理方式已逐渐无法满足现代化、高效化的需求&#xff0c;数字化改革成为了行业发展的必然趋势。…

DeepSORT(目标跟踪算法)中卡尔曼滤波器中的更新

DeepSORT&#xff08;目标跟踪算法&#xff09;中卡尔曼滤波器中的更新 flyfish 说协方差先说期望 在协方差的定义中&#xff0c;符号 E \mathbb{E} E 表示期望值&#xff08;Expectation&#xff09;。期望值是随机变量的平均值或均值&#xff0c;表示在大量试验中随机变量…

什么是 URL 过滤?是如何保障浏览体验的?

互联网是一个无边无际的空间&#xff0c;几乎包含了你能想象到的一切。不幸的是&#xff0c;这意味着也存在着从不合适到非常危险的网站。这就是 URL 过滤可以发挥作用的地方。 一、URL 过滤的含义 我们希望您已经熟悉 URL&#xff08;统一资源定位器&#xff09;&#xff0c;…

Java MyBatis实战:QueryWrapper中的and和or拼接技巧

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、引言 在Java Web开发中&#xff0c;MyBatis是一个非常流行的持久层框架。它通过XML或注解的方式将Java对象与数据库表进行映射&#xff0c;从而实现数据的增删改查操作。在使用MyBatis的过程中&#xff0c;经常…

进程和内存管理

描述&#xff1a; 内存的使用和剩余情况当前cpu的负载情况找进程的id结束某个进程 检查内存&#xff1a; 方法一&#xff1a; /proc/meminfo 文件这是一个伪文件这个文件&#xff0c;纪录了内存的相关信息不用用vi打开&#xff0c;应该用cat查看 方法二&#xff1a; 命令…

Qt程序打包成单个exe文件

文章目录 0. 准备工作1. 使用 windeployqt 提取必要的动态链接库和资源文件1.1 操作步骤1.2 补充 2. 使用 Enigma Virtual Box将文件夹打包成单个exe2.1 操作步骤 0. 准备工作 Qt程序打包用到的工具有&#xff1a; windeployqt &#xff1a;安装Qt时自带Enigma Virtual Box 下…