Spring Security登录验证

 一、前言

一般的AOP切面+JWT验证缺少一定的安全性,因此需要Spring Security进行进一步的安全保护。

该登录验证使用了JWT、AOP、Redis、MyBatis-Plus。

(为了方便使用,直接贴代码)

二、实现功能

(1)登录的账户密码验证

(2)访问其他接口时判断当前用户是否登录

(3)错误信息会返回给前端(登录错误信息)(token验证失败信息)

(4)数据库密码加密

三、Maven

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jwt依赖-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>
<!--redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatisplus依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
<!--mysql依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
    <scope>provided</scope>
</dependency>
<!--Spring Security依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

四、代码

service包

UserDetailsServiceImpl类

实现Spring Security自带的UserDetailsService接口

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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;

/**
 * @Author 幻现
 * @Date 2025/5/5 下午10:42
 **/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        System.out.println("登录中... username:" + username);

        // 根据用户名username查询用户信息
        LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();

        qw.eq(User::getUsername, username);

        //查询用户账号信息
        User user = userMapper.selectOne(qw);

        if(user == null) {
            throw new UsernameNotFoundException("该用户名不存在!!!");
        }

        LoginUser loginUser = new LoginUser();

        // 把从数据库查询到的账号、密码信息提供给SpringSecurity的登录用户信息类
        loginUser.setUser(user);

        return loginUser;
    }
}

 UserService接口

import com.baomidou.mybatisplus.extension.service.IService;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author 幻现
 * @since 2025-04-07
 */
public interface UserService extends IService<User> {
    String createToken(String username, String password);
}

 UserServiceImpl类

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 幻现
 * @since 2025-04-07
 */
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public String createToken(String username, String password) {
        try{
            // 触发Spring Security认证流程
            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(username, password)
            );
            System.out.println("认证成功");

            //认证成功 -> 获取登录者信息(存储到LoginUser对象)
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();

            String key = "token:" + UUID.randomUUID();

            Map<String,Object> map = new HashMap<>();
            map.put("userId",loginUser.getUser().getUserId());
            map.put("radisKey",key);

            //认证成功后生成Token
            String token = JWTUtil.createToken(map);

            // 把登录者信息对象LoginUser缓存到Redis中,用于后续根据token来获取对应的信息
            redisUtil.setCacheObject(key,loginUser);

            return token;
        }catch (AuthenticationException e) {
            // 抛出特定异常,由全局处理器捕获
            throw new AuthenticationServiceException(e.getMessage(), e);
        }
    }
}

 entity包

LoginUser类

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * @Author 幻现
 * @Date 2025/4/16 上午9:25
 *
 **/

@Data
// 解决后续redis读取数据时反序列化报错
// 会使用LoginUser进行数据存储
@JsonIgnoreProperties(ignoreUnknown = true)
public class LoginUser implements UserDetails {

    private User user;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    //账号和密码均由UserDetailsServiceImpl返回
    @Override
    public String getPassword() {
        // 获取查询到的密码(必须为加密后的密码,否则会报没有密码解析器错误(可以使用:{noop}+未加密密码:来逃避))
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        // 获取查询到的用户名
        return user.getUsername();
    }

    // 检查用户账户是否未过期 -> 账户过期无法使用,需要重新激活当前账户,通常可以基于数据库中一些日期相关的字段
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    // 检查用户的账户是否未被锁定 -> 作为一种安全机制,防止用户通过极端方式暴力破解(尝试大量密码组合)来访问系统
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    // 用户检查用户的凭证是否未过期 -> 也是一种安全机制,通常用于强制用户定期更改密码
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    // 用户检查用户的账户是否已经启用(激活) -> 通常检查数据库中的设计的某些字段(如:is_active/enabled)
    @Override
    public boolean isEnabled() {
        return true;
    }
}

 User类

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * <p>
 * 
 * </p>
 *
 * @author 幻现
 * @since 2025-04-07
 */
@Getter
@Setter
@Accessors(chain = true)
@TableName("user")
@ApiModel(value = "User对象", description = "")
@Data
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "user_id", type = IdType.AUTO)
    private Integer userId;

    @TableField("username")
    private String username;

    @TableField("password")
    private String password;

    @TableField("sex")
    private String sex;

    @TableField("address")
    private String address;

    @TableField("phone")
    private String phone;

    @TableField("user_image")
    private String userImage;
}

  LoginVo类

import lombok.Data;

/**
 * @Author 幻现
 * @Date 2025/5/5 下午10:37
 **/
@Data
public class LoginVo {

    private String username;

