1. 程式人生 > >Spring Security 強制退出指定使用者

Spring Security 強制退出指定使用者

應用場景

最近社群總有人發文章帶上小廣告,嚴重影響社群氛圍,好氣!對於這種型別的使用者,就該永久拉黑!

社群的安全框架使用了 spring-security 和 spring-session,登入狀態 30 天有效,session 資訊是存在 redis 中,如何優雅地處理這些不老實的使用者呢?

首先,簡單劃分下使用者的許可權:

  • 管理員(ROLE_MANAGER):基本操作 + 管理操作
  • 普通使用者(ROLE_USER):基本操作
  • 拉黑使用者(ROLE_BLACK):不允許登入

然後,拉黑指定使用者(ROLE_USER -> ROLE_BLACK),再強制該使用者退出即可(刪除該使用者在 redis 中 session 資訊)。

專案相關依賴及配置

Maven 依賴

        <!-- 安全 Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- Redis -->
        <dependency
>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- Spring Session Redis --> <dependency> <groupId>org.springframework.session</groupId
>
<artifactId>spring-session-data-redis</artifactId> </dependency>

Spring Session 策略配置 application.yml

# 此處省略 redis 連線相關配置
spring:
  session:
    store-type: redis

Spring Security 配置程式碼示例

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .antMatchers("/manager/**").hasAnyRole(RoleEnum.MANAGER.getMessage())
                .anyRequest().permitAll()
                .and().formLogin().loginPage("/login").permitAll()
                .and().logout().permitAll()
                .and().csrf().disable();
    }

}

強制退出指定給使用者介面

import com.spring4all.bean.ResponseBean;
import com.spring4all.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RestController
@AllArgsConstructor
public class UserManageApi {

    private final FindByIndexNameSessionRepository<? extends Session> sessionRepository;

    private final RedisOperationsSessionRepository redisOperationsSessionRepository;

    private final UserService userService;

    /**
     * 管理指定使用者退出登入
     * @param userId 使用者ID
     * @return 使用者 Session 資訊
     */
    @PreAuthorize("hasRole('MANAGER')")
    @GetMapping("/manager/logout/{userId}")
    public ResponseBean data(@PathVariable() Long userId){
        // 查詢 PrincipalNameIndexName(Redis 使用者資訊的 key),結合自身業務邏輯來實現
        String indexName = userService.getPrincipalNameIndexName(userId);
        // 查詢使用者的 Session 資訊,返回值 key 為 sessionId
        Map<String, ? extends Session> userSessions = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, indexName);
        // 移除使用者的 session 資訊
        List<String> sessionIds = new ArrayList<>(userSessions.keySet());
        for (String session : sessionIds) {
            redisOperationsSessionRepository.deleteById(session);
        }
        return ResponseBean.success(userSessions);
    }

}

說明 indexName 為 Principal.getName() 的返回值。