基于redis的分布式认证鉴权解决方案
点击上方蓝色字体,选择“标星公众号”
优质文章,第一时间送达
项目介绍
方案介绍
基于数据库的认证鉴权方式,登录之后会产生一个session存储在内存之中,然后将sessionId返回给客户端,后续客户端请求资源时,会将sessionId携带上,服务端根据sessionId判断用户的登录状态,如果用户登录过,则放行,如果没有登录,则会被拦截,跳转到登录页面重新登录,但这种将登录状态以及信息放在内存中的方式会带来两个问题:
由于session产生后放在服务器的内存之中,服务器因为某种原因(宕机或者更新)重启之后,则所有的session都会丢失,那么登录过的所有用户都需要重新登录;
分布式场景下,可能登录是在服务器A上,那么session保存在服务器A上,下一个请求,可能由服务器B处理,那么服务器B上没有这个session,就需要重新登录;
通过将session保存到redis中实现session共享解决分布式场景下的以上两个问题,
项目结构
项目源码: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的方式有如下几个问题
每一个用户都会生成一个session,当用户量巨大的时候,巨量的session会占用巨大的服务端内存
可以通过将session缓存在redis中共享,解决分布式场景下的登录状态记录,但是一旦redis宕机后,会造成所有用户都无法访问任何需要认证的资源;
————————————————
版权声明:本文为CSDN博主「dy豆芽」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/douya2016/article/details/108222273
锋哥最新SpringCloud分布式电商秒杀课程发布
👇👇👇
👆长按上方微信二维码 2 秒
感谢点赞支持下哈