    private String password;

}

 filter包(AOP切面过滤器)

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author 幻现
 * @Date 2025/5/5 下午10:14
 **/
@Component
public class JWTFilter extends OncePerRequestFilter {

    @Autowired
    private RedisUtil redisUtil;

    //返回格式
    private final ObjectMapper objectMapper = new ObjectMapper();

    //放行登录接口,避免干扰认证流程
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return request.getServletPath().equals("/user/login");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("JWT拦截中。。。");

        try {
            // 从请求头中获取填入的token
            String token = request.getHeader("token");

            if (token == null) {
                // 直接放行即可,因为SpringSecurity本身也有做过滤器校验
                filterChain.doFilter(request, response);
                return;
            }

            // 验证Token合法性(可能抛出JWTValidationException)
            JWTUtil.verifyToken(token);

            //从token中获取解析
            String key = JWTUtil.getClaimValue(token, "radisKey").toString();
            System.out.println("Redis Key: " + key);

            // 从Redis获取用户信息
            LoginUser loginUser = redisUtil.getCacheObject(key);
            System.out.println(loginUser);
            System.out.println("--------------------");
            if (loginUser == null) {
                throw new JWTValidationException("用户会话已过期", 401);
            }

            // 构建认证对象
            // SpringSecurity设置了认证状态为true,容器中有缓存的用户信息,Security就会放行
            // 需要借助Redis缓存用户对象
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    loginUser, null, loginUser.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);

            // 校验通过,放行
            filterChain.doFilter(request, response);
        } catch (JWTValidationException e) {
            // 处理JWT相关错误
            handleJwtError(response, e);
        } catch (Exception e) {
            // 处理其他未捕获异常
            handleGenericError(response, e);
        }
    }
    /**
     * 处理JWT验证失败错误(HTTP 401/400)
     */
    private void handleJwtError(HttpServletResponse response,
                                JWTValidationException e) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(e.getStatusCode());

        M errorResponse = M.error(e.getMessage());
        errorResponse.setCode(e.getStatusCode());

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

    /**
     * 处理其他未捕获异常(HTTP 500)
     */
    private void handleGenericError(HttpServletResponse response,
                                    Exception e) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

        M errorResponse = M.error("系统内部错误: " + e.getMessage());
        errorResponse.setCode(500);

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

exception包(自定义异常)

/**
 * @Author 幻现
 * @Date 2025/5/6 下午3:52
 **/
public class JWTValidationException extends RuntimeException {
    private final int statusCode;

    public JWTValidationException(String message, int statusCode) {
        super(message);
        this.statusCode = statusCode;
    }

    public int getStatusCode() {
        return statusCode;
    }
}

handler包(捕获异常)

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author 幻现
 * @Date 2025/5/6 下午3:53
 **/
@RestControllerAdvice
public class GlobalExceptionHandler {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @ExceptionHandler(JWTValidationException.class)
    public void handleJWTValidationException(JWTValidationException e,
                                             HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(e.getStatusCode());
        M errorResponse = M.error(e.getMessage());
        errorResponse.setCode(e.getStatusCode());
        response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
    }
}

config包(配置)

SecurityConfig类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.configuration.WebSecurityConfigurerAdapter;
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.authentication.UsernamePasswordAuthenticationFilter;

import java.util.Arrays;

/**
 * @Author 幻现
 * @Date 2025/5/5 下午10:13
 **/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JWTFilter jwtFilter;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();// 必须与登录验证的编码器一致
    }

    // 弃用SpringSecurity原本的登录页面(登录时会自动调用) -> 改用后端实现接口的形式提供登录接口,前端设计新登录页面
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 禁用CSRF和CORS
                .csrf().disable()
                .cors().and()

                // 会话管理配置
                .sessionManagement()
                //不通过Session获取SecurityContext
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()

                // 请求权限配置
                .authorizeRequests()
                .antMatchers(
                        "/user/login"
                ).permitAll()  // 登录接口放行
                //除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()

                // 将自定义的JWT过滤器添加到SpringSecurity过滤器之前
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        provider.setHideUserNotFoundExceptions(false); // 关键配置
        return provider;
    }
}

 RedisConfig类

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @Author 幻现
 * @Date 2025/5/5 下午10:20
 **/
@Configuration
public class RedisConfig {
    // 定义了一个RedisTemplate
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // RedisTemplate 为了自己方便一般直接使用<String,Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        // 序列化配置
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 设置可见度
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 启动默认的类型
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        // 序列化类,对象映射设置
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key采用String的序列化
        template.setHashKeySerializer(stringRedisSerializer);
        // value采用jackson的序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value采用jackson的序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }
}

