ShardingSphere-Jdbc + Spring Security + Redis 实现简单JWT认证

news2024/11/15 7:59:45

1. 项目结构

2. 数据库相关操作

create database user_profiles;
use user_profiles;
CREATE TABLE `user`
(
    `id`       INT AUTO_INCREMENT PRIMARY KEY,
    `username` VARCHAR(255) NOT NULL UNIQUE,
    `password` VARCHAR(255) NOT NULL,
    `email`    VARCHAR(255) UNIQUE,
    `role`     VARCHAR(255) DEFAULT 'USER',
    `enabled`  BOOLEAN      DEFAULT TRUE
);

src

main

java

org.example
config
LettuceConfig.java
package org.example.config;

import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.StatefulRedisConnection;
import io.lettuce.core.api.sync.RedisCommands;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration  // 标注这是一个配置类
public class LettuceConfig {

    @Bean  // 定义一个 RedisTemplate Bean,用于与 Redis 进行交互
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());  // 设置键的序列化方式
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());  // 设置值的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());  // 设置哈希键的序列化方式
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());  // 设置哈希值的序列化方式
        return template;
    }

    @Value("${redis.host}")  // 从配置文件中注入 Redis 主机地址
    private String redisHost;

    @Value("${redis.port}")  // 从配置文件中注入 Redis 端口号
    private int redisPort;

    @Bean  // 定义一个 RedisClient Bean,用于创建 Redis 客户端
    public RedisClient redisClient() {
        RedisURI redisURI = RedisURI.builder()
                .withHost(redisHost)
                .withPort(redisPort)
                .build();
        return RedisClient.create(redisURI);
    }

    @Bean  // 定义一个 StatefulRedisConnection Bean,用于管理 Redis 连接
    public StatefulRedisConnection<String, String> connection(RedisClient redisClient) {
        return redisClient.connect();
    }

    @Bean  // 定义一个 RedisCommands Bean,用于同步执行 Redis 命令
    public RedisCommands<String, String> redisCommands(StatefulRedisConnection<String, String> connection) {
        return connection.sync();
    }

    @Bean  // 定义一个 RedisConnectionFactory Bean,用于创建 Redis 连接工厂
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }
}

解释:该配置类 LettuceConfig 用于配置与 Redis 相关的各种 Bean,包括 Redis 客户端、连接工厂、连接管理和 Redis 命令执行等 。

SecurityConfig.java
package org.example.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.example.filter.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Configuration  // 标注这是一个配置类
@EnableWebSecurity  // 启用 Spring Security 的 Web 安全支持
public class SecurityConfig {

