SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認證的實現(xiàn)
前言
本項目主要是一個SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認證的Demo,其中也涉及到自定義的過濾器和處理器,希望能對大家有幫助,本文中所有代碼正常情況下可以直接復制使用。
一、前期準備
1. 創(chuàng)建項目

勾選需要用到的框架

2. 引入相關依賴
提示:有三個依賴需要手動添加,其余的在創(chuàng)建項目時就生成了
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--以上是創(chuàng)建項目時勾選直接生成的,下面是手動添加的-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>3. 創(chuàng)建數(shù)據(jù)庫并生成數(shù)據(jù)
數(shù)據(jù)庫名為javasec,可自行更改
role
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`rid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`nameZh` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1', 'hGwQWakALy', 'admin', '管理員');
INSERT INTO `role` VALUES ('2', 'afdasfsadf', 'user', '普通用戶');
SET FOREIGN_KEY_CHECKS = 1;user
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`uid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`time` datetime NULL DEFAULT NULL,
`locked` tinyint NULL DEFAULT NULL,
`enabled` tinyint NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'xbvlhKeYXv', 'root', '$2a$10$SuYo3aVhuZDBDGaEJSlNGedkFcRqPB6WPlXpntpt8bklp067VtVs.', '2021-08-31 14:41:01', 0, 1);
INSERT INTO `user` VALUES ('2', 'asdfsd', 'user', '$2a$10$SuYo3aVhuZDBDGaEJSlNGedkFcRqPB6WPlXpntpt8bklp067VtVs.', '2023-08-22 21:00:22', 0, 1);
SET FOREIGN_KEY_CHECKS = 1;user_role
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`uid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用戶id',
`rid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES ('1', 'xbvlhKeYXv', 'hGwQWakALy');
INSERT INTO `user_role` VALUES ('2', 'asdfsd', 'afdasfsadf');
SET FOREIGN_KEY_CHECKS = 1;4. 整體項目結(jié)構(gòu)
可以按照我的來,也可自行決定

