如何使用Spring Security在用户初始登录时强制更改密码

在用户使用Spring Security初次登录时,实现强制密码更改的最佳方式是什么?

我尝试实现这里提到的自定义AuthenticationSuccessHandler ,但正如rodrigoap所提到的,如果用户在地址栏手动输入URL,即使用户没有更改密码,用户仍然可以继续访问该页面。

我用filterForceChangePasswordFilter做了这个。 因为如果用户手动键入url,他们可以绕过更改密码表单。 使用filter,请求始终被截获。

因此,我继续实施自定义filter。

我的问题是,当我实现自定义filter并在其中发送重定向时,它会再次通过filter,从而导致无限重定向循环,如此处所述。 我尝试通过在security-context.xml中声明两个http标签来实现所提到的解决方案,第一个标签具有pattern属性,但它仍然通过我的自定义filter:

    ...   ...       

我当前的实现(有效)是:

  • 在我的自定义身份validation成功处理程序中,我设置了一个会话属性isFirstLogin
  • 在我的ForcePasswordChangeFilter中,我检查是否设置了会话isFirstLogin
    • 如果是,那么我发送重定向到我的强制密码更改
    • 否则,我调用chain.doFilter()

我对此实现的问题是访问我的资源文件夹也会通过此filter导致我的页面失真(因为* .js和* .css未成功检索)。 这就是我在我的安全应用程序context.xml中尝试使用两个标记的原因(这不起作用)。

因此,如果servletPath启动或包含“/ resources”,我最终必须手动过滤请求。 我不希望它像这样 – 必须手动过滤请求路径 – 但现在它就是我拥有的。

这样做的优雅方式是什么?

我通过为用户提供状态值来解决了这个问题,

  • status = -1; 初次登录
  • status = 0; 不活跃的帐户
  • status = 1; 活跃帐户

security.xml 2个自定义身份validation控制器。 首先检查用户名,传递和秒,以获取其他控件,如初始登录,密码过期策略。

在首次登录时,提供正确的用户名和密码值,第一个控制器( user-service-ref="jdbcUserService" )无法validation用户(因为用户的status=-1 )而不是第二个控制器( ref="myAuthenticationController" )捕获请求。 在此控制器中抛出DisabledException

最后,您可以在AuthenticationFailureListeneronAuthenticationFailure方法上将用户重定向到密码更改页面。

security.xml的一部分

                     

 @Service("myAuthenticationController") public class MyAuthenticationController extends AbstractUserDetailsAuthenticationProvider { private final Logger logger = Logger.getLogger(getClass()); @Autowired private WfmUserValidator userValidator; private String username; private String password; @Required public void setAdminUser(String username) { this.username = username; } @Required public void setAdminPassword(String password) { this.password = password; } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { return; } @Override protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { String password = (String) authentication.getCredentials(); List authorities = new ArrayList(); String userRole = ""; if (status = -1) { throw new DisabledException("It is first login. Password change is required!"); } else if (password expired) { throw new CredentialsExpiredException("Password is expired. Please change it!"); } return new User(userName, password, true, // enabled true, // account not expired true, // credentials not expired true, // account not locked authorities); } } 

 public class AuthenticationFailureListener implements AuthenticationFailureHandler { private static Logger logger = Logger.getLogger(AuthenticationFailureListener.class); private static final String BAD_CREDENTIALS_MESSAGE = "bad_credentials_message"; private static final String CREDENTIALS_EXPIRED_MESSAGE = "credentials_expired_message"; private static final String DISABLED_MESSAGE = "disabled_message"; private static final String LOCKED_MESSAGE = "locked_message"; @Override public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse res, AuthenticationException ex) throws IOException, ServletException { // TODO Auto-generated method stub String userName = req.getParameter("j_username"); logger.info("[AuthenticationFailure]:" + " [Username]:" + userName + " [Error message]:" + ex.getMessage()); if (ex instanceof BadCredentialsException) { res.sendRedirect("../pages/login.jsf?message=" + MessageFactory.getMessageValue(BAD_CREDENTIALS_MESSAGE)); } else if (ex instanceof CredentialsExpiredException) { res.sendRedirect("../pages/changecredentials.jsf?message=" + MessageFactory.getMessageValue(CREDENTIALS_EXPIRED_MESSAGE)); } else if (ex instanceof DisabledException) { res.sendRedirect("../pages/changecredentials.jsf?message=" + MessageFactory.getMessageValue(DISABLED_MESSAGE)); } else if (ex instanceof LockedException) { res.sendRedirect("../pages/login.jsf?message=" + MessageFactory.getMessageValue(LOCKED_MESSAGE)); } } } 

我不确定弹簧是否提供内置function。

我通过在表格中设置一列来帮助我识别用户是否第一次登录,从而实现了类似的function。

如果是第一次登录,那么查看显示在我的情况下是重置密码页面,否则我的仪表板页面。

在ForceChangePasswordFilter中,为了从循环中停止过滤,您应该检查包含或不是否包含ChangePassword URL的ServletPath。 像这样:

  if(multiReadRequest.getServletPath().startsWith("/ChangePass.htm")) flag=false; 

我的资源问题也一样。 我去了以下:

    

我将这些设置为sec:intercept-url,其中access =“IS_AUTHENTICATED_ANONYMOUSLY,这最终与保护Spring Security访问这些资源的方式完全相同。

至于你通过使用一些firstLogin变量解决的“无限重定向循环”问题,我通过仅仅将请求URL与我将重定向到的密码更改URL进行比较来解决这个问题。 虽然我很高兴听到我忽略了什么。

我处理密码修改,如修改任何其他实体字段。

在这种情况下,您可以为假设用户对象创建更新表单。 在数据库中保存用户实体时,可能需要保存散列密码,处理盐等。但这不是Spring安全工作。

Interesting Posts