一、前言
一般的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格式错误"
}
关注、点赞、收藏(三连感谢)!!!
目录