将维护模式添加到Spring(Security)应用程序中



我正在寻找一种在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应该是SpringSecurity过滤器链中的最后一个过滤器。

现在,每个请求都会被503响应取消。尽管无法访问登录页面。

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

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


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

因此,过滤器/拦截器的行为应该像:

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

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

对于两者,我都添加了一个自定义的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。也许有人能想出怎么做。

这有点像鸡还是蛋的困境,你想向未经授权的用户显示一条"我们处于维护模式…"的消息,同时允许授权用户登录,但在他们登录之前你不知道他们是否被授权。理想情况下,在某种过滤器中设置这一点会很好,但我发现,在实践中,通过在登录后放置逻辑,我更容易解决类似的问题,比如在UserDetailsService中。

以下是我在一个项目中解决问题的方法。当我处于维护模式时,我会为视图设置一个标志,以在全局标头或登录页面上显示"we are in maintenance mode.."消息。所以用户,不管他们是谁,都知道这是一种维护模式。登录应正常工作。

在用户经过身份验证后,在我的自定义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终止每个人的会话。

我也遇到过类似的情况,发现这个答案很有帮助。我采用了第二种方法,还成功地返回了自定义响应。

以下是我为返回自定义响应所做的操作。

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);

相关内容

  • 没有找到相关文章

最新更新