spring-security 向Spring(Security)应用添加维护模式

e0bqpujr  于 2022-11-11  发布在  Spring
关注(0)|答案(3)|浏览(175)

我正在寻找一种方法来实现我的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);
    }
}
xmd2e60i

xmd2e60i1#

我现在找到了两个类似的解决方案,但我注入代码的地方看起来不太好。
对于这两种情况,我都添加了一个自定义的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。也许有人能想出如何做到这一点。

9fkzdhlc

9fkzdhlc2#

这有点像是先有鸡还是先有蛋的两难问题,您希望向未授权用户显示“我们处于维护模式...”消息,同时允许授权用户登录,但您不知道他们是否已获得授权,直到他们登录。理想情况下,将此设置在某种筛选器中会很好,但我发现在实践中,通过将逻辑放在登录之后,对我来说解决类似问题会更容易。就像在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

它并不漂亮,但它很容易理解(我认为这对安全配置很好),而且它很有效。
更新:
使用您的解决方案,我必须销毁每个人的会话,否则在启用维护模式之前登录的用户仍然可以使用系统
在我们的项目中,我们不允许任何用户在维护模式下登录。一个管理员启动一个任务,启用“维护...”msg,并进行倒计时,然后在最后,我们使用SessionRegistry终止每个人的会话。

rjjhvcjd

rjjhvcjd3#

我也是类似的情况,发现这个答案很有帮助。我遵循了第二种方法,也设法返回自定义响应。
下面是我为返回自定义响应所做的工作。
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);

相关问题