SpringSecurity+jwt+redis基于数据库登录认证的实现
前言
本项目主要是一个SpringSecurity+jwt+redis基于数据库登录认证的Demo,其中也涉及到自定义的过滤器和处理器,希望能对大家有帮助,本文中所有代码正常情况下可以直接复制使用。
一、前期准备
1. 创建项目
勾选需要用到的框架
2. 引入相关依赖
提示:有三个依赖需要手动添加,其余的在创建项目时就生成了
<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> <!--以上是创建项目时勾选直接生成的,下面是手动添加的--> <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. 创建数据库并生成数据
数据库名为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<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来查询数据库中是否存在这个用户
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会调用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; 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验证成功则会返回一个带有授权信息的身份验证对象。如果身份验证失败,则应返回一个未通过的身份验证对象。
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<T> 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 <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的签发时间 .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键名数组 */ 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); } /** * 哈希获取数据 * * @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中一条或多条数据 * * @param key Redis键 * @param hashKeys 哈希键名数组 * @return 删除数量 */ 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); } /** * 列表-获取指定范围数据 * * @param key Redis键名 * @param start 开始行号 * @param end 结束行号 * @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 结束序号 * @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
先写一个测试类,这个类中的接口需要有相关权限的用户才能访问
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到后端进行检验。
到此这篇关于SpringSecurity+jwt+redis基于数据库登录认证的实现的文章就介绍到这了,更多相关SpringSecurity+jwt+redis登录认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
java打印从1到100的值(break,return断句)
java 先写一个程序,打印从1到100的值。之后修改程序,通过使用break关键词,使得程序在打印到98时退出。然后尝试使用return来达到相同的目的2017-02-02Spring Boot 自定义 Shiro 过滤器无法使用 @Autowired问题及解决方法
这篇文章主要介绍了Spring Boot 自定义 Shiro 过滤器无法使用 @Autowired问题及解决方法 ,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下2019-06-06
最新评论