将维护模式添加到 Spring(安全)应用程序
Add Maintenance Mode to Spring (Security) app
我正在寻找一种在我的 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 安全过滤器链中的最后一个过滤器。
现在每个请求都会被取消并返回 503 响应。虽然无法访问登录页面。
如果我向过滤器添加重定向,这将导致无限循环,因为登录页面的访问也被拒绝。
此外,我想不出获取当前用户角色的好方法。或者我应该选择 SecurityContextHolder
?
我正在寻找一种将每个用户重定向到登录页面的方法(可能使用查询参数 ?maintenance=true
)并且每个 role = MAINTENANCE
的用户都可以使用该应用程序。
所以过滤器/拦截器应该表现得像:
if(maintenance.isEnabled()) {
if(currentUser.hasRole(MAINTENANCE)) {
// this filter does nothing
} else {
redirectTo(loginPage?maintenance=true);
}
}
这是先有鸡还是先有蛋的两难选择,你想向未授权用户显示 "we're in maintenance mode ..." 消息,同时允许授权用户登录,但在他们登录之前你不知道他们是否已授权。理想情况下,将它放在某种过滤器中会很好,但我发现在实践中,通过在登录后放置逻辑对我来说更容易解决类似的问题,例如 UserDetailsService
.
下面是我在项目中的解决方法。当我处于维护模式时,我为视图设置了一个标志以在全局 header 或登录页面上显示 "we're 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
它不是很漂亮,但它很容易理解(我认为这对安全配置有好处)并且有效。
更新:
With you solution I'd have to destroy everyone's session, else a user
which was logged in before maintenance mode was enabled, is still able
to work with the system
在我们的项目中,我们不允许任何用户在维护模式下登录。管理员启动了一项启用 "maintenance..." msg 的任务,倒计时,然后在最后,我们使用 SessionRegistry
.
使每个人的 session 过期
我现在找到了两个相似的解决方案,但我注入代码的地方看起来不太好。
我为两者添加了一个自定义 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
。
唯一的缺点是,我无法设置自定义响应。我更愿意 return 一个 503 Service Unavailable
。也许有人可以弄清楚如何做到这一点。
我遇到了类似的情况,发现这个答案很有帮助。我采用了第二种方法,并且还设法 return 自定义响应。
这是我对 return 自定义响应所做的。
1- 定义 return 所需的自定义响应的控制器方法。
@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 应用程序中实施维护模式的方法。
当应用程序处于维护模式时,仅应允许用户 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 安全过滤器链中的最后一个过滤器。
现在每个请求都会被取消并返回 503 响应。虽然无法访问登录页面。
如果我向过滤器添加重定向,这将导致无限循环,因为登录页面的访问也被拒绝。
此外,我想不出获取当前用户角色的好方法。或者我应该选择 SecurityContextHolder
?
我正在寻找一种将每个用户重定向到登录页面的方法(可能使用查询参数 ?maintenance=true
)并且每个 role = MAINTENANCE
的用户都可以使用该应用程序。
所以过滤器/拦截器应该表现得像:
if(maintenance.isEnabled()) {
if(currentUser.hasRole(MAINTENANCE)) {
// this filter does nothing
} else {
redirectTo(loginPage?maintenance=true);
}
}
这是先有鸡还是先有蛋的两难选择,你想向未授权用户显示 "we're in maintenance mode ..." 消息,同时允许授权用户登录,但在他们登录之前你不知道他们是否已授权。理想情况下,将它放在某种过滤器中会很好,但我发现在实践中,通过在登录后放置逻辑对我来说更容易解决类似的问题,例如 UserDetailsService
.
下面是我在项目中的解决方法。当我处于维护模式时,我为视图设置了一个标志以在全局 header 或登录页面上显示 "we're 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
它不是很漂亮,但它很容易理解(我认为这对安全配置有好处)并且有效。
更新:
With you solution I'd have to destroy everyone's session, else a user which was logged in before maintenance mode was enabled, is still able to work with the system
在我们的项目中,我们不允许任何用户在维护模式下登录。管理员启动了一项启用 "maintenance..." msg 的任务,倒计时,然后在最后,我们使用 SessionRegistry
.
我现在找到了两个相似的解决方案,但我注入代码的地方看起来不太好。
我为两者添加了一个自定义 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
。
唯一的缺点是,我无法设置自定义响应。我更愿意 return 一个 503 Service Unavailable
。也许有人可以弄清楚如何做到这一点。
我遇到了类似的情况,发现这个答案很有帮助。我采用了第二种方法,并且还设法 return 自定义响应。
这是我对 return 自定义响应所做的。
1- 定义 return 所需的自定义响应的控制器方法。
@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);