SpringSecurity+jwt+redis基于數(shù)據(jù)庫(kù)登錄認(rèn)證的實(shí)現(xiàn)
前言
本項(xiàng)目主要是一個(gè)SpringSecurity+jwt+redis基于數(shù)據(jù)庫(kù)登錄認(rèn)證的Demo,其中也涉及到自定義的過濾器和處理器,希望能對(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)
可以按照我的來,也可自行決定
二、具體實(shí)現(xiàn)
1. 編寫配置文件
提示:修改數(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ù)值表示沒有限制) max-wait: -1 # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制) max-idle: 8 # 連接池中的最大空閑連接 min-idle: 0 # 連接池中的最小空閑連接 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.javasec.bean
2. 創(chuàng)建實(shí)體類
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; /** * 角色名稱 */ private String name; /** * 角色名稱中文 */ 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; /** * 用戶是否開啟 */ 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ù)庫(kù)中是否存在這個(gè)用戶
RoleDao主要是用來查詢登錄用戶的角色列表(一個(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. 編寫springSecurity相關(guān)處理器
編寫UserServices
進(jìn)行身份驗(yàn)證之前,Spring Security會(huì)調(diào)用loadUserByUsername()方法來獲取用戶信息。該方法通常用于在數(shù)據(jù)庫(kù)中查詢用戶信息,然后將其封裝在UserDetails接口的實(shí)現(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; } }
登錄處理器
這個(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過濾器
該過濾器可以攔截每一次請(qǐng)求,并驗(yàn)證header中是否存在token,驗(yàn)證成功則會(huì)通過UsernamePasswordAuthenticationToken傳給一個(gè)authentication provider驗(yàn)證成功則會(huì)返回一個(gè)帶有授權(quán)信息的身份驗(yàn)證對(duì)象。如果身份驗(yàn)證失敗,則應(yīng)返回一個(gè)未通過的身份驗(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. 本文所用的工具類
響應(yīng)類
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工具類
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天過期 // .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是否過期 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; } /** * 寫入緩存設(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 開始行號(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 開始序號(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配置類
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); //關(guān)閉csrf http.csrf().disable(); //開啟過濾器,并將其置于UsernamePasswordAuthenticationFilter過濾器之前 http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }
三、測(cè)試
1. 測(cè)試類
有兩個(gè)賬戶,一個(gè)是root,一個(gè)是user,密碼都是123456
先寫一個(gè)測(cè)試類,這個(gè)類中的接口需要有相關(guān)權(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 "測(cè)試"; } //該接口需要user權(quán)限才能訪問 @PreAuthorize("hasAuthority('user')") @GetMapping("/test2") public String demo() { return "這是demo接口"; } }
2. 使用root賬戶登錄
訪問test1接口,沒有問題
訪問test2接口,被禁止,權(quán)限錯(cuò)誤的信息也可以自定義,本項(xiàng)目未自定義
3. 使用user賬戶登錄
同樣的登錄成功的信息
訪問test1接口,被禁止
訪問test2接口,訪問成功
redis中登錄成功存儲(chǔ)的jwt,有兩個(gè)賬號(hào)登錄,所以有兩條
總結(jié)
后端生成的jwt應(yīng)該存儲(chǔ)在redis中,每次處理請(qǐng)求時(shí)都應(yīng)檢驗(yàn)一次用戶的權(quán)限信息,以及jwt是否過期,在前后端分離的項(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)文章希望大家以后多多支持腳本之家!
- SpringSecurity6.0 如何通過JWTtoken進(jìn)行認(rèn)證授權(quán)
- springSecurity自定義登錄接口和JWT認(rèn)證過濾器的流程
- SpringSecurity+jwt+captcha登錄認(rèn)證授權(quán)流程總結(jié)
- SpringSecurity+Redis+Jwt實(shí)現(xiàn)用戶認(rèn)證授權(quán)
- SpringBoot整合SpringSecurity和JWT和Redis實(shí)現(xiàn)統(tǒng)一鑒權(quán)認(rèn)證
- SpringBoot+SpringSecurity+JWT實(shí)現(xiàn)系統(tǒng)認(rèn)證與授權(quán)示例
- SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
- SpringSecurity整合jwt權(quán)限認(rèn)證的全流程講解
- SpringSecurity構(gòu)建基于JWT的登錄認(rèn)證實(shí)現(xiàn)
- SpringSecurity JWT基于令牌的無狀態(tài)認(rèn)證實(shí)現(xiàn)
相關(guān)文章
spring cloud zuul修改請(qǐng)求url的方法
這篇文章主要給大家介紹了關(guān)于spring cloud zuul修改請(qǐng)求url的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring cloud具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2017-09-09Java使用pulsar-flink-connector讀取pulsar catalog元數(shù)據(jù)代碼剖析
這篇文章主要介紹了Java使用pulsar-flink-connector讀取pulsar catalog元數(shù)據(jù)代碼剖析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08springboot全局配置文件與多環(huán)境配置的全過程
SpringBoot項(xiàng)目在多環(huán)境配置上表現(xiàn)的非常優(yōu)秀,只需要非常簡(jiǎn)單的操作就可以完成配置,下面這篇文章主要給大家介紹了關(guān)于springboot全局配置文件與多環(huán)境配置的相關(guān)資料,需要的朋友可以參考下2021-12-12一文教你如何通過三級(jí)緩存解決Spring循環(huán)依賴
這篇文章主要介紹了如何通過三級(jí)緩存解決?Spring?循環(huán)依賴,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考價(jià)值,需要的朋友可以參考下2023-07-07Spring Boot前后端分離開發(fā)模式中的跨域問題及解決方法
本文介紹了解決Spring Boot前端Vue跨域問題的實(shí)戰(zhàn)經(jīng)驗(yàn),并提供了后端和前端的配置示例,通過配置后端和前端,我們可以輕松解決跨域問題,實(shí)現(xiàn)正常的前后端交互,需要的朋友可以參考下2023-09-09Java面試題及答案集錦(基礎(chǔ)題122道,代碼題19道)
本文是小編收集整理的關(guān)于java基礎(chǔ)面試題及答案集錦,基礎(chǔ)題目有122道,代碼題目有19道,非常不錯(cuò),值得收藏,需要的朋友參考下2017-01-01SpringBoot中實(shí)現(xiàn)登錄攔截器的代碼實(shí)例
這篇文章主要介紹了SpringBoot中實(shí)現(xiàn)登錄攔截器的代碼實(shí)例,對(duì)于管理系統(tǒng)或其他需要用戶登錄的系統(tǒng),登錄驗(yàn)證都是必不可少的環(huán)節(jié),在SpringBoot開發(fā)的項(xiàng)目中,通過實(shí)現(xiàn)攔截器來實(shí)現(xiàn)用戶登錄攔截并驗(yàn)證,需要的朋友可以參考下2023-10-10