分布式:曾经都集中部署的系统,现在往往会进行拆分,并部署在不同的服务器上,以增大系统容量、增强系统可用性。
单点登录(SSO)就是用户在首次登录之后,再访问其他功能模块就不再需要重新登录。
Cookie:一些存储在客户端电脑上的信息。首次客户端访问服务器的时候,服务器都会生成Cookie,之后的交互都会携带该Cookie进行交互。因为存储在客户端的原因,导致Cookie就相对不安全。
Session:一些存储在服务端的信息。客户端在与服务器之间的交互都会建立Session。由于存储在服务器上,故服务器宕机、服务重启都会导致信息的丢失,当然也不可避免地影响服务器的性能。
JWT实现机制就是通过登陆时生成Token令牌,之后前后端通过该Token进行交互,每次cookie里带上Token即可。实现见Spring Security整合JWT实现单点登录
由于Session保存在服务器端,所以在现在分布式的系统中,如果没做好Session共享问题,很容易造成数据的丢失。
Session共享实现方式
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-data-redisartifactId>
dependency>
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private AuthenticationConfiguration authenticationConfiguration;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private CustomerExpiredSessionStrategy sessionStrategy;
@Autowired
private CustomerLogoutHandler logoutHandler;
@Bean
/**
* 新版本配置
*/
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//自定义登录页面地址
http.formLogin().loginPage("/login.html")
.loginProcessingUrl("/login") //登录的请求地址
.successForwardUrl("/session") //成功登录之后跳转的地址
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/register", "/toLogin").permitAll() // 放行哪些请求
.anyRequest().authenticated() // 除了上述请求都进行拦截校验
.and().userDetailsService(userDetailsService)// 设置后 从数据库查询数据
.addFilter(new TokenLoginFilter(authenticationManager(), redisTemplate)) // 自定义在登录之后存权限
.addFilter(new TokenAuthenticationFilter(authenticationManager(), redisTemplate))// 自定义验证过滤器 session是否存在
.logout().addLogoutHandler(logoutHandler)// 自定义退出
.and().httpBasic();
// 这里限制最多同时在线1个用户,否则就强制下线
http.sessionManagement().maximumSessions(1)
.expiredSessionStrategy(sessionStrategy);
return http.build();
}
@Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// 哪些web请求可以直接放行 不需要拦截
return (web) -> web.ignoring().antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/v2/**", "/api/**", "/register.html", "/login.html");
}
}
@EnableCaching //开启缓存
@Configuration //配置类
@EnableRedisHttpSession //开启Spring Session 帮我们管理
public class RedisConfig extends CachingConfigurerSupport {
}
@Component
public class CustomerExpiredSessionStrategy implements SessionInformationExpiredStrategy {
@Autowired
private RedisTemplate redisTemplate;
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
ResponseUtil.out(event.getResponse(), R.ok().message("您的账号已经在其他地方登录").code(ResultCode.UNAUTHORIZED));
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth){
HttpSession session = req.getSession();
SecurityUser user = (SecurityUser) auth.getPrincipal();
RedisBean redisBean = new RedisBean(user.getUsername(),user.getPermissionValueList());
redisTemplate.opsForValue().set(session.getId(), redisBean); // 以Session : admin 、 权限 存入redis
ResponseUtil.out(res, R.ok().message("登录成功"));
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
HttpSession session = request.getSession();
String sessionId = session.getId();
// 从redis中获取权限
Object object = redisTemplate.opsForValue().get(sessionId);
if (Objects.nonNull(object)) {
RedisBean redisBean = (RedisBean) object;
List<String> permissionValueList = redisBean.getPermissionValueList();
Collection<GrantedAuthority> authorities = new ArrayList<>(); // 把权限封装成指定形式的集合
for (String permissionValue : permissionValueList) {
if (Strings.isNullOrEmpty(permissionValue)) {
continue;
}
// SimpleGrantedAuthority 权限内容为String 将String类型的权限存入SimpleGrantedAuthority类
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
// 把有用信息塞入UsernamePasswordAuthenticationToken后返回
// UsernamePasswordAuthenticationToken令牌存了sessionid 和 权限
return new UsernamePasswordAuthenticationToken(redisBean.getUsername(), null, authorities);
}
return null;
}