    @Autowired 
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean  // 定义一个 SecurityFilterChain Bean,用于配置 Spring Security
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF 保护
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/auth/**").permitAll() // 允许公开访问 /auth/** 端点
                        .requestMatchers("/admin/**").hasRole("ADMIN") // 限制 admin 路径只有 ADMIN 角色能访问
                        .requestMatchers("/user/**").hasRole("USER") // 限制 user 路径只有 USER 角色能访问
                        .anyRequest().authenticated()) // 其他请求需要认证
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 使用无状态会话
                .exceptionHandling(exception -> exception
                        .accessDeniedHandler(new AccessDeniedHandler() { // 自定义处理访问被拒绝情况
                            @Override
                            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
                                sendErrorResponse(response, HttpServletResponse.SC_FORBIDDEN, "无权访问该资源");
                            }
                        })
                        .authenticationEntryPoint(new AuthenticationEntryPoint() { // 自定义处理未认证情况
                            @Override
                            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
                                sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "未认证,请先登录");
                            }
                        })) // 使用匿名类处理未认证和越权访问
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 添加 JWT 过滤器
        return http.build();
    }

    // 定义发送错误响应的方法
    private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException {
        response.setStatus(status);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        Map<String, String> errorResponse = new HashMap<>();
        errorResponse.put("error", message);
        response.getWriter().write(new ObjectMapper().writeValueAsString(errorResponse));
    }
}

解释:通过以上配置,SecurityConfig 类确保了应用程序的安全性,支持基于 JWT 的无状态认证,并提供了灵活的请求授权和自定义异常处理机制。

ShardingSphereConfig.java
package org.example.config;

import org.apache.shardingsphere.driver.api.yaml.YamlShardingSphereDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;

@Configuration  // 标注这是一个配置类
public class ShardingSphereConfig {

    @Value("classpath:shardingsphere.yml")  // 从类路径中加载 ShardingSphere 配置文件
    private Resource configResource;

    @Bean  // 定义一个 DataSource Bean,用于配置数据源
    public DataSource dataSource(ResourceLoader resourceLoader) throws SQLException, IOException {
        return YamlShardingSphereDataSourceFactory.createDataSource(configResource.getInputStream().readAllBytes());  // 创建并返回 ShardingSphere 数据源
    }
}

解释:通过以上配置,ShardingSphereConfig 类确保了应用程序可以正确加载 ShardingSphere 配置文件并创建数据源,从而支持分库分表和读写分离等高级数据库操作。

controller
AdminController.java
package org.example.controller;

import org.example.util.RedisJwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController  
@RequestMapping("/admin")  // 映射请求到 "/admin" 路径
public class AdminController {

    @GetMapping("/hi")  // 处理 GET 请求 "/admin/hi"
    public String index() {
        return "HI ADMIN!";  
    }

    @Autowired  
    private RedisJwtUtil redisJwtUtil;

    @PostMapping("/blacklist")  // 处理 POST 请求 "/admin/blacklist"
    public ResponseEntity<String> addToBlacklist(@RequestParam String ipAddress) {
        redisJwtUtil.addToBlacklist(ipAddress);  // 调用工具类方法将 IP 地址加入黑名单
        return ResponseEntity.status(HttpStatus.OK).body("IP地址已加入黑名单");  // 返回成功消息
    }

    @DeleteMapping("/blacklist")  // 处理 DELETE 请求 "/admin/blacklist"
    public ResponseEntity<String> removeFromBlacklist(@RequestParam String ipAddress) {
        redisJwtUtil.removeFromBlacklist(ipAddress);  // 调用工具类方法将 IP 地址从黑名单中移除
        return ResponseEntity.status(HttpStatus.OK).body("IP地址已从黑名单中移除");  // 返回成功消息
    }
}

说明:通过以上配置,AdminController 类提供了管理员的基本操作接口,包括欢迎信息的显示和 IP 地址黑名单的管理。 

AuthController.java
package org.example.controller;

import org.example.entity.User;
import org.example.entity.UserVo;
import org.example.service.UserService;
import org.example.util.RedisJwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController  
@RequestMapping("/auth")  // 映射请求到 "/auth" 路径
public class AuthController {

    @Autowired  
    private AuthenticationManager authenticationManager;

    @Autowired  
    private PasswordEncoder passwordEncoder;

    @Autowired 
    private UserService userService;

    @Autowired  
    private RedisJwtUtil redisJwtUtil;

    @PostMapping("/register")  // 处理 POST 请求 "/auth/register"
    public ResponseEntity<UserVo<?>> register(@RequestBody User user) {
        user.setPassword(passwordEncoder.encode(user.getPassword()));  // 加密用户密码
        try {
            userService.register(user);  // 注册用户
            String token = redisJwtUtil.generateToken(user.getUsername());  // 生成 JWT
            redisJwtUtil.saveToken(user.getUsername(), token);  // 保存 JWT
            Map<String, String> tokenData = new HashMap<>();
            tokenData.put("token", "Bearer " + token);  // 将 JWT 放入响应中
            return ResponseEntity.ok(new UserVo<>(HttpStatus.OK.value(), "Register Success", tokenData));  // 返回成功响应
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body(new UserVo<>(HttpStatus.CONFLICT.value(), "Register failed: Username already exists!", null));  // 返回失败响应
        }
    }

    @PostMapping("/login")  // 处理 POST 请求 "/auth/login"
    public ResponseEntity<UserVo<?>> login(@RequestBody User user) {
        try {
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));  // 进行身份验证
            SecurityContextHolder.getContext().setAuthentication(authentication);  // 设置安全上下文
            String token = redisJwtUtil.generateToken(user.getUsername());  // 生成 JWT
            redisJwtUtil.saveToken(user.getUsername(), token);  // 保存 JWT
            Map<String, String> tokenData = new HashMap<>();
            tokenData.put("token", "Bearer " + token);  // 将 JWT 放入响应中
            return ResponseEntity.ok(new UserVo<>(HttpStatus.OK.value(), "Login Success", tokenData));  // 返回成功响应
        } catch (BadCredentialsException e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new UserVo<>(HttpStatus.UNAUTHORIZED.value(), "Login failed: Invalid username or password", null));  // 返回失败响应
        }
    }

    @PostMapping("/logout")  // 处理 POST 请求 "/auth/logout"
    public ResponseEntity<UserVo<String>> logout(@RequestHeader("Authorization") String header) {
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);  // 提取 JWT
            String username = redisJwtUtil.extractUsername(token);  // 提取用户名
            if (username != null) {
                redisJwtUtil.deleteToken(username);  // 删除 JWT
                userService.evictUserCache(username);  // 清除用户缓存
                SecurityContextHolder.clearContext();  // 清除安全上下文
                return ResponseEntity.ok(new UserVo<>(HttpStatus.OK.value(), "Logout Success", null));  // 返回成功响应
            }
        }
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new UserVo<>(HttpStatus.BAD_REQUEST.value(), "Invalid request", null));  // 返回失败响应
    }
}

解释:通过以上配置,AuthController 类提供了用户注册、登录和注销的接口,支持基于 JWT 的无状态认证,并管理用户的认证状态和缓存。 

UserController.java
package org.example.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController  
@RequestMapping("/user")  
public class UserController {

    @GetMapping("/hi")  
    public String hi() {
        return "HI USER!";  
    }
}

解释:通过以上配置,UserController 类提供了一个简单的用户接口,响应特定路径的 GET 请求并返回一条欢迎信息。

entity
User.java
package org.example.entity;

import lombok.Data;

@Data
public class User {
    private Integer id;
    private String username;
    private String password;
    private String email;
    private String role;
    private Boolean enabled;
}
 UserVo.java
package org.example.entity;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class UserVo<T> {
    private int status;
    private String message;
    private T data;
}
filter 
JwtAuthenticationFilter.java
package org.example.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.util.RedisJwtUtil;
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.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisJwtUtil redisJwtUtil;

    private final ObjectMapper objectMapper = new ObjectMapper();

    private void sendErrorResponse(HttpServletResponse response, int status, String message) throws IOException {
        response.setStatus(status);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");

        Map<String, String> errorResponse = new HashMap<>();
        errorResponse.put("error", message);

        response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException {

        // 获取客户端IP地址
        String clientIP = request.getRemoteAddr();
        String path = request.getRequestURI();

        try {
            // 检查IP是否在黑名单中
            if (redisJwtUtil.isBlacklisted(clientIP)) {
                sendErrorResponse(response, HttpServletResponse.SC_FORBIDDEN, "该IP地址已被禁止访问");
                return;
            }

            // 检查IP频率限制
            if (redisJwtUtil.isRateLimited(clientIP, path)) {
                sendErrorResponse(response, 429, "请求过多,请稍后再试");
                return;
            }

            // 从请求头中获取 Authorization 字段
            String header = request.getHeader("Authorization");
            String token = null;
            String username = null;

            // JWT Token的形式为"Bearer token",移除 Bearer 单词,只获取 Token 部分
            if (header != null && header.startsWith("Bearer ")) {
                token = header.substring(7);
                try {
                    // 从 Token 中提取用户名
                    username = redisJwtUtil.extractUsername(token);
                } catch (Exception e) {
                    // 无效的 JWT Token 或无法解析
                    sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "未认证,请先登录!");
                    return;
                }
            }

            // 获取到Token后,进行验证
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                // 验证 Token 是否在 Redis 中存在且有效
                if (redisJwtUtil.redisValidate(token)) {
                    // 根据用户名加载用户详情
                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                    if (userDetails != null) {
                        // 如果 Token 有效,配置 Spring Security 手动设置认证
                        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());
                        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                        // 在设置 Authentication 之后,指定当前用户已认证
                        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    } else {
                        sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "未认证,请先登录!");
                        return;
                    }
                } else {
                    sendErrorResponse(response, HttpServletResponse.SC_UNAUTHORIZED, "未认证,请先登录!");
                    return;
                }
            }

            // 如果没有token或token无效,将请求传递到过滤器链的下一个过滤器
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            // 捕获所有异常,并发送错误响应
            sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "服务器内部错误");
        }
    }
}

解释:JwtAuthenticationFilter 类是一个自定义的 Spring Security 过滤器,用于对每个请求进行 JWT 验证和处理。该过滤器继承了 OncePerRequestFilter,保证在每个请求过程中只调用一IP。 

       1. 黑名单检查:确保在黑名单中的 IP 无法访问。
        2. IP 请求频率限制:防止单个 IP 频繁请求导致服务器过载。
        3. JWT 验证:从请求头中提取 JWT,验证其有效性,确保用户已认证。
        4. 错误处理:捕获并处理所有异常,返回适当的错误响应。
该过滤器确保每个请求都经过严格的 IP 检查和 JWT 验证,提升了应用的安全性和稳定性。 

mapper 
UserMapper.java
package org.example.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;
import org.example.entity.User;


@Mapper
public interface UserMapper {
    @Insert("INSERT INTO user(username, password, email, role, enabled) VALUES(#{username}, #{password}, #{email}, #{role}, #{enabled})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void register(User user);

    @Select("SELECT * FROM user WHERE username = #{username} ")
    User findByUsername(String username);
}
service 
UserService.java
package org.example.service;

import org.example.entity.User;
import org.example.mapper.UserMapper;
import org.example.util.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.time.Duration;
import java.util.Collections;

@Service
public class UserService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Value("${aes.key}")  // 从配置文件中读取AES密钥
    private String aesKey;


    private  final Duration CACHE_EXPIRATION = Duration.ofMinutes(3);  // 设置缓存过期时间3分钟

    private static final String USER_CACHE_KEY = "userCache:";

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = null;
        try {
            user = findByUsername(username);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + user.getRole());
        return org.springframework.security.core.userdetails.User
                .withUsername(user.getUsername())
                .password(user.getPassword())
                .authorities(Collections.singletonList(authority))
                .build();
    }

    public void register(User user) throws Exception {
        if (userMapper.findByUsername(user.getUsername()) != null) {
            throw new Exception("User already exists!");
        }
        userMapper.register(user);
        encryptUser(user);  // 加密用户数据
        cacheUser(user);  // 缓存加密后的用户数据
        decryptUser(user); //解密
    }

    public User findByUsername(String username) throws Exception {
        String encryptedUsername = AESUtil.encrypt(username, aesKey);
        User user = (User) redisTemplate.opsForValue().get(USER_CACHE_KEY + encryptedUsername);
        if (user != null) {
            decryptUser(user);  // 解密用户数据
            return user;
        }
        user = userMapper.findByUsername(username);
        if (user != null) {
            encryptUser(user);
            cacheUser(user);  // 缓存加密后的用户数据
            decryptUser(user);  // 解密用户数据
        }
        return user;
    }

    public void evictUserCache(String username) {
        try {
            String encryptedUsername = AESUtil.encrypt(username, aesKey);
            redisTemplate.delete(USER_CACHE_KEY + encryptedUsername);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void encryptUser(User user) throws Exception {
        user.setUsername(AESUtil.encrypt(user.getUsername(), aesKey));
        user.setEmail(AESUtil.encrypt(user.getEmail(), aesKey));
        user.setPassword(AESUtil.encrypt(user.getPassword(), aesKey));
        user.setRole(AESUtil.encrypt(user.getRole(), aesKey));
    }

    private void decryptUser(User user) throws Exception {
        user.setUsername(AESUtil.decrypt(user.getUsername(), aesKey));
        user.setEmail(AESUtil.decrypt(user.getEmail(), aesKey));
        user.setPassword(AESUtil.decrypt(user.getPassword(), aesKey));
        user.setRole(AESUtil.decrypt(user.getRole(), aesKey));
    }

    private void cacheUser(User user) {
        try {
            redisTemplate.opsForValue().set(USER_CACHE_KEY + user.getUsername(), user, CACHE_EXPIRATION);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

解释: UserService 类是一个服务类,实现了 UserDetailsService 接口,负责用户相关的业务逻辑,包括用户注册、查找和缓存管理。

util
AESUtil.java
package org.example.util;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AESUtil {

    private static final String ALGORITHM = "AES";
    /**
     * 生成一个新的AES密钥
     * @return Base64编码的密钥字符串
     * @throws Exception
     */
    public static String generateKey() throws Exception {
        // 使用AES算法生成密钥
        KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
        keyGen.init(256); // 使用256位密钥
        SecretKey secretKey = keyGen.generateKey();
        return Base64.getEncoder().encodeToString(secretKey.getEncoded());
    }

