SpringSecurity+jwt+redis基于数据库登录认证的实现

 更新时间:2023年09月25日 10:29:36   作者:笑的像个child  
本文主要介绍了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 random随机数的用法及常见应用场景

    java random随机数的用法及常见应用场景

    这篇文章主要给大家介绍了关于java random随机数的用法及常见应用场景的相关资料,Java中的Random类是用来生成伪随机数的工具类,它可以用来生成随机的整数、浮点数和布尔值,需要的朋友可以参考下
    2023-11-11
  • JAVA使用反射读取注解方式

    JAVA使用反射读取注解方式

    在Java中,反射机制不仅可以操作对象的属性和方法,还可以读取注解,本文介绍了反射读取注解的步骤,包括定义注解、使用注解、通过反射获取注解信息及处理注解信息,通过具体代码例子和运行结果,详细解释了注解的定义、使用和反射获取注解的过程
    2024-09-09
  • Java基于正则表达式实现查找匹配的文本功能【经典实例】

    Java基于正则表达式实现查找匹配的文本功能【经典实例】

    这篇文章主要介绍了Java基于正则表达式实现查找匹配的文本功能,结合具体实例形式分析了java正则查找、字符串遍历、group分组相关操作技巧,需要的朋友可以参考下
    2017-04-04
  • 如何解决Springboot Dao注入失败的问题

    如何解决Springboot Dao注入失败的问题

    这篇文章主要介绍了如何解决Spring boot Dao注入失败的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • JAVA“无法验证证书。将不执行该应用程序。”提示解决办法

    JAVA“无法验证证书。将不执行该应用程序。”提示解决办法

    这篇文章主要给大家介绍了关于JAVA“无法验证证书,将不执行该应用程序”提示的解决办法,要解决Java无法验证证书的问题,可以尝试下本文的方法,需要的朋友可以参考下
    2024-03-03
  • Spring Security 控制授权的方法

    Spring Security 控制授权的方法

    本篇文章主要介绍了Spring Security 控制授权的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • mybatis中resultMap 标签的使用教程

    mybatis中resultMap 标签的使用教程

    resultMap 标签用来描述如何从数据库结果集中来加载对象,这篇文章重点给大家介绍mybatis中resultMap 标签的使用,感兴趣的朋友一起看看吧
    2018-07-07
  • idea取消git托管方式(删除git文件)

    idea取消git托管方式(删除git文件)

    遇到Git文件传输错误或打不开问题时,需进行Git清理和重新配置,首先删除项目中的.git文件和.gitignore文件,若找不到,检查是否为隐藏文件,接着在IDE的设置中,删除所有版本控制模块,最后,若想重新使用Git,可在设置里重新启用并配置,连接至GitHub仓库即可恢复正常
    2024-10-10
  • java打印从1到100的值(break,return断句)

    java打印从1到100的值(break,return断句)

    java 先写一个程序,打印从1到100的值。之后修改程序,通过使用break关键词,使得程序在打印到98时退出。然后尝试使用return来达到相同的目的
    2017-02-02
  • Spring Boot 自定义 Shiro 过滤器无法使用 @Autowired问题及解决方法

    Spring Boot 自定义 Shiro 过滤器无法使用 @Autowired问题及解决方法

    这篇文章主要介绍了Spring Boot 自定义 Shiro 过滤器无法使用 @Autowired问题及解决方法 ,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-06-06

最新评论