二、具體實現(xiàn)
1. 編寫配置文件
提示:修改數(shù)據(jù)庫的密碼,以及redis的端口號,我使用的7000,redis的默認端口號為6379
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/javasec?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&allowMultiQueries=true
password: ****
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
## redis配置
redis:
database: 0 # 數(shù)據(jù)庫索引 默認為0
host: 127.0.0.1 # redis服務器地址
port: 7000 # 端口號
password: # 密碼(默認為空)
timeout: 5000 # 連接超時時間(毫秒)
jedis:
pool: # 連接池配置
max-active: 8 # 連接池最大連接數(shù)(使用負值表示沒有限制)
max-wait: -1 # 連接池最大阻塞等待時間(使用負值表示沒有限制)
max-idle: 8 # 連接池中的最大空閑連接
min-idle: 0 # 連接池中的最小空閑連接
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.javasec.bean2. 創(chuàng)建實體類
package com.javasec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* @Author YZK
* @Date 2023/7/5
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class Role {
/**
* 數(shù)據(jù)庫主鍵
*/
private String id;
/**
* 角色uid
*/
private String rid;
/**
* 角色名稱
*/
private String name;
/**
* 角色名稱中文
*/
private String nameZh;
}創(chuàng)建的User需要實現(xiàn)UserDetails接口
package com.javasec.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* @Author YZK
* @Date 2023/7/1
*/
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
/**
* 數(shù)據(jù)庫主鍵
*/
private String id;
/**
* 用戶uid
*/
private String uid;
/**
* 用戶登錄名
*/
private String username;
/**
* 用戶登錄密碼
*/
private String password;
/**
* 用戶創(chuàng)建時間
*/
private Date time;
/**
* 用戶是否被鎖
*/
private boolean locked;
/**
* 用戶是否開啟
*/
private boolean enabled;
/**
* 賬戶登錄token
*/
private String token;
/**
* 用戶角色列表
*/
List<Role> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return !locked;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
}3. 編寫dao層代碼以及mapper
UserDao中主要是通過username來查詢數(shù)據(jù)庫中是否存在這個用戶
RoleDao主要是用來查詢登錄用戶的角色列表(一個用戶可能有多個角色)
package com.javasec.dao;
import com.javasec.bean.User;
import org.apache.ibatis.annotations.Param;
/**
* @Author YZK
* @Date 2023/7/5
*/
public interface UserDao {
User loadUserByUsername(@Param("username") String username);
}package com.javasec.dao;
import com.javasec.bean.Role;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* @Author YZK
* @Date 2023/7/5
*/
public interface RoleDao {
List<Role> getUserRoleByUid(@Param("uid") String uid);
}<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javasec.dao.UserDao">
<resultMap type="com.javasec.bean.User" id="UserMap">
<result property="id" column="id" jdbcType="VARCHAR"/>
<result property="uid" column="uid" jdbcType="VARCHAR"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="time" column="time" jdbcType="TIMESTAMP"/>
<result property="locked" column="locked" jdbcType="INTEGER"/>
<result property="enabled" column="enabled" jdbcType="INTEGER"/>
</resultMap>
<select id="loadUserByUsername" resultMap="UserMap">
select *
from user
where username = #{username}
</select>
</mapper><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javasec.dao.RoleDao">
<resultMap type="com.javasec.bean.Role" id="RoleMap">
<result property="id" column="id" jdbcType="VARCHAR"/>
<result property="rid" column="rid" jdbcType="VARCHAR"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="nameZh" column="nameZh" jdbcType="VARCHAR"/>
</resultMap>
<select id="getUserRoleByUid" resultMap="RoleMap">
select *
from role r,
user_role ur
where r.rid = ur.rid
and ur.uid = #{uid}
</select>
</mapper>4. 編寫springSecurity相關處理器
編寫UserServices
進行身份驗證之前,Spring Security會調(diào)用loadUserByUsername()方法來獲取用戶信息。該方法通常用于在數(shù)據(jù)庫中查詢用戶信息,然后將其封裝在UserDetails接口的實現(xiàn)類中,并返回給Spring Security。
package com.javasec.sec;
import com.javasec.bean.User;
import com.javasec.dao.RoleDao;
import com.javasec.dao.UserDao;
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 javax.annotation.Resource;
import java.util.Objects;
/**
* @Author YZK
* @Date 2023/7/5
*/
@Service
public class UserServices implements UserDetailsService {
@Resource
UserDao userDao;
@Resource
RoleDao roleDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.loadUserByUsername(username);
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("用戶不存在");
}
user.setRoles(roleDao.getUserRoleByUid(user.getUid()));
return user;
}
}登錄處理器
這個處理器中有兩個方法,onAuthenticationSuccess()方法主要用于登錄成功時為header設置token,并將整個已經(jīng)登錄的對象(authentication)存入到redis中,onAuthenticationFailure()方法主要用于登錄失敗時返回提示信息。
package com.javasec.sec.handler;
import com.alibaba.fastjson.JSONObject;
import com.javasec.bean.User;
import com.javasec.utils.JwtUtil;
import com.javasec.utils.RedisUtils;
import com.javasec.utils.result.SystemResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author YZK
* @Date 2023/7/15
*/
@Component
public class CustomAuthenticationHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
@Resource
RedisUtils redisUtils;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
User user = (User) authentication.getPrincipal();
String jwt = JwtUtil.generateToken(user);
if (redisUtils.exists("login:" + user.getUid())) {
redisUtils.remove("login:" + user.getUid());
}
response.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=UTF-8");
redisUtils.set("login:" + user.getUid(), jwt);
response.setHeader("token", jwt);
response.getWriter().write(JSONObject.toJSONString(SystemResult.success("登錄成功")));
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.getWriter().write(JSONObject.toJSONString(SystemResult.fail(401, exception.getMessage())));
}
}Jwt過濾器
該過濾器可以攔截每一次請求,并驗證header中是否存在token,驗證成功則會通過UsernamePasswordAuthenticationToken傳給一個authentication provider驗證成功則會返回一個帶有授權(quán)信息的身份驗證對象。如果身份驗證失敗,則應返回一個未通過的身份驗證對象。
package com.javasec.sec.filter;
import com.alibaba.fastjson.JSON;
import com.javasec.bean.User;
import com.javasec.utils.JwtUtil;
import com.javasec.utils.RedisUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Resource
RedisUtils redisUtils;
public JwtAuthenticationFilter() {
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 從請求頭或請求參數(shù)中獲取JWT token
String token = request.getHeader("token");
try {
Claims claimByToken = JwtUtil.getClaimByToken(token);
assert claimByToken != null;
String tem = JSON.toJSONString(claimByToken.get("user"));
User user = JSON.parseObject(tem, User.class);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
redisUtils.get("login" + user.getUid()), null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
} catch (Exception e) {
// 驗證失敗,可以進行一些處理
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
log.error(e.getMessage());
}
filterChain.doFilter(request, response);
}
}5. 本文所用的工具類
響應類
package com.javasec.utils.result;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SystemResult<T> implements Serializable {
/**
* 成功失敗標識
*/
private boolean flag;
/**
* 響應數(shù)據(jù)
*/
private T data;
/**
* 狀態(tài)碼
*/
private Integer code;
/**
* 響應消息
*/
private String message;
public static Integer SUCCESS_200 = 200;
public static Integer FAIL_500 = 500;
public static <T> SystemResult<T> success() {
return SystemResult.success(null);
}
public static <T> SystemResult<T> success(T result) {
SystemResult<T> systemResult = new SystemResult<>();
systemResult.setFlag(true);
systemResult.setData(result);
systemResult.setMessage("成功");
systemResult.setCode(SUCCESS_200);
return systemResult;
}
public static <T> SystemResult<T> success(String msg) {
SystemResult<T> systemResult = new SystemResult<>();
systemResult.setFlag(true);
systemResult.setMessage(msg);
return systemResult;
}
public static <T> SystemResult<T> fail(T result) {
SystemResult<T> systemResult = new SystemResult<>();
systemResult.setFlag(false);
systemResult.setCode(FAIL_500);
systemResult.setData(result);
return systemResult;
}
public static <T> SystemResult<T> fail(String msg) {
SystemResult<T> systemResult = new SystemResult<>();
systemResult.setFlag(false);
systemResult.setCode(FAIL_500);
systemResult.setMessage(msg);
return systemResult;
}
public static <T> SystemResult<T> fail(T result, String msg) {
SystemResult<T> systemResult = new SystemResult<>();
systemResult.setFlag(false);
systemResult.setCode(FAIL_500);
systemResult.setMessage(msg);
systemResult.setData(result);
return systemResult;
}
}Jwt工具類
package com.javasec.utils;
import com.javasec.bean.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.stereotype.Component;
import java.util.Date;
@Data
@Component
public class JwtUtil {
// private long expire;
// private static final String secret = "admin";
private String header;
// 生成jwt
public static String generateToken(User user) {
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + 1000 * 604800);
// Map<String, Object> userMap = new HashMap<>();
// userMap.put("user",user);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(user.getUsername())//主題
.setIssuedAt(nowDate) //jwt的簽發(fā)時間
.setExpiration(expireDate) // 7天過期
// .setPayload(String.valueOf(user))//設置載荷 payload和claims不能同時指定
.claim("user",user)
.signWith(SignatureAlgorithm.HS512, "admin")//指定加密算法
.compact();
}
// 解析jwt
public static Claims getClaimByToken(String jwt) {
try {
return (Claims) Jwts.parser()
.setSigningKey("admin")
// .parseClaimsJwt(jwt)
.parse(jwt)
.getBody();
} catch (Exception e) {
return null;
}
}
// jwt是否過期
public static boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}Redis工具類
package com.javasec.utils;
import com.google.common.collect.HashMultimap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author Administrator
*/
@Component
public class RedisUtils {
@Autowired
private StringRedisTemplate redisTemplate;
public RedisUtils(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 寫入緩存
*
* @param key redis鍵
* @param value redis值
* @return 是否成功
*/
public boolean set(final String key, String value) {
boolean result = false;
try {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set(key, value);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 寫入緩存設置時效時間
*
* @param key redis鍵
* @param value redis值
* @return 是否成功
*/
public boolean set(final String key, String value, Long expireTime) {
boolean result = false;
try {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set(key, value);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
result = true;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 批量刪除對應的鍵值對
*
* @param keys Redis鍵名數(shù)組
*/
public void removeByKeys(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量刪除Redis key
*
* @param pattern 鍵名包含字符串(如:myKey*)
*/
public void removePattern(final String pattern) {
Set<String> keys = redisTemplate.keys(pattern);
if (keys != null && keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/**
* 刪除key,也刪除對應的value
*
* @param key Redis鍵名
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判斷緩存中是否有對應的value
*
* @param key Redis鍵名
* @return 是否存在
*/
public Boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 讀取緩存
*
* @param key Redis鍵名
* @return 是否存在
*/
public String get(final String key) {
String result = null;
ValueOperations<String, String> operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 哈希 添加
*
* @param key Redis鍵
* @param hashKey 哈希鍵
* @param value 哈希值
*/
public void hmSet(String key, String hashKey, String value) {
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希獲取數(shù)據(jù)
*
* @param key Redis鍵
* @param hashKey 哈希鍵
* @return 哈希值
*/
public String hmGet(String key, String hashKey) {
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 判斷hash是否存在鍵
*
* @param key Redis鍵
* @param hashKey 哈希鍵
* @return 是否存在
*/
public boolean hmHasKey(String key, String hashKey) {
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
return hash.hasKey(key, hashKey);
}
/**
* 刪除hash中一條或多條數(shù)據(jù)
*
* @param key Redis鍵
* @param hashKeys 哈希鍵名數(shù)組
* @return 刪除數(shù)量
*/
public long hmRemove(String key, String... hashKeys) {
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
return hash.delete(key, hashKeys);
}
/**
* 獲取所有哈希鍵值對
*
* @param key Redis鍵名
* @return 哈希Map
*/
public Map<String, String> hashMapGet(String key) {
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
return hash.entries(key);
}
/**
* 保存Map到哈希
*
* @param key Redis鍵名
* @param map 哈希Map
*/
public void hashMapSet(String key, Map<String, String> map) {
HashOperations<String, String, String> hash = redisTemplate.opsForHash();
hash.putAll(key, map);
}
/**
* 列表-追加值
*
* @param key Redis鍵名
* @param value 列表值
*/
public void lPush(String key, String value) {
ListOperations<String, String> list = redisTemplate.opsForList();
list.rightPush(key, value);
}
/**
* 列表-獲取指定范圍數(shù)據(jù)
*
* @param key Redis鍵名
* @param start 開始行號
* @param end 結(jié)束行號
* @return 列表
*/
public List<String> lRange(String key, long start, long end) {
ListOperations<String, String> list = redisTemplate.opsForList();
return list.range(key, start, end);
}
/**
* 集合添加
*
* @param key Redis鍵名
* @param value 值
*/
public void add(String key, String value) {
SetOperations<String, String> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合獲取
*
* @param key Redis鍵名
* @return 集合
*/
public Set<String> setMembers(String key) {
SetOperations<String, String> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key Redis鍵名
* @param value 值
* @param score 排序號
*/
public void zAdd(String key, String value, double score) {
ZSetOperations<String, String> zSet = redisTemplate.opsForZSet();
zSet.add(key, value, score);
}
/**
* 有序集合-獲取指定范圍
*
* @param key Redis鍵
* @param startScore 開始序號
* @param endScore 結(jié)束序號
* @return 集合
*/
public Set<String> rangeByScore(String key, double startScore, double endScore) {
ZSetOperations<String, String> zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, startScore, endScore);
}
/**
* 模糊查詢Redis鍵名
*
* @param pattern 鍵名包含字符串(如:myKey*)
* @return 集合
*/
public Set<String> keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 獲取多個hashMap
*
* @param keySet
* @return List<Map < String, String>> hashMap列表
*/
public List hashMapList(Collection<String> keySet) {
return redisTemplate.executePipelined(new SessionCallback<String>() {
@Override
public <K, V> String execute(RedisOperations<K, V> operations) throws DataAccessException {
HashOperations hashOperations = operations.opsForHash();
for (String key : keySet) {
hashOperations.entries(key);
}
return null;
}
});
}
/**
* 保存多個哈希表(HashMap)(Redis鍵名可重復)
*
* @param batchMap Map<Redis鍵名,Map<鍵,值>>
*/
public void batchHashMapSet(HashMultimap<String, Map<String, String>> batchMap) {
// 設置5秒超時時間
redisTemplate.expire("max", 25, TimeUnit.SECONDS);
redisTemplate.executePipelined(new RedisCallback<List<Map<String, String>>>() {
@Override
public List<Map<String, String>> doInRedis(RedisConnection connection) throws DataAccessException {
Iterator<Map.Entry<String, Map<String, String>>> iterator = batchMap.entries().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Map<String, String>> hash = iterator.next();
// 哈希名,即表名
byte[] hashName = redisTemplate.getStringSerializer().serialize(hash.getKey());
Map<String, String> hashValues = hash.getValue();
Iterator<Map.Entry<String, String>> it = hashValues.entrySet().iterator();
// 將元素序列化后緩存,即表的多條哈希記錄
Map<byte[], byte[]> hashes = new HashMap<byte[], byte[]>();
while (it.hasNext()) {
// hash中一條key-value記錄
Map.Entry<String, String> entry = it.next();
byte[] key = redisTemplate.getStringSerializer().serialize(entry.getKey());
byte[] value = redisTemplate.getStringSerializer().serialize(entry.getValue());
hashes.put(key, value);
}
// 批量保存
connection.hMSet(hashName, hashes);
}
return null;
}
});
}
/**
* 保存多個哈希表(HashMap)(Redis鍵名不可以重復)
*
* @param dataMap Map<Redis鍵名,Map<哈希鍵,哈希值>>
*/
public void batchHashMapSet(Map<String, Map<String, String>> dataMap) {
// 設置5秒超時時間
redisTemplate.expire("max", 25, TimeUnit.SECONDS);
redisTemplate.executePipelined(new RedisCallback<List<Map<String, String>>>() {
@Override
public List<Map<String, String>> doInRedis(RedisConnection connection) throws DataAccessException {
Iterator<Map.Entry<String, Map<String, String>>> iterator = dataMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Map<String, String>> hash = iterator.next();
// 哈希名,即表名
byte[] hashName = redisTemplate.getStringSerializer().serialize(hash.getKey());
Map<String, String> hashValues = hash.getValue();
Iterator<Map.Entry<String, String>> it = hashValues.entrySet().iterator();
// 將元素序列化后緩存,即表的多條哈希記錄
Map<byte[], byte[]> hashes = new HashMap<byte[], byte[]>();
while (it.hasNext()) {
// hash中一條key-value記錄
Map.Entry<String, String> entry = it.next();
byte[] key = redisTemplate.getStringSerializer().serialize(entry.getKey());
byte[] value = redisTemplate.getStringSerializer().serialize(entry.getValue());
hashes.put(key, value);
}
// 批量保存
connection.hMSet(hashName, hashes);
}
return null;
}
});
}
/**
* 保存多個哈希表(HashMap)列表(哈希map的Redis鍵名不能重復)
*
* @param list Map<Redis鍵名,Map<哈希鍵,哈希值>>
* @see RedisUtils*.batchHashMapSet()*
*/
public void batchHashMapListSet(List<Map<String, Map<String, String>>> list) {
// 設置5秒超時時間
redisTemplate.expire("max", 25, TimeUnit.SECONDS);
redisTemplate.executePipelined(new RedisCallback<List<Map<String, String>>>() {
@Override
public List<Map<String, String>> doInRedis(RedisConnection connection) throws DataAccessException {
for (Map<String, Map<String, String>> dataMap : list) {
Iterator<Map.Entry<String, Map<String, String>>> iterator = dataMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Map<String, String>> hash = iterator.next();
// 哈希名,即表名
byte[] hashName = redisTemplate.getStringSerializer().serialize(hash.getKey());
Map<String, String> hashValues = hash.getValue();
Iterator<Map.Entry<String, String>> it = hashValues.entrySet().iterator();
// 將元素序列化后緩存,即表的多條哈希記錄
Map<byte[], byte[]> hashes = new HashMap<byte[], byte[]>();
while (it.hasNext()) {
// hash中一條key-value記錄
Map.Entry<String, String> entry = it.next();
byte[] key = redisTemplate.getStringSerializer().serialize(entry.getKey());
byte[] value = redisTemplate.getStringSerializer().serialize(entry.getValue());
hashes.put(key, value);
}
// 批量保存
connection.hMSet(hashName, hashes);
}
}
return null;
}
});
}
}6. SpringScurity配置類
package com.javasec.config;
import com.javasec.sec.UserServices;
import com.javasec.sec.filter.JwtAuthenticationFilter;
import com.javasec.sec.handler.CustomAuthenticationHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.annotation.Resource;
/**
* @Author YZK
* @Date 2023/5/4
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Resource
UserServices userServices;
@Resource
JwtAuthenticationFilter jwtAuthenticationFilter;
@Resource
CustomAuthenticationHandler customAuthenticationHandler;
@Bean
public PasswordEncoder passwordEncoder() {
//開啟加密
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServices);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//所有接口都需要登錄才能訪問
.anyRequest().authenticated()
.and()
//開啟表單登錄
.formLogin()
//登錄成功的處理方法
.successHandler(customAuthenticationHandler)
//登錄失敗的處理方法
.failureHandler(customAuthenticationHandler);
//關閉csrf
http.csrf().disable();
//開啟過濾器,并將其置于UsernamePasswordAuthenticationFilter過濾器之前
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}三、測試
1. 測試類
有兩個賬戶,一個是root,一個是user,密碼都是123456
先寫一個測試類,這個類中的接口需要有相關權(quán)限的用戶才能訪問
package com.javasec.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author YZK
* @Date 2023/8/21
*/
@RestController
public class TestController {
//該接口需要admin權(quán)限才能訪問
@PreAuthorize("hasAuthority('admin')")
@GetMapping("/test1")
public String test() {
return "測試";
}
//該接口需要user權(quán)限才能訪問
@PreAuthorize("hasAuthority('user')")
@GetMapping("/test2")
public String demo() {
return "這是demo接口";
}
}2. 使用root賬戶登錄

訪問test1接口,沒有問題

訪問test2接口,被禁止,權(quán)限錯誤的信息也可以自定義,本項目未自定義

3. 使用user賬戶登錄
同樣的登錄成功的信息

訪問test1接口,被禁止

訪問test2接口,訪問成功

redis中登錄成功存儲的jwt,有兩個賬號登錄,所以有兩條

總結(jié)
后端生成的jwt應該存儲在redis中,每次處理請求時都應檢驗一次用戶的權(quán)限信息,以及jwt是否過期,在前后端分離的項目中,前端登錄后,后端生成的jwt會在請求頭中設置,然后前端拿到后,會存儲在localstorage中(只是舉例,想存哪兒隨意),在前端發(fā)起請求時,也會攜帶著token到后端進行檢驗。
到此這篇關于SpringSecurity+jwt+redis基于數(shù)據(jù)庫登錄認證的實現(xiàn)的文章就介紹到這了,更多相關SpringSecurity+jwt+redis登錄認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- 如何使用jwt+redis實現(xiàn)單點登錄
- 使用Redis實現(xiàn)JWT令牌主動失效機制
- SpringSecurity+Redis+Jwt實現(xiàn)用戶認證授權(quán)
- Shiro整合Springboot和redis,jwt過程中的錯誤shiroFilterChainDefinition問題
- jwt+redis實現(xiàn)登錄認證的示例代碼
- 基于 Redis 的 JWT令牌失效處理方案(實現(xiàn)步驟)
- springboot+springsecurity+mybatis+JWT+Redis?實現(xiàn)前后端離實戰(zhàn)教程
- SpringBoot整合SpringSecurity和JWT和Redis實現(xiàn)統(tǒng)一鑒權(quán)認證
- java實現(xiàn)認證與授權(quán)的jwt與token+redis,哪種方案更好用?
相關文章
微服務架構(gòu)設計RocketMQ進階事務消息原理詳解
這篇文章主要介紹了為大家介紹了微服務架構(gòu)中RocketMQ進階層面事務消息的原理詳解,有需要的朋友可以借鑒參考下希望能夠有所幫助2021-10-10
quartz定時執(zhí)行任務,并配置web.xml的操作方法
下面小編就為大家?guī)硪黄猶uartz定時執(zhí)行任務,并配置web.xml的操作方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
Java線程安全解決方案(synchronized,ReentrantLock,Atomic)
這篇文章主要介紹了Java線程安全解決方案(synchronized,ReentrantLock,Atomic),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09
Java生產(chǎn)1-100的隨機數(shù)簡單實例(分享)
下面小編就為大家?guī)硪黄狫ava生產(chǎn)1-100的隨機數(shù)簡單實例(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05
詳解Java的MyBatis框架和Spring框架的整合運用
在Web端的SSH框架整合中Spring主要負責數(shù)據(jù)庫處理,而引入MyBatis后二者的集成使用效果更佳,下面我們就來詳解Java的MyBatis框架和Spring框架的整合運用2016-06-06