controller包

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author 幻现
 * @since 2025-04-07
 */
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public M login(@RequestBody LoginVo loginVo) {
        try {
            System.out.println("用户登录信息");
            System.out.println(loginVo);

            if (loginVo == null) return M.loginResError("账号或密码为空");
            String token = userService.createToken(loginVo.getUsername(), loginVo.getPassword());

            String userId = (String) JWTUtil.getClaimValue(token, "userId");
            QueryWrapper<User> qw = new QueryWrapper<>();
            qw.eq("user_id", userId);
            User user = userService.getOne(qw);

            return M.loginResSuccess(token, user.getUserId(), user.getUsername());
        }catch (AuthenticationServiceException e) {
            // 解析底层异常类型(如 UsernameNotFoundException/BadCredentialsException)
            Throwable rootCause = e.getCause();
            String errorMsg = (rootCause instanceof UsernameNotFoundException)
                    ? "用户名不存在"
                    : (rootCause instanceof BadCredentialsException)
                    ? "密码错误" : "登录失败";
            return M.loginResError(errorMsg); // 返回标准错误响应
        }
    }
}

utils包(工具集合)

JWTUtil类

用于token的生成和验证

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
import java.util.Set;

public class JWTUtil {
    public static final String secretKey ="lztyscdrj@!!!";//秘钥
    private static final Integer EXPIRATION_MS = 60*60*24*1;//时长1天

    public static String createToken(Map<String,Object> payload){
        System.out.println("正在生成token");
        //日历类
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND,EXPIRATION_MS);
        // 设置桂平群殴瑟吉欧靠门
        JWTCreator.Builder builder = JWT.create().withExpiresAt(calendar.getTime());

        //遍历信息,将所有信息进行加密
        Set<String> keys = payload.keySet();
        for(String key : keys){
            String value = payload.get(key).toString();
            // 后续可以直接在token中获取对应信息
            builder.withClaim(key, value);
        }

        return builder.sign(Algorithm.HMAC256(secretKey));
    }

    /**
     * 验证JWT,返回为false的时候表示验证失败
     *
     * @param token token字符串
     * @return 返回boolean 表示是否登录成功
     */
    public static boolean verifyToken(String token){
        String errorName = null;
        try {
            JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
            return true;
        } catch (TokenExpiredException e) {
            errorName = "Token已过期";
            throw new JWTValidationException(errorName, 401); // 自定义异常
        } catch (JWTDecodeException | IllegalArgumentException e) {
            errorName = "Token格式错误";
            throw new JWTValidationException(errorName, 400);
        } catch (SignatureVerificationException e) {
            errorName = "签名验证失败";
            throw new JWTValidationException(errorName, 401);
        } catch (JWTVerificationException e) {
            errorName = "Token验证失败:"+ e.getMessage();
            throw new JWTValidationException(errorName, 401);
        } catch (Exception e) {
            errorName = "未知错误";
            throw new JWTValidationException(errorName, 500);
        }finally {
            if(errorName != null){
                System.out.println("---------------------------有一个JWT相关错误,信息如下--------------------------");
                System.out.println(errorName);
            }
        }
    }


    /**
     * 获取token中存储的信息
     *
     * @param token 字符串, claimKey 需要获取的参数名
     * @return 返回对应的用户id,如果为0则表示没有用户
     */
    public static Object getClaimValue(String token, String claimKey) {
        try {
            DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secretKey))
                    .build()
                    .verify(token); // 验证并解析Token
            Claim claim = decodedJWT.getClaim(claimKey); // 获取指定Claim

            // 根据Claim实际类型返回结果
            if (claim.isNull()) {
                return null; // Claim不存在
            } else if (claim.asString() != null) {
                return claim.asString(); // 字符串类型
            } else if (claim.asBoolean() != null) {
                return claim.asBoolean(); // 布尔类型
            } else if (claim.asLong() != null) {
                return claim.asLong(); // 长整型
            } else {
                return claim.as(Object.class); // 其他类型(需自定义处理)
            }
        } catch (JWTVerificationException | IllegalArgumentException e) {
            throw new JWTValidationException("Token解析失败:" + e.getMessage(), 401); // 抛出异常
        }
    }
}

 RedisUtil类

进行Redis操作(需前置RedisConfig)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spring redis 工具类
 *
 * @author 幻现
 **/
