系统中的一个小需求,一个用户在我的浏览器登录后,其他人用我的账号在别处登录,将我的浏览器的账号token做一个失效处理,达到只有一个用户登录的目的。
主要是利用redis去存储 用户id 和token的绑定
在登陆的时候根据id去覆盖这个token,就能实现在其他地方登陆后,原来的登陆位置token失效
1. 配置WebSecurityConfiguration
主要是增加一个sessionManagement
配置.maximumSessions(1) // 限制同一个用户只能有一个会话
.maxSessionsPreventsLogin(false) //如果设置为true,当达到最大会话数时,拒绝新的登录
/**
* 服务安全相关配置
*/
@EnableWebSecurity
public class WebSecurityConfiguration {
/**
* spring security 默认的安全策略
* @param http security注入点
* @return SecurityFilterChain
* @throws Exception
*/
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers("/token/*")
.permitAll()// 开放自定义的部分端点
.anyRequest()
.authenticated())
.headers()
.frameOptions()
.sameOrigin()// 避免iframe同源无法登录
.and()
.apply(new FormIdentityLoginConfigurer())// 表单登录个性化
.and()
.sessionManagement(sessionManagement -> sessionManagement
.maximumSessions(1) // 限制同一个用户只能有一个会话
.maxSessionsPreventsLogin(false) // 如果设置为true,当达到最大会话数时,拒绝新的登录
);
// 处理 UsernamePasswordAuthenticationToken
http.authenticationProvider(new PigDaoAuthenticationProvider());
return http.build();
}
/**
* 暴露静态资源
* @param http
* @return
* @throws Exception
*/
@Bean
@Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
// ...
// 省略
}
}
2.在实现了OAuth2AuthorizationService的类中
存储redis和利用redis做判断
@RequiredArgsConstructor
public class PigRedisOAuth2AuthorizationService implements OAuth2AuthorizationService {
private final RedisTemplate<String, Object> redisTemplate;
@Override
public void save(OAuth2Authorization authorization) {
Assert.notNull(authorization, "authorization cannot be null");
if (isState(authorization)) {
// ....
// 省略
}
if (isCode(authorization)) {
// ....
// 省略
}
if (isRefreshToken(authorization)) {
// ....
// 省略
}
if (isAccessToken(authorization)) {
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
String userId = authorization.getPrincipalName(); // 假设用户ID是principalName
// 1. 获取当前用户的旧token
String oldTokenKey = (String) redisTemplate.opsForValue().get(USER_TOKEN + userId);
// 2. 移除旧token的授权信息
if (oldTokenKey != null && !oldTokenKey.equals(accessToken.getTokenValue())) {
redisTemplate.delete(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, oldTokenKey));
}
// 3. 将新的 token 和用户ID关联存储
redisTemplate.opsForValue().set(USER_TOKEN + userId, accessToken.getTokenValue());
long between = ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt());
redisTemplate.setValueSerializer(RedisSerializer.java());
redisTemplate.opsForValue().set(buildKey(OAuth2ParameterNames.ACCESS_TOKEN, accessToken.getTokenValue()),
authorization, between, TimeUnit.SECONDS);
}
}
private static boolean isAccessToken(OAuth2Authorization authorization) {
return Objects.nonNull(authorization.getAccessToken());
}
}