基于redis的分布式認(rèn)證鑒權(quán)解決方案
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
項(xiàng)目介紹
方案介紹
基于數(shù)據(jù)庫的認(rèn)證鑒權(quán)方式,登錄之后會(huì)產(chǎn)生一個(gè)session存儲(chǔ)在內(nèi)存之中,然后將sessionId返回給客戶端,后續(xù)客戶端請求資源時(shí),會(huì)將sessionId攜帶上,服務(wù)端根據(jù)sessionId判斷用戶的登錄狀態(tài),如果用戶登錄過,則放行,如果沒有登錄,則會(huì)被攔截,跳轉(zhuǎn)到登錄頁面重新登錄,但這種將登錄狀態(tài)以及信息放在內(nèi)存中的方式會(huì)帶來兩個(gè)問題:
由于session產(chǎn)生后放在服務(wù)器的內(nèi)存之中,服務(wù)器因?yàn)槟撤N原因(宕機(jī)或者更新)重啟之后,則所有的session都會(huì)丟失,那么登錄過的所有用戶都需要重新登錄;
分布式場景下,可能登錄是在服務(wù)器A上,那么session保存在服務(wù)器A上,下一個(gè)請求,可能由服務(wù)器B處理,那么服務(wù)器B上沒有這個(gè)session,就需要重新登錄;
通過將session保存到redis中實(shí)現(xiàn)session共享解決分布式場景下的以上兩個(gè)問題,

項(xiàng)目結(jié)構(gòu)
項(xiàng)目源碼:https://github.com/xdouya/Spring-Security-demo/tree/master/04-mybatis-redis-security

項(xiàng)目構(gòu)建
數(shù)據(jù)表創(chuàng)建以及用戶數(shù)據(jù)導(dǎo)入
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');
這里的這個(gè)users表和authorities表的字段沒有強(qiáng)制要求,只要后續(xù)查詢的時(shí)候能對應(yīng)上就可以了;
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 {
/**
* 配置攔截器保護(hù)請求
*/
@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();
}
/**
* 根據(jù)自動(dòng)匹配密碼編碼器
* @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,通過實(shí)現(xiàn)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,用戶數(shù)據(jù)訪問接口
/**
* @author caiwl
* @date 2020/8/20 17:09
*/
public interface UserDao {
/**
* 根據(jù)用戶名查詢用戶信息
* @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";
}
}
項(xiàng)目測試
訪問http://localhost:8080/,輸入用戶名,密碼admin:088114訪問,可以發(fā)現(xiàn)session保存在redis里面了;

思考
以上介紹的都是基于session來記錄用戶的登錄狀態(tài),這種基于session的方式有如下幾個(gè)問題
每一個(gè)用戶都會(huì)生成一個(gè)session,當(dāng)用戶量巨大的時(shí)候,巨量的session會(huì)占用巨大的服務(wù)端內(nèi)存
可以通過將session緩存在redis中共享,解決分布式場景下的登錄狀態(tài)記錄,但是一旦redis宕機(jī)后,會(huì)造成所有用戶都無法訪問任何需要認(rèn)證的資源;
————————————————
版權(quán)聲明:本文為CSDN博主「dy豆芽」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/douya2016/article/details/108222273
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