@SuppressWarnings(value = {"unchecked", "rawtypes"})
@Component
public class RedisUtil {
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param timeout  时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout) {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @param unit    时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public long deleteObject(final Collection collection) {
        return redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList) {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key) {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext()) {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey) {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 删除Hash中的数据
     *
     * @param key
     * @param hKey
     */
    public void delCacheMapValue(final String key, final String hKey) {
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern) {
        return redisTemplate.keys(pattern);
    }
}

M类

自定义的一个返回格式类 

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.io.Serializable;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)// 忽略值为null的字段
public class M implements Serializable {
    //普通参数
    private Integer code;
    private String msg;
    private Object rows;
    private Long total;

    //登录参数
    private String token;
    private Integer userId;
    private String username;

    public static M success(String msg) {
        M m = new M();
        m.code=200;
        m.msg=msg;
        return m;
    }

    public static M success(String msg, Object rows) {
        M m = success(msg);
        m.rows=rows;
        return m;
    }

    public static M success(String msg, Object data, Long total) {
        M m = success(msg, data);
        m.total = total;
        return m;
    }

    public static M error(String msg) {
        M m = new M();
        m.code=500;
        m.msg=msg;
        return m;
    }

    public static M error(String msg, Object rows) {
        M m = error(msg);
        m.rows=rows;
        return m;
    }

    public static M loginResSuccess(String token, Integer userId, String username){
        M m = new M();
        m.code = 200;
        m.msg = "登录成功";
        m.token = token;
        m.userId = userId;
        m.username = username;
        return m;
    }

    public static M loginResError(String msg){
        M m = error(msg);
        m.token = null;
        m.userId = null;
        m.username = null;
        return m;
    }
}

application.yml(项目配置)

# 配置数据源
spring:
  application:
    name: 自定义
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/自定义?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: "123456"
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 100MB

server:
  port: 8088
  servlet:
    context-path: /自定义

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: 自定义.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

mybatis-plus:
  # mapper配置文件
  mapper-locations: classpath:mapper/*.xml
  # resultType别名,没有这个配置resultType包名要写全,配置后只要写类名
  type-aliases-package: com.example.springboot01.entity
  configuration:
    # 日志的配置,直接添加即可,内部有自带配置项
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

加密密码生成(可以写成接口,让用户修改密码)

    @Test
    void contextLoads() {

        // 对数据库中的密码password进行加密
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        String encode = encoder.encode("123");//加密"123"密码
        System.out.println(encode);

    }

五、注意

1.数据库中的密码必须为进行Spring Security加密后的密码,否则在密码验证时,会报权限错误。

2. SpringSecurity一旦引入,它的过滤器就会生效,会要求用户先登录

3. 如果使用SpringSecurity自带的流程 -> 所有的用户都是共用同一套账号密码 -> 自定义登录流程

六、输出示例

登录接口

示例一(成功)

输入

{
    "username": "111111",
    "password": "111111"
}

输出

{
    "code": 200,
    "msg": "登录成功",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyYWRpc0tleSI6InRva2VuOjgzNmRjYzFiLTJlMDctNDU1NC05M2VlLTAyZTI0MDVkNzJkYSIsImV4cCI6MTc0NjYyMTU3OSwidXNlcklkIjoiMiJ9.RYvuAjQs1ZMMkIirwIr0tZl7T-7SrM9pumqw3PlfZbU",
    "userId": 2,
    "username": "111111"
}

示例二(失败)

输入

{
    "username": "11111",
    "password": "111111"
}

输出

{
    "code": 500,
    "msg": "用户名不存在"
}

 其他接口

示例(失败)

输入

Header
{
    token:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyYWRpc0tleSI6InRva2VuOjgzNmRjYzFiLTJlMDctNDU1NC05M2VlLTAyZTI0MDVkNzJkYSIsImV4cCI6MTc0NjYyMTU3OSwidXNlcklkIjoiMiJ9.RYvuAjQs1ZMMkIirwIr0tZl7T-7SrM9pumqw3PlfZbU"
}

输出

{
    "code": 400,
    "msg": "Token格式错误"
}

关注、点赞、收藏(三连感谢)!!! 

目录

 一、前言

二、实现功能

三、Maven

四、代码

service包

UserDetailsServiceImpl类

 UserService接口

 UserServiceImpl类

 entity包

LoginUser类

 User类

  LoginVo类

 filter包(AOP切面过滤器)

exception包(自定义异常)

handler包(捕获异常)

config包(配置)

SecurityConfig类

 RedisConfig类

controller包

utils包(工具集合)

JWTUtil类

 RedisUtil类

M类

application.yml(项目配置)

加密密码生成(可以写成接口,让用户修改密码)

五、注意

六、输出示例

登录接口

示例一(成功)

示例二(失败)

 其他接口

示例(失败)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值