Spring Security(四)基于redis的分布式认证鉴权解决方案

项目介绍

方案介绍

​ 基于数据库的认证鉴权方式,登录之后会产生一个session存储在内存之中,然后将sessionId返回给客户端,后续客户端请求资源时,会将sessionId携带上,服务端根据sessionId判断用户的登录状态,如果用户登录过,则放行,如果没有登录,则会被拦截,跳转到登录页面重新登录,但这种将登录状态以及信息放在内存中的方式会带来两个问题:

  1. 由于session产生后放在服务器的内存之中,服务器因为某种原因(宕机或者更新)重启之后,则所有的session都会丢失,那么登录过的所有用户都需要重新登录;
  2. 分布式场景下,可能登录是在服务器A上,那么session保存在服务器A上,下一个请求,可能由服务器B处理,那么服务器B上没有这个session,就需要重新登录;

通过将session保存到redis中实现session共享解决分布式场景下的以上两个问题,

Browser Server Redis 使用用户名、密码登录 验证用户名密码,并产生SecuriryContext 将SecuriryContext success sessionId 携带sessionId请求资源 根据sessionId查询SecuriryContext SecuriryContext 处理请求 返回相应 Browser Server Redis
项目结构

项目源码:https://github.com/xdouya/Spring-Security-demo/tree/master/04-mybatis-redis-security
在这里插入图片描述

项目构建

数据表创建以及用户数据导入
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `username` varchar(50) NOT NULL,
  `password` varchar(500) NOT NULL,
  `enabled` tinyint(1) NOT NULL,
  PRIMARY KEY (`username`)
) ENGINE=InnoDB;

INSERT  IGNORE INTO `users` VALUES ('admin','{bcrypt}$2a$10$SAqQq0WEQYRA4etZpRa6e.Kew0sKKtC/ahFrSZXS1iHsy5EhZqLsa',1),('user','{bcrypt}$2a$10$SAqQq0WEQYRA4etZpRa6e.Kew0sKKtC/ahFrSZXS1iHsy5EhZqLsa',1),('vip','{bcrypt}$2a$10$SAqQq0WEQYRA4etZpRa6e.Kew0sKKtC/ahFrSZXS1iHsy5EhZqLsa',1);

DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities` (
  `username` varchar(50) NOT NULL,
  `authority` varchar(50) NOT NULL,
  UNIQUE KEY `ix_auth_username` (`username`,`authority`),
  CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
) ENGINE=InnoDB;

INSERT  IGNORE INTO `authorities` VALUES ('admin','ROLE_admin'),('user','ROLE_user'),('vip','ROLE_vip');

这里的这个users表和authorities表的字段没有强制要求,只要后续查询的时候能对应上就可以了;

Maven依赖
  • pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-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.1.3</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</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>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
Spring Security配置
  • Srping Security配置
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 配置拦截器保护请求
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/vip/**").hasRole("vip")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated()
                .and().formLogin()
                .and().httpBasic();
    }

    /**
     * 根据自动匹配密码编码器
     * @return PasswordEncoder
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}
  • Redis配置
/**
 * @author caiwl
 * @date 2020/8/21 16:58
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(genericJackson2JsonRedisSerializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
  • spring-session配置
@Configurable
@EnableRedisHttpSession
public class SessionConfig {
}
  • MyUserDetailServiceImpl,通过实现UserDetailsService#loadUserByUsername方法自定用户信息查询
/**
 * 自定义UserDetailsService
 * @author caiwl
 * @date 2020/8/20 17:06
 */
@Service
public class MyUserDetailServiceImpl implements UserDetailsService {

    private UserDao userDao;

    @Autowired
    public MyUserDetailServiceImpl(UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("整合mybatis 查询用户信息");
        return userDao.loadUserByUsername(username);
    }
}
  • UserDao,用户数据访问接口
/**
 * @author caiwl
 * @date 2020/8/20 17:09
 */
public interface UserDao {
    /**
     * 根据用户名查询用户信息
     * @param username 用户名
     * @return 用户信息
     */
    UserPo loadUserByUsername(String username);
}
  • UserMapper.xml, 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="org.dy.security.dao.UserDao">
	<resultMap type="org.dy.security.entiy.UserPo" id="UserMap">
		<id column="username" property="username"/>
		<result column="password" property="password"/>	
		<collection property="authorities" ofType="org.dy.security.entiy.RolePo">
			<id column="username" property="username"/>
			<result column="authority" property="authority"/>
		</collection>		
	</resultMap>

	<select id="loadUserByUsername" resultMap="UserMap">
		select 
			users.username, users.password, authorities.authority
		from 
			users left join authorities on users.username = authorities.username
		 where users.username=#{username}
	</select>
</mapper>
  • UserPo,用户信息
/**
 * @author caiwl
 * @date 2020/8/20 17:08
 */
@Data
public class UserPo implements UserDetails, Serializable {

    private static final long serialVersionUID = 1L;

    private String username;
    private String password;
    private List<RolePo> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}
  • RolePo,角色信息
/**
 * @author caiwl
 * @date 2020/8/20 17:09
 */
@Data
public class RolePo implements GrantedAuthority {

    private static final long serialVersionUID = 1L;
    private String username;
    private String authority;

    @Override
    public String getAuthority() {
        return authority;
    }
}
  • HelloController,控制器接口
/**
 * @author caiwl
 * @date 2020/8/9 14:05
 */
@RestController
public class HelloController {

    @GetMapping("/")
    public String hello(){
        return "hello, welcome";
    }

    @GetMapping("/admin/hello")
    public String helloAdmin(){
        return "hello admin";
    }

    @GetMapping("/vip/hello")
    public String helloVip(){
        return "hello vip";
    }

    @GetMapping("/user/hello")
    public String helloUser(){
        return "hello user";
    }
}

项目测试

访问http://localhost:8080/,输入用户名,密码admin:088114访问,可以发现session保存在redis里面了;
在这里插入图片描述

思考

​ 以上介绍的都是基于session来记录用户的登录状态,这种基于session的方式有如下几个问题

  1. 每一个用户都会生成一个session,当用户量巨大的时候,巨量的session会占用巨大的服务端内存
  2. 可以通过将session缓存在redis中共享,解决分布式场景下的登录状态记录,但是一旦redis宕机后,会造成所有用户都无法访问任何需要认证的资源;

下一节,介绍Spring Security(五)基于JWT令牌的认证鉴权的这种无状态的解决方法来规避以上两个问题

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Security是一个基于Spring框架的安全框架,可以用于实现各种安全需求,如身份认证、授权、攻击防护等。而Redis是一种高性能的内存数据库,可以用于缓存、消息队列、分布式锁等场景。将Spring SecurityRedis整合起来,可以提高系统的性能和可扩展性。 具体实现方式可以采用Spring Security提供的缓存机制,将用户信息、权限信息等存储到Redis中,以减少数据库的访问次数。可以使用Spring Data Redis来操作Redis,通过配置RedisTemplate来实现对Redis的访问。同时,可以使用Spring Security提供的RedisCacheManager来管理缓存,实现缓存的自动刷新和失效。 在实现过程中,需要注意Redis的数据结构和数据类型,如字符串、哈希表、列表、集合等,以及缓存的过期时间、缓存的清理策略等。同时,需要考虑Redis的高可用性和数据一致性,如使用Redis Sentinel或Cluster来实现主从复制和故障转移,使用Redis事务或Lua脚本来保证数据的原子性操作等。 总之,Spring Security整合Redis可以提高系统的性能和可扩展性,但需要考虑到Redis的特性和使用方式,以保证系统的稳定性和安全性。 ### 回答2: Spring Security 是一个安全框架,用于为基于 Java 的企业应用程序提供身份验证和授权。Redis是一个高性能的内存数据存储系统,用于缓存数据。将Spring Security 整合redis可以提高系统的性能,加快缓存数据的读取速度,同时也能提高系统的安全性。 Spring SecurityRedis整合的步骤: 1. 首先,需要添加spring-data-redis依赖,包括spring-data-redis和lettuce-core 2. 配置RedisConnectionFactory,用于创建链接Redis的对象,可以通过JedisPool类实现,也可以使用RedisStandaloneConfiguration构造器。 3. 配置RedisTemplate,用于操作Redis数据库,设置key和value的序列化方式。 4. 通过Spring Security的CacheManager配置,将Redis用于存储用户认证的缓存,以便快速地进行身份验证,从而提高系统的性能。 5. 可以使用spring-session来管理用户会话,将用户信息存储在Redis中,从而在多个服务器上实现用户会话共享。 综上,通过Spring SecurityRedis的整合,可以达到缓存优化、用户认证加速、会话共享等多个方面的优化,提高系统的性能和安全性。但是,由于Redis是基于内存,需要特别注意内存使用情况,防止内存泄漏导致系统崩溃。同时,还需要使用加密技术对缓存的敏感数据进行保护,防止泄露。 ### 回答3: Spring Security是一个用于安全身份验证和授权的框架,具有强大的功能和可扩展性。而Redis是一种优秀的NoSQL数据库,可以提供快速、高性能和高可用性的数据存储和访问。将Spring SecurityRedis整合起来,可以使得身份验证和授权更加高效和可靠。 Spring Security整合Redis的主要步骤如下: 1. 添加Spring Data Redis依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-data</artifactId> </dependency> ``` 2. 配置RedisTemplate 在Spring Boot配置文件中添加以下内容: ``` spring: redis: host: localhost port: 6379 # RedisTemplate配置 @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 设置key的序列化方式 template.setKeySerializer(new StringRedisSerializer()); // 设置value的序列化方式 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } ``` 3. 实现UserDetailsService 在Spring Security中,需要实现UserDetailsService接口来获取用户信息,我们可以从Redis中获取存储的用户信息。 ``` @Service public class RedisUserDetailsService implements UserDetailsService { private final RedisTemplate<String, Object> redisTemplate; public RedisUserDetailsService(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String key = "user:" + username; boolean exists = redisTemplate.hasKey(key); if (!exists) { throw new UsernameNotFoundException("用户不存在"); } Map<String, Object> map = (Map<String, Object>) redisTemplate.opsForValue().get(key); User user = new User(); user.setUsername(map.get("username").toString()); user.setPassword(map.get("password").toString()); List<GrantedAuthority> authorities = new ArrayList<>(); List<String> roles = (List<String>) map.get("roles"); roles.forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); }); user.setAuthorities(authorities); return user; } } ``` 4. 配置认证和授权 在Spring Security配置文件中,配置认证和授权的方式和使用传统方式一样,只是在获取用户信息的方法上,使用RedisUserDetailsService替换默认的UserDetailsService即可。 ``` @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RedisTemplate<String, Object> redisTemplate; @Autowired private RedisUserDetailsService redisUserDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 设置UserDetailsService和密码加密方式 auth.userDetailsService(redisUserDetailsService).passwordEncoder(passwordEncoder()); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() .and().formLogin(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public RedisUserDetailsService redisUserDetailsService() { return new RedisUserDetailsService(redisTemplate); } @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } ``` 通过以上步骤,就可以实现Spring SecurityRedis的整合,使得身份验证和授权更加高效和可靠。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值