【问题标题】:Add Maintenance Mode to Spring (Security) app将维护模式添加到 Spring(安全)应用程序
【发布时间】:2015-05-03 02:27:06
【问题描述】:

我正在寻找一种在我的 Spring 应用中实现维护模式的方法。

当应用程序处于维护模式时,应仅允许用户 role = MAINTENANCE 登录。其他人将被重定向到登录页面。

现在我刚刚建立了一个过滤器:

@Component
public class MaintenanceFilter extends GenericFilterBean {
    @Autowired SettingStore settings;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
            HttpServletResponse res = (HttpServletResponse) response; 
            res.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
        } else {
            chain.doFilter(request, response); 
        }
    }
}

并添加它使用:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // omitted other stuff
        .addFilterAfter(maintenanceFilter, SwitchUserFilter.class);
}

因为据我所知SwitchUserFilter 应该是 Spring Security 过滤器链中的最后一个过滤器。

现在每个请求都会被取消,并返回 503 响应。虽然无法访问登录页面。

如果我向过滤器添加重定向,这将导致无限循环,因为对登录页面的访问也会被拒绝。

此外,我想不出一种获取当前用户角色的好方法。还是我应该选择SecurityContextHolder


我正在寻找一种将每个用户重定向到登录页面的方法(可能使用查询参数?maintenance=true)并且每个使用role = MAINTENANCE 的用户都可以使用该应用程序。

所以过滤器/拦截器的行为应该像:

if(maintenance.isEnabled()) {
    if(currentUser.hasRole(MAINTENANCE)) {
        // this filter does nothing
    } else {
        redirectTo(loginPage?maintenance=true);
    }
}

【问题讨论】:

    标签: spring spring-mvc spring-security maintenance maintenance-mode


    【解决方案1】:

    我现在找到了两个类似的解决方案,但我注入代码的地方看起来不太好。

    我添加了一个自定义的RequestMatcher,得到@Autowired 并检查是否启用了维护模式。

    解决方案 1:

    @Component
    public class MaintenanceRequestMatcher implements RequestMatcher {
        @Autowired SettingStore settingStore;
    
        @Override
        public boolean matches(HttpServletRequest request) {
            return settingStore.get(MaintenanceMode.KEY).isEnabled()
        }
    }
    

    在我的安全配置中:

    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .requestMatchers(maintenanceRequestMatcher).hasAuthority("MY_ROLE")
                .anyRequest().authenticated()
        // ...
    }
    

    解决方案 2:

    非常相似,但使用HttpServletRequest.isUserInRole(...)

    @Component
    public class MaintenanceRequestMatcher implements RequestMatcher {
        @Autowired SettingStore settingStore;
    
        @Override
        public boolean matches(HttpServletRequest request) {
            return settingStore.get(MaintenanceMode.KEY).isEnabled() && !request.isUserInRole("MY_ROLE");
        }
    }
    
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired MaintenanceRequestMatcher maintenanceRequestMatcher;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .requestMatchers(maintenanceRequestMatcher).denyAll()
                .anyRequest().authenticated()
        // ...
    }
    

    如果启用维护模式并且当前用户没有MY_ROLE,这将执行denyAll()


    唯一的缺点是,我无法设置自定义响应。我更愿意返回503 Service Unavailable。也许有人可以弄清楚如何做到这一点。

    【讨论】:

    【解决方案2】:

    这有点像先有鸡还是先有蛋的两难境地,您想向未经授权的用户显示“我们处于维护模式...”消息,同时允许授权用户登录,但您不知道他们是否被授权,直到他们登录。理想情况下,最好在某种过滤器中使用它,但我发现在实践中通过在登录后放置逻辑更容易解决类似的问题,就像在 UserDetailsService 中一样。

    这是我在一个项目中解决它的方法。当我处于维护模式时,我为视图设置了一个标志,以在全局标题或登录页面上显示“我们处于维护模式..”消息。所以用户,无论他们是谁,都知道它的维护模式。登录应该正常工作。

    用户通过身份验证后,在我的自定义UserDetailsService 中,他们的用户详细信息与他们的角色一起加载,我执行以下操作:

    // if we're in maintenance mode and does not have correct role
    if(maintenance.isEnabled() && !loadedUser.hasRole(MAINTENANCE)) {
      throw new UnauthorizedException(..)
    }
    // else continue as normal
    

    它并不漂亮,但它很容易理解(我认为这对安全配置很有好处)并且它有效。

    更新:

    有了你的解决方案,我必须销毁每个人的会话,否则用户 在启用维护模式之前登录的,仍然可以 与系统合作

    在我们的项目中,我们不允许任何用户在维护模式下登录。管理员启动启用“维护...”消息的任务,倒计时,然后在最后,我们使用SessionRegistry 使每个人的会话到期。

    【讨论】:

    • 有了你的解决方案,我必须销毁每个人的会话,否则在启用维护模式之前登录的用户仍然能够使用系统。 ... 我只是想知道 Spring 的登录拦截器是如何工作的。这正是工作:如果用户在调用资源时没有经过身份验证,那么他会被重定向到登录页面。这有点像我想要/需要的。
    【解决方案3】:

    我也遇到过类似的情况,发现这个答案很有帮助。我遵循了第二种方法,并且还设法返回了自定义响应。

    这是我为返回自定义响应所做的工作。

    1- 定义返回所需自定义响应的控制器方法。

    @RestController
    public class CustomAccessDeniedController {
        
        @GetMapping("/access-denied")
        public String getAccessDeniedResponse() {
            return "my-access-denied-page";
        }
    }
    

    2- 在您的安全上下文中,您应该允许此 URL 可访问。

    http.authorizeRequests().antMatchers("/access-denied").permitAll()
    

    3- 创建自定义访问被拒绝异常处理程序

    @Component
    public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    
        @Autowired
        private SettingStore settingStore;
        
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            
            if(settingStore.get(MaintenanceMode.KEY).isEnabled()) {
                response.sendRedirect(request.getContextPath() + "/access-denied");
            }
        }
    }
    

    4- 在安全配置中注册自定义访问拒绝异常处理程序

        @Autowired
        private CustomAccessDeniedHandler accessDeniedHandler;
    
    
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    

    【讨论】:

      猜你喜欢
      • 2019-11-03
      • 1970-01-01
      • 1970-01-01
      • 2017-05-31
      • 1970-01-01
      • 1970-01-01
      • 2018-05-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多