一、前期准备
1. 创建项目
勾选需要用到的框架
2. 引入相关依赖
提示:有三个依赖需要手动添加,其余的在创建项目时就生成了
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.3.1
com.mysql
mysql-connector-j
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.mybatis.spring.boot
mybatis-spring-boot-starter-test
2.3.1
test
org.springframework.security
spring-security-test
test
io.jsonwebtoken
jjwt
0.9.1
com.google.guava
guava
31.1-jre
com.alibaba
fastjson
1.2.83
3. 创建数据库并生成数据
数据库名为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. 整体项目结构
可以按照我的来,也可自行决定
二、具体实现
1. 编写配置文件
提示:修改数据库的密码,以及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 # 数据库索引 默认为0
host: 127.0.0.1 # redis服务器地址
port: 7000 # 端口号
password: # 密码(默认为空)
timeout: 5000 # 连接超时时间(毫秒)
jedis:
pool: # 连接池配置
max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 8 # 连接池中的最大空闲连接
min-idle: 0 # 连接池中的最小空闲连接
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.javasec.bean
2. 创建实体类
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 {
/**
* 数据库主键
*/
private String id;
/**
* 角色uid
*/
private String rid;
/**
* 角色名称
*/
private String name;
/**
* 角色名称中文
*/
private String nameZh;
}
创建的User需要实现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 {
/**
* 数据库主键
*/
private String id;
/**
* 用户uid
*/
private String uid;
/**
* 用户登录名
*/
private String username;
/**
* 用户登录密码
*/
private String password;
/**
* 用户创建时间
*/
private Date time;
/**
* 用户是否被锁
*/
private boolean locked;
/**
* 用户是否开启
*/
private boolean enabled;
/**
* 账户登录token
*/
private String token;
/**
* 用户角色列表
*/
List roles;
@Override
public Collection
select *
from user
where username = #{username}
select *
from role r,
user_role ur
where r.rid = ur.rid
and ur.uid = #{uid}
4. 编写springSecurity相关处理器
编写UserServices
进行身份验证之前,Spring Security会调用loadUserByUsername()方法来获取用户信息。该方法通常用于在数据库中查询用户信息,然后将其封装在UserDetails接口的实现类中,并返回给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,并将整个已经登录的对象(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; ");
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验证成功则会返回一个带有授权信息的身份验证对象。如果身份验证失败,则应返回一个未通过的身份验证对象。
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 {
// 从请求头或请求参数中获取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 implements Serializable {
/**
* 成功失败标识
*/
private boolean flag;
/**
* 响应数据
*/
private T data;
/**
* 状态码
*/
private Integer code;
/**
* 响应消息
*/
private String message;
public static Integer SUCCESS_200 = 200;
public static Integer FAIL_500 = 500;
public static SystemResult success() {
return SystemResult.success(null);
}
public static SystemResult success(T result) {
SystemResult systemResult = new SystemResult();
systemResult.setFlag(true);
systemResult.setData(result);
systemResult.setMessage("成功");
systemResult.setCode(SUCCESS_200);
return systemResult;
}
public static SystemResult success(String msg) {
SystemResult systemResult = new SystemResult();
systemResult.setFlag(true);
systemResult.setMessage(msg);
return systemResult;
}
public static SystemResult fail(T result) {
SystemResult systemResult = new SystemResult();
systemResult.setFlag(false);
systemResult.setCode(FAIL_500);
systemResult.setData(result);
return systemResult;
}
public static SystemResult fail(String msg) {
SystemResult systemResult = new SystemResult();
systemResult.setFlag(false);
systemResult.setCode(FAIL_500);
systemResult.setMessage(msg);
return systemResult;
}
public static SystemResult fail(T result, String msg) {
SystemResult 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 userMap = new HashMap();
// userMap.put("user",user);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(user.getUsername())//主题
.setIssuedAt(nowDate) //jwt的签发时间
.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 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 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键名数组
*/
public void removeByKeys(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 批量删除Redis key
*
* @param pattern 键名包含字符串(如:myKey*)
*/
public void removePattern(final String pattern) {
Set 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 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 hash = redisTemplate.opsForHash();
hash.put(key, hashKey, value);
}
/**
* 哈希获取数据
*
* @param key Redis键
* @param hashKey 哈希键
* @return 哈希值
*/
public String hmGet(String key, String hashKey) {
HashOperations hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 判断hash是否存在键
*
* @param key Redis键
* @param hashKey 哈希键
* @return 是否存在
*/
public boolean hmHasKey(String key, String hashKey) {
HashOperations hash = redisTemplate.opsForHash();
return hash.hasKey(key, hashKey);
}
/**
* 删除hash中一条或多条数据
*
* @param key Redis键
* @param hashKeys 哈希键名数组
* @return 删除数量
*/
public long hmRemove(String key, String... hashKeys) {
HashOperations hash = redisTemplate.opsForHash();
return hash.delete(key, hashKeys);
}
/**
* 获取所有哈希键值对
*
* @param key Redis键名
* @return 哈希Map
*/
public Map hashMapGet(String key) {
HashOperations hash = redisTemplate.opsForHash();
return hash.entries(key);
}
/**
* 保存Map到哈希
*
* @param key Redis键名
* @param map 哈希Map
*/
public void hashMapSet(String key, Map map) {
HashOperations hash = redisTemplate.opsForHash();
hash.putAll(key, map);
}
/**
* 列表-追加值
*
* @param key Redis键名
* @param value 列表值
*/
public void lPush(String key, String value) {
ListOperations list = redisTemplate.opsForList();
list.rightPush(key, value);
}
/**
* 列表-获取指定范围数据
*
* @param key Redis键名
* @param start 开始行号
* @param end 结束行号
* @return 列表
*/
public List lRange(String key, long start, long end) {
ListOperations list = redisTemplate.opsForList();
return list.range(key, start, end);
}
/**
* 集合添加
*
* @param key Redis键名
* @param value 值
*/
public void add(String key, String value) {
SetOperations set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合获取
*
* @param key Redis键名
* @return 集合
*/
public Set setMembers(String key) {
SetOperations set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key Redis键名
* @param value 值
* @param score 排序号
*/
public void zAdd(String key, String value, double score) {
ZSetOperations zSet = redisTemplate.opsForZSet();
zSet.add(key, value, score);
}
/**
* 有序集合-获取指定范围
*
* @param key Redis键
* @param startScore 开始序号
* @param endScore 结束序号
* @return 集合
*/
public Set rangeByScore(String key, double startScore, double endScore) {
ZSetOperations zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, startScore, endScore);
}
/**
* 模糊查询Redis键名
*
* @param pattern 键名包含字符串(如:myKey*)
* @return 集合
*/
public Set keys(String pattern) {
return redisTemplate.keys(pattern);
}
/**
* 获取多个hashMap
*
* @param keySet
* @return List> hashMap列表
*/
public List hashMapList(Collection keySet) {
return redisTemplate.executePipelined(new SessionCallback() {
@Override
public String execute(RedisOperations operations) throws DataAccessException {
HashOperations hashOperations = operations.opsForHash();
for (String key : keySet) {
hashOperations.entries(key);
}
return null;
}
});
}
/**
* 保存多个哈希表(HashMap)(Redis键名可重复)
*
* @param batchMap Map
*/
public void batchHashMapSet(HashMultimap batchMap) {
// 设置5秒超时时间
redisTemplate.expire("max", 25, TimeUnit.SECONDS);
redisTemplate.executePipelined(new RedisCallback() {
@Override
public List doInRedis(RedisConnection connection) throws DataAccessException {
Iterator iterator = batchMap.entries().iterator();
while (iterator.hasNext()) {
Map.Entry hash = iterator.next();
// 哈希名,即表名
byte[] hashName = redisTemplate.getStringSerializer().serialize(hash.getKey());
Map hashValues = hash.getValue();
Iterator it = hashValues.entrySet().iterator();
// 将元素序列化后缓存,即表的多条哈希记录
Map hashes = new HashMap();
while (it.hasNext()) {
// hash中一条key-value记录
Map.Entry 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
*/
public void batchHashMapSet(Map dataMap) {
// 设置5秒超时时间
redisTemplate.expire("max", 25, TimeUnit.SECONDS);
redisTemplate.executePipelined(new RedisCallback() {
@Override
public List doInRedis(RedisConnection connection) throws DataAccessException {
Iterator iterator = dataMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry hash = iterator.next();
// 哈希名,即表名
byte[] hashName = redisTemplate.getStringSerializer().serialize(hash.getKey());
Map hashValues = hash.getValue();
Iterator it = hashValues.entrySet().iterator();
// 将元素序列化后缓存,即表的多条哈希记录
Map hashes = new HashMap();
while (it.hasNext()) {
// hash中一条key-value记录
Map.Entry 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
* @see RedisUtils*.batchHashMapSet()*
*/
public void batchHashMapListSet(List list) {
// 设置5秒超时时间
redisTemplate.expire("max", 25, TimeUnit.SECONDS);
redisTemplate.executePipelined(new RedisCallback() {
@Override
public List doInRedis(RedisConnection connection) throws DataAccessException {
for (Map dataMap : list) {
Iterator iterator = dataMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry hash = iterator.next();
// 哈希名,即表名
byte[] hashName = redisTemplate.getStringSerializer().serialize(hash.getKey());
Map hashValues = hash.getValue();
Iterator it = hashValues.entrySet().iterator();
// 将元素序列化后缓存,即表的多条哈希记录
Map hashes = new HashMap();
while (it.hasNext()) {
// hash中一条key-value记录
Map.Entry 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
先写一个测试类,这个类中的接口需要有相关权限的用户才能访问
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权限才能访问
@PreAuthorize("hasAuthority('admin')")
@GetMapping("/test1")
public String test() {
return "测试";
}
//该接口需要user权限才能访问
@PreAuthorize("hasAuthority('user')")
@GetMapping("/test2")
public String demo() {
return "这是demo接口";
}
}
2. 使用root账户登录
访问test1接口,没有问题
访问test2接口,被禁止,权限错误的信息也可以自定义,本项目未自定义
3. 使用user账户登录
同样的登录成功的信息
访问test1接口,被禁止
访问test2接口,访问成功
redis中登录成功存储的jwt,有两个账号登录,所以有两条
总结
后端生成的jwt应该存储在redis中,每次处理请求时都应检验一次用户的权限信息,以及jwt是否过期,在前后端分离的项目中,前端登录后,后端生成的jwt会在请求头中设置,然后前端拿到后,会存储在localstorage中(只是举例,想存哪儿随意),在前端发起请求时,也会携带着token到后端进行检验。