将维护模式添加到 Spring(安全)应用程序

Posted

技术标签:

【中文标题】将维护模式添加到 Spring(安全)应用程序【英文标题】:Add Maintenance Mode to Spring (Security) app 【发布时间】: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);
    

【问题讨论】:

【参考方案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。也许有人可以弄清楚如何做到这一点。

【讨论】:

感谢您的回答。这是一个工作示例github.com/ozkanpakdil/spring-examples/tree/master/…【参考方案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);

【讨论】:

以上是关于将维护模式添加到 Spring(安全)应用程序的主要内容,如果未能解决你的问题,请参考以下文章

Exchange 安全和维护

ansible+jenkins实现zabbix维护模式添加

如何在 Symfony 2 中进入维护模式以安全地更新生产应用程序?

text 如果用户未登录,则仅显示维护模式。将maintenance.php文件添加到wp-content文件夹以自定义维护页面。

特定子域上的 Laravel 维护模式

将网站置于维护模式?