    /**
     * 使用给定的密钥加密字符串
     *
     * @param data 要加密的数据
     * @param key  Base64编码的密钥字符串
     * @return 加密后的Base64编码字符串
     * @throws Exception
     */
    public static String encrypt(String data, String key) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] encryptedData = cipher.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(encryptedData);
    }

    /**
     * 使用给定的密钥解密字符串
     *
     * @param encryptedData 加密后的Base64编码字符串
     * @param key           Base64编码的密钥字符串
     * @return 解密后的原始字符串
     * @throws Exception
     */
    public static String decrypt(String encryptedData, String key) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(key), ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        byte[] decryptedData = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
        return new String(decryptedData);
    }
    public static void main(String[] args) {
        try {
            // 生成一个256位的AES密钥
            String aesKey = generateKey();
            System.out.println("生成的256位AES密钥: " + aesKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解释:AESUtil 类是一个实用工具类,用于生成 AES 密钥以及使用 AES 算法加密和解密字符串。 

RedisJwtUtil.java
package org.example.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import io.lettuce.core.api.sync.RedisCommands;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

@Component  // 标注这是一个 Spring 组件
public class RedisJwtUtil {

    private static final Logger logger = LoggerFactory.getLogger(RedisJwtUtil.class);

    private final String secretKey;
    private final long expirationTime;
    private final RedisCommands<String, String> redisCommands;
    private final int maxRequests;
    private final long timeWindow;

    private static final String BLACKLIST_KEY_PREFIX = "blacklist_"; // 黑名单标识前缀

    public RedisJwtUtil(@Value("${jwt.secret_key}") String secretKey,
                        @Value("${jwt.expire_time}") long expirationTime,
                        @Value("${jwt.max_requests}") int maxRequests,
                        @Value("${jwt.time_window}") int timeWindow,
                        RedisCommands<String, String> redisCommands) {
        this.secretKey = secretKey;
        this.expirationTime = expirationTime;
        this.maxRequests = maxRequests;
        this.timeWindow = timeWindow;
        this.redisCommands = redisCommands;
    }

    /**
     * 生成 JWT
     *
     * @param username 用户名
     * @return 生成的 JWT
     */
    public String generateToken(String username) {
        Date issuedAt = new Date();
        Date expiresAt = new Date(issuedAt.getTime() + expirationTime); // 设置过期时间

        return JWT.create()
                .withSubject(username)
                .withIssuedAt(issuedAt)
                .withExpiresAt(expiresAt)
                .sign(Algorithm.HMAC256(secretKey));
    }

    /**
     * 验证 JWT
     *
     * @param token JWT 字符串
     * @return 如果有效则返回 true,否则返回 false
     */
    public boolean validateToken(String token) {
        try {
            String username = extractUsername(token);
            if (username == null) {
                return false;
            }
            Algorithm algorithm = Algorithm.HMAC256(secretKey);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withSubject(username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return !isTokenExpired(jwt);
        } catch (JWTVerificationException exception) {
            logger.error("JWT Verification failed", exception);
            return false;
        }
    }

    /**
     * 从 JWT 中提取用户名
     *
     * @param token JWT 字符串
     * @return 提取的用户名
     */
    public String extractUsername(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getSubject();
        } catch (JWTVerificationException exception) {
            logger.error("Error decoding JWT", exception);
            return null;
        }
    }

    /**
     * 检查 JWT 是否已过期
     *
     * @param jwt 解码后的 JWT 对象
     * @return 如果过期则返回 true,否则返回 false
     */
    private boolean isTokenExpired(DecodedJWT jwt) {
        return jwt.getExpiresAt().before(new Date());
    }

    /**
     * 将 JWT 保存到 Redis
     *
     * @param username 用户名
     * @param token JWT 字符串
     */
    public void saveToken(String username, String token) {
        try {
            redisCommands.setex(username, expirationTime / 1000, token); // 使用 setex 方法设置过期时间,单位为秒
            logger.info("Token saved for user: {}", username);
        } catch (Exception e) {
            logger.error("Error saving token to Redis", e);
        }
    }

    /**
     * 验证 Redis 中的 JWT
     *
     * @param token JWT 字符串
     * @return 如果有效则返回 true,否则返回 false
     */
    public boolean redisValidate(String token) {
        try {
            String username = extractUsername(token);
            if (username == null) {
                return false;
            }
            String redisToken = redisCommands.get(username);
            return token.equals(redisToken) && validateToken(redisToken);
        } catch (Exception e) {
            logger.error("Error validating token with Redis", e);
            return false;
        }
    }

    /**
     * 从 Redis 中删除 JWT
     *
     * @param username 用户名
     */
    public void deleteToken(String username) {
        try {
            redisCommands.del(username);
            logger.info("Token deleted for user: {}", username);
        } catch (Exception e) {
            logger.error("Error deleting token from Redis", e);
        }
    }

    /**
     * 检查 IP 地址的请求频率
     *
     * @param ipAddress 客户端 IP 地址
     * @param path 请求路径
     * @return 如果频率受限则返回 true,否则返回 false
     */
    public boolean isRateLimited(String ipAddress, String path) {
        try {
            String key = "req_count_" + ipAddress + "_" + path;
            Integer currentCount = redisCommands.get(key) != null ? Integer.parseInt(redisCommands.get(key)) : null;

            if (currentCount == null) {
                redisCommands.setex(key, timeWindow * 60, String.valueOf(1)); // 以秒为单位设置过期时间
                return false;
            } else if (currentCount < maxRequests) {
                redisCommands.incr(key);
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
            logger.error("Error checking rate limit", e);
            return true;
        }
    }

    /**
     * 将 IP 地址添加到黑名单
     *
     * @param ipAddress IP 地址
     */
    public void addToBlacklist(String ipAddress) {
        try {
            redisCommands.set(BLACKLIST_KEY_PREFIX + ipAddress, "true");
            logger.info("IP added to blacklist: {}", ipAddress);
        } catch (Exception e) {
            logger.error("Error adding IP to blacklist", e);
        }
    }

    /**
     * 将 IP 地址从黑名单中移除
     *
     * @param ipAddress IP 地址
     */
    public void removeFromBlacklist(String ipAddress) {
        try {
            redisCommands.del(BLACKLIST_KEY_PREFIX + ipAddress);
            logger.info("IP removed from blacklist: {}", ipAddress);
        } catch (Exception e) {
            logger.error("Error removing IP from blacklist", e);
        }
    }

    /**
     * 检查 IP 地址是否在黑名单中
     *
     * @param ipAddress IP 地址
     * @return 如果在黑名单中则返回 true,否则返回 false
     */
    public boolean isBlacklisted(String ipAddress) {
        try {
            return "true".equals(redisCommands.get(BLACKLIST_KEY_PREFIX + ipAddress));
        } catch (Exception e) {
            logger.error("Error checking if IP is blacklisted", e);
            return false;
        }
    }
}

解释:RedisJwtUtil 类是一个实用工具类,主要用于生成、验证、保存和管理 JWT,同时支持基于 Redis 的 IP 黑名单和请求频率限制。

SpringJwtApplication.java
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootApplication
public class SpringJwtApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringJwtApplication.class, args);
    }
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

resources

shardingsphere.yml
databaseName: virtual_database
dataSources:
  master:
    dataSourceClassName: com.zaxxer.hikari.HikariDataSource
    url: jdbc:mysql://127.0.0.1:3306/user_profiles?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: 123456
    connectionTimeoutMilliseconds: 30000
    idleTimeoutMilliseconds: 60000
    maxLifetimeMilliseconds: 1800000
    maxPoolSize: 50
    minPoolSize: 1
rules:
  - !ENCRYPT
    encryptors:
      aes_encryptor:
        type: AES
        props:
          aes-key-value: 123456abc
    tables:
      user:
        columns:
          password:
            cipher:
              name: password
              encryptorName: aes_encryptor
          username:
            cipher:
              name: username
              encryptorName: aes_encryptor
props:
  sql-show: true

解释:该配置文件用于设置一个虚拟数据库,配置连接池参数,并定义了对用户表中usernamepassword列的AES加密规则,同时开启了SQL语句的显示功能。  

application.yml
spring:
  application:
    name: spring_jwt  # 应用名称,设置为 spring_jwt
  cache:
    type: redis  # 缓存类型,设置为 redis,使用 Redis 作为缓存机制
redis:
  host: 192.168.186.77  # Redis 服务器的主机地址
  port: 6379  # Redis 服务器的端口号
jwt:
  secret_key: abc123  # JWT 的密钥,用于签名和验证 JWT
  expire_time: 180000  # JWT 的过期时间,单位为毫秒,设置为 180000 毫秒(3 分钟)
  max_requests: 5  # 在 time_window 内允许的最大请求次数
  time_window: 1  # 限制请求次数的时间窗口,单位为分钟,设置为 1 分钟
aes:
  key: H9ylG13Otn6ZRC0LhMy+cyu5TJzU4sT2LPAFJjRJt9Q=  # AES 加密密钥,Base64 编码的密钥
logging:
  level:
    root: debug  # 日志级别,设置为 debug,记录详细的调试信息

解释:该配置文件用于配置 Spring 应用,包括应用名称、Redis 缓存、JWT 验证、AES 加密和日志级别设置。具体来说,它设置了 Redis 作为缓存机制,配置了 Redis 服务器的连接信息,定义了 JWT 的密钥和相关参数,指定了 AES 加密的密钥,并将日志级别设置为 debug 以便于调试。 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>spring_jwt</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring_jwt</name>
    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>

        <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.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.16</version>
        </dependency>


        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc</artifactId>
            <version>5.5.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.shardingsphere</groupId>
                    <artifactId>shardingsphere-test-util</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/io.lettuce/lettuce-core -->
        <dependency>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
            <version>6.3.0.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on -->
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3. 测试验证 

3.1 请求路径

AdminController
GET /admin/hi
POST /admin/blacklist
DELETE /admin/blacklist
AuthController
POST /auth/register
POST /auth/login
POST /auth/logout
UserController
GET /user/hi

3.2  POST /auth/register(注册)

{
  "role":"ADMIN",  
  "username":"admin",
  "password": "123456",
  "email": "12345678@qq.com",
  "enabled": true
}

说明:注册成功会生成token,同时存储到redis中,本例设置3分钟过期,读者可以自行修改有效时间便于测试。 

3.3 POST /auth/login(登录)

3.3  GET /admin/hi (带权访问)

3.3.1 未携带token请求

3.3.2 携带token请求

3.4 POST /admin/blacklist (加入黑名单)

        再次请求,IP被禁止访问 

 3.5 DELETE /admin/blacklist(移出黑名单)

再次访问 

3.6 POST /auth/logout (退出)

 

        再次访问

3.7 GET /user/hi

3.7.1 注册一个普通用户
{
  "role":"USER",  
  "username":"guest",
  "password": "123456",
  "email": "123456789@qq.com",
  "enabled": true
}

 3.7.2 访问user/hi

  3.8 不同角色访问

说明:使用普通用户的token对admin/hi进行访问。

 3.9 频繁请求校验

说明:我设置了一分钟内,一个IP的同一个路径只能请求5次超过了,就限制访问。 

3.10 数据库的数据

说明:username只通过shardingsphere的加密规则加密一次;password先通过passwordEncoder加密一次,再通过shardingsphere的加密规则再加密一次总共加密2次;缓存用户信息的时候又通过AES对用户名密码邮箱进行加密和解密。

4. 总结

        实现简单的jwt令牌验证,先禁用CSRF,只是简单的结合Redis进行缓存和有效期验证。如果 JWT(JSON Web Token)泄露了,任何持有该令牌的人都可以冒充令牌所有者发起请求,带来安全风险。

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

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

相关文章

数据结构之《二叉树》(上)

在之前的数据结构的学习中&#xff0c;我们了解了顺序表、链表等线性表&#xff0c;接下来在本篇中将要学习一种非线性的数据结构——树&#xff0c;我们将来了解树的相关概念和性质&#xff0c;在树当中将重点学习二叉树的结构和特性。学习完相关概念后将试着实现二叉树&#…

我是怎么解决一个电力采集问题的全过程分享

行业设备联网&#xff0c;没人开发/开发太慢/投入太大 怎么办&#xff1f;用合宙DTU整体解决方案&#xff01; 一、整体解决方案内容 合宙DTU整体解决方案 DTU硬件&固件SIM卡业务云平台APP&小程序&web h5页面看板&#xff1b; 合宙提供的DTU整体解决方案&#x…

免费【2024】springboot 宠物中心信息管理系统app

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

【超级宠物】-对战游戏

【超级宠物】是一款轻松好玩的休闲趣味卡通宠物模拟养成游戏,游戏中玩家需要不断的训练你的小宠物以参加比赛,你将会是一名专业的训宠物大师,成功的培养自己的宠物成为宠物之星。 超级宠物是一款充满趣味的休闲趣味宠物养成模拟竞技游戏&#xff0c;游戏中玩家需要培养你的狗狗…

Ollama0.3.0 + llama3.1本地部署

Ollama0.3.0 llama3.1本地部署 安装Ollama 官网下载安装包 https://ollama.com/download ​​ 根据系统选择对应版本 安装完成后&#xff0c;电脑右下角会出现ollama程序图标&#xff0c;表示ollama正在运行。 ​​ 打开cmd命令 下载Llama3.1 输入ollama&#xff0c…

51单片机嵌入式开发:22、STC89C52R控制 实现单总线温度传感器DS18b20的温度读取

STC89C52R控制 实现单总线温度传感器DS18b20的温度读取 1 概述1.1 介绍1.2 特点1.3 应用领域 2 DS18B20原理详解2.1 内部机理2.2 读写时序2.3 DS18B20操作代码程序 3 演示4 总结 配套演示例程 1 概述 DS18B20是一款数字温度传感器&#xff0c;由Maxim Integrated&#xff08;美…

linux下frambuffer的使用

什么是Framebuffer&#xff1f; Framebuffer是一个内存区域&#xff0c;操作系统可以通过它直接控制显示设备的像素。与传统的图形加速硬件不同&#xff0c;framebuffer不依赖于图形处理器&#xff0c;而是通过CPU来处理图形数据。这种方式虽然在性能上可能不如硬件加速&#…

vegecad画线及调整功能

vegecad添加了绘制直线和层颜色&#xff0c;目前没有线型&#xff0c;点击工具按钮"画线"&#xff0c;画线是连续的画&#xff0c;右键结束&#xff0c;下面是画的 vegetable&#xff0c;目前画线没有捕捉和引导线画特定角度的线&#xff0c;所以现在还是涂鸦式的&am…

昇思25天学习打卡营第XX天|RNN实现情感分类

希望代码能维持开源维护状态hhh&#xff0c;要是再文件整理下就更好了&#xff0c;现在好乱&#xff0c;不能好fork tutorials/application/source_zh_cn/nlp/sentiment_analysis.ipynb MindSpore/docs - Gitee.com

Redis架构之主从复制

Redis主从架构 主从集群&#xff0c;实现读写分离 主从数据同步 主从同步流程 slave节点向master节点发送replicaof命令建立连接&#xff0c;随后发送 psync {repID} {offset} 指令&#xff0c;repID表示主节点唯一标识&#xff0c;offset为复制偏移量。如果是第一次同步&…

软件测试——Bug篇

什么是BUG 定义&#xff1a; ⼀个计算机bug指在计算机程序中存在的⼀个错误(error)、缺陷(flaw)、疏忽(mistake)或者故障(fault)&#xff0c;这些bug使程序⽆法正确的运⾏。Bug产⽣于程序的源代码或者程序设计阶段的疏忽或者错误。 准确的来说&#xff1a; 1. 当且仅当规格说…

Java面试——Tomcat

优质博文&#xff1a;IT_BLOG_CN 一、Tomcat 顶层架构 Tomcat中最顶层的容器是Server&#xff0c;代表着整个服务器&#xff0c;从上图中可以看出&#xff0c;一个Server可以包含至少一个Service&#xff0c;用于具体提供服务。Service主要包含两个部分&#xff1a;Connector和…

【C语言】“void” 如何在 C 语言中开创无限可能

目录 C语言void关键字详解void关键字的功能和应用详细说明 1. void作为函数返回类型示例代码输出结果 2. void作为函数参数示例代码输出结果 3. void*通用指针示例代码输出结果 4. void与NULL示例代码输出结果 5. void与函数指针示例代码输出结果 6. void的限制和注意事项6.1 不…

AI技术革命对未来就业的影响

一、引言 AI技术革命带来的就业结构变化是双面的&#xff0c;既存在着挑战也蕴含着机遇。过去的几次技术革命例如工业革命和信息技术革命都对就业结构产生了深刻的影响&#xff0c;使一些工作消失&#xff0c;也催生了许多新的工作类型。当前和未来的AI技术发展也必将推动类似…

Vue3 + js-echarts 实现前端大屏可视化

1、前言 此文章作为本人大屏可视化项目的入门学习笔记&#xff0c;以此作为记录&#xff0c;记录一下我的大屏适配解决方案&#xff0c;本项目是基于vite Vue3 js less 实现的&#xff0c;首先看ui&#xff0c;ui是网上随便找的&#xff0c;代码是自己实现的&#xff0c;后面…

昇思25天学习打卡营第XX天|基于MindSpore通过GPT实现情感分类

其实数据集和模型的其他大平台接口的&#xff0c;感觉不用非包在自己包里 %env HF_ENDPOINThttps://hf-mirror.com mindnlp.transformers 库中的 GPTTokenizer 类来加载和处理与GPT&#xff08;生成式预训练变换器&#xff09;模型兼容的分词器&#xff0c;并添加特殊的控制标…

Vmware创建centos后使用yum报错(网络连接正常)

安装完centos虚拟机后&#xff0c;yum报错信息如下&#xff1a; 快速解决方法 删除原有yum文件 rm -f /etc/yum.repos.d/CentOS-Base.repo 然后重新下载阿里的&#xff1a; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 清理…

ctfshow-web入门-sql注入(web171-web175)

目录 1、web171 2、web172 3、web173 4、web174 5、web175 1、web171 单引号测一下&#xff0c;报错 -- 闭合后回显正常 也可以用 # &#xff0c;不过需要 URL 编码 成功闭合之后&#xff0c;先判断下字段数&#xff1a; 1 order by 3-- 3 的时候正常 4 的时候报错&am…

MyBatis 动态代理和映射器

一、映射器简介 1.什么是mapper动态代理? 在接口中有方法的返回值定义&#xff0c;参数的定义&#xff0c;方法名&#xff0c;在sqlMapper.xml 中也对应这接口给予了赋值&#xff0c; 这时候dao的实现类就显得多余&#xff0c;这是Mybatis可以帮助我们自动产生实现类&#xf…

H5+JS 4096小游戏

主要实现 1.使用WASD或方向按钮控制游戏 2.最高值4096&#xff0c;玩到4096视为胜利 3.随机生成2、4、8方块 4.移动方块 5.合并方块 JS代码干了什么 初始化游戏界面&#xff1a;创建游戏板和控制按钮。 定义游戏相关变量&#xff1a;如棋盘大小、棋盘状态、得分等。 初始化棋…