2

我正在寻找一种在我的 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);
    }
}
4

3 回答 3

3

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

对于这两者,我添加了一个 custom 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()
    // ...
}

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


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

于 2015-03-02T22:07:19.960 回答
0

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

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

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

// 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.

于 2015-03-02T19:06:18.913 回答
0

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

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

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);
于 2021-10-25T13:23:35.680 回答