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

勾選需要用到的框架

2. 引入相關(guān)依賴
提示:有三個(gè)依賴需要手動(dòng)添加,其余的在創(chuàng)建項(xiàng)目時(shí)就生成了
<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)建項(xiàng)目時(shí)勾選直接生成的,下面是手動(dò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ù)庫(kù)并生成數(shù)據(jù)
數(shù)據(jù)庫(kù)名為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. 整體項(xiàng)目結(jié)構(gòu)
可以按照我的來(lái),也可自行決定

二、具體實(shí)現(xiàn)
1. 編寫(xiě)配置文件
提示:修改數(shù)據(jù)庫(kù)的密碼,以及redis的端口號(hào),我使用的7000,redis的默認(rèn)端口號(hào)為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ù)庫(kù)索引 默認(rèn)為0
host: 127.0.0.1 # redis服務(wù)器地址
port: 7000 # 端口號(hào)
password: # 密碼(默認(rèn)為空)
timeout: 5000 # 連接超時(shí)時(shí)間(毫秒)
jedis:
pool: # 連接池配置
max-active: 8 # 連接池最大連接數(shù)(使用負(fù)值表示沒(méi)有限制)
max-wait: -1 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒(méi)有限制)
max-idle: 8 # 連接池中的最大空閑連接
min-idle: 0 # 連接池中的最小空閑連接
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.javasec.bean2. 創(chuàng)建實(shí)體類(lèi)
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ù)庫(kù)主鍵
*/
private String id;
/**
* 角色uid
*/
private String rid;
/**
* 角色名稱(chēng)
*/
private String name;
/**
* 角色名稱(chēng)中文
*/
private String nameZh;
}創(chuàng)建的User需要實(shí)現(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ù)庫(kù)主鍵
*/
private String id;
/**
* 用戶uid
*/
private String uid;
/**
* 用戶登錄名
*/
private String username;
/**
* 用戶登錄密碼
*/
private String password;
/**
* 用戶創(chuàng)建時(shí)間
*/
private Date time;
/**
* 用戶是否被鎖
*/
private boolean locked;
/**
* 用戶是否開(kāi)啟
*/
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. 編寫(xiě)dao層代碼以及mapper
UserDao中主要是通過(guò)username來(lái)查詢數(shù)據(jù)庫(kù)中是否存在這個(gè)用戶
RoleDao主要是用來(lái)查詢登錄用戶的角色列表(一個(gè)用戶可能有多個(gè)角色)
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. 編寫(xiě)springSecurity相關(guān)處理器
編寫(xiě)UserServices
進(jìn)行身份驗(yàn)證之前,Spring Security會(huì)調(diào)用loadUserByUsername()方法來(lái)獲取用戶信息。該方法通常用于在數(shù)據(jù)庫(kù)中查詢用戶信息,然后將其封裝在UserDetails接口的實(shí)現(xiàn)類(lèi)中,并返回給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;
}
}登錄處理器
這個(gè)處理器中有兩個(gè)方法,onAuthenticationSuccess()方法主要用于登錄成功時(shí)為header設(shè)置token,并將整個(gè)已經(jīng)登錄的對(duì)象(authentication)存入到redis中,onAuthenticationFailure()方法主要用于登錄失敗時(shí)返回提示信息。
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過(guò)濾器
該過(guò)濾器可以攔截每一次請(qǐng)求,并驗(yàn)證header中是否存在token,驗(yàn)證成功則會(huì)通過(guò)UsernamePasswordAuthenticationToken傳給一個(gè)authentication provider驗(yàn)證成功則會(huì)返回一個(gè)帶有授權(quán)信息的身份驗(yàn)證對(duì)象。如果身份驗(yàn)證失敗,則應(yīng)返回一個(gè)未通過(guò)的身份驗(yàn)證對(duì)象。
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 {
// 從請(qǐng)求頭或請(qǐng)求參數(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) {
// 驗(yàn)證失敗,可以進(jìn)行一些處理
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
log.error(e.getMessage());
}
filterChain.doFilter(request, response);
}
}5. 本文所用的工具類(lèi)
響應(yīng)類(lèi)
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 {
/**
* 成功失敗標(biāo)識(shí)
*/
private boolean flag;
/**
* 響應(yīng)數(shù)據(jù)
*/
private T data;
/**
* 狀態(tài)碼
*/
private Integer code;
/**
* 響應(yīng)消息
*/
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工具類(lèi)
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ā)時(shí)間
.setExpiration(expireDate) // 7天過(guò)期
// .setPayload(String.valueOf(user))//設(shè)置載荷 payload和claims不能同時(shí)指定
.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是否過(guò)期
public static boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
}Redis工具類(lèi)
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;
}
/**
* 寫(xiě)入緩存
*
* @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;
}
/**
* 寫(xiě)入緩存設(shè)置時(shí)效時(shí)間
*
* @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;
}
/**
* 批量刪除對(duì)應(yīng)的鍵值對(duì)
*
* @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,也刪除對(duì)應(yīng)的value
*
* @param key Redis鍵名
*/
public void remove(final String key) {
if (exists(key)) {
redisTemplate.delete(key);
}
}
/**
* 判斷緩存中是否有對(duì)應(yīng)的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);
}
/**
* 獲取所有哈希鍵值對(duì)
*
* @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 開(kāi)始行號(hào)
* @param end 結(jié)束行號(hào)
* @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 排序號(hào)
*/
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 開(kāi)始序號(hào)
* @param endScore 結(jié)束序號(hào)
* @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);
}
/**
* 獲取多個(gè)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;
}
});
}
/**
* 保存多個(gè)哈希表(HashMap)(Redis鍵名可重復(fù))
*
* @param batchMap Map<Redis鍵名,Map<鍵,值>>
*/
public void batchHashMapSet(HashMultimap<String, Map<String, String>> batchMap) {
// 設(shè)置5秒超時(shí)時(shí)間
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;
}
});
}
/**
* 保存多個(gè)哈希表(HashMap)(Redis鍵名不可以重復(fù))
*
* @param dataMap Map<Redis鍵名,Map<哈希鍵,哈希值>>
*/
public void batchHashMapSet(Map<String, Map<String, String>> dataMap) {
// 設(shè)置5秒超時(shí)時(shí)間
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;
}
});
}
/**
* 保存多個(gè)哈希表(HashMap)列表(哈希map的Redis鍵名不能重復(fù))
*
* @param list Map<Redis鍵名,Map<哈希鍵,哈希值>>
* @see RedisUtils*.batchHashMapSet()*
*/
public void batchHashMapListSet(List<Map<String, Map<String, String>>> list) {
// 設(shè)置5秒超時(shí)時(shí)間
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配置類(lèi)
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() {
//開(kāi)啟加密
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()
//所有接口都需要登錄才能訪問(wèn)
.anyRequest().authenticated()
.and()
//開(kāi)啟表單登錄
.formLogin()
//登錄成功的處理方法
.successHandler(customAuthenticationHandler)
//登錄失敗的處理方法
.failureHandler(customAuthenticationHandler);
//關(guān)閉csrf
http.csrf().disable();
//開(kāi)啟過(guò)濾器,并將其置于UsernamePasswordAuthenticationFilter過(guò)濾器之前
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}三、測(cè)試
1. 測(cè)試類(lèi)
有兩個(gè)賬戶,一個(gè)是root,一個(gè)是user,密碼都是123456
先寫(xiě)一個(gè)測(cè)試類(lèi),這個(gè)類(lèi)中的接口需要有相關(guān)權(quán)限的用戶才能訪問(wè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)限才能訪問(wèn)
@PreAuthorize("hasAuthority('admin')")
@GetMapping("/test1")
public String test() {
return "測(cè)試";
}
//該接口需要user權(quán)限才能訪問(wèn)
@PreAuthorize("hasAuthority('user')")
@GetMapping("/test2")
public String demo() {
return "這是demo接口";
}
}2. 使用root賬戶登錄

訪問(wèn)test1接口,沒(méi)有問(wèn)題

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

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

訪問(wèn)test1接口,被禁止

訪問(wèn)test2接口,訪問(wèn)成功

redis中登錄成功存儲(chǔ)的jwt,有兩個(gè)賬號(hào)登錄,所以有兩條

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

