spring security3.0默认的表单登陆并不会抛出UsernameNotFoundException异常,我尝试了多次, 自定配置了bean, 也把错误信息扔到了session属性都没有解决
public final class WebAttributes {
public static final String AUTHENTICATION_EXCEPTION = "SPRING_SECURITY_LAST_EXCEPTION";
得到的老是令人讨厌的Bad credentials(当然可以通过message.property可以做国际化, spring security3提供的, 我们写个中文的属性文件就可以了。
之所以我想在登陆验证时抛出异常是出于两个目的:
1 提示用户不存在
2 提示用户输入密码错误最大次数不能超过3, 超过3则锁定账号, 让管理员自己去解锁。
(注意:账号可用,过期,是否锁定, 这些是spring security3自带的异常, 没必要我们再去扔出了, 上面列的2个是我自定义的异常。
补充一点,默认的登录表单异常处理是在类SimpleUrlAuthenticationFailureHandler里处理的。
有个不一样的地方是, spring security并不是直接抛出异常, 而是包装了放在session里,属性名就是
WebAttributes.AUTHENTICATION_EXCEPTION
我自定义了异常处理, 不用SimpleUrlAuthenticationFailureHandler
public class AccessAuthenticationFailureHandler implements AuthenticationFailureHandler { protected final Log logger = LogFactory.getLog(getClass()); private String defaultFailureUrl; private boolean forwardToDestination = false; private boolean allowSessionCreation = true; private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); public final static String TRY_MAX_COUNT="tryMaxCount"; private int maxTryCount=3; public AccessAuthenticationFailureHandler() { } public AccessAuthenticationFailureHandler(String defaultFailureUrl) { setDefaultFailureUrl(defaultFailureUrl); } /** * Performs the redirect or forward to the {@code defaultFailureUrl} if set, otherwise returns a 401 error code. * <p> * If redirecting or forwarding, {@code saveException} will be called to cache the exception for use in * the target view. */ @SuppressWarnings("unchecked") public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { HttpSession session=request.getSession(); Integer tryCount=(Integer) session.getAttribute(TRY_MAX_COUNT); if(tryCount==null){ session.setAttribute(TRY_MAX_COUNT, 1);//增加失败次数 }else{ if(tryCount>maxTryCount-1){ //锁定账户 BaseDao baseDao=(BaseDao) ConfigConstants.context.getBean("baseDao"); String name=request.getParameter(UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY); List<User> userList=baseDao.getHibernateTemplate().find("from User u where u.name=?", name); if(userList.size()>0){ userList.get(0).setIsLocked(true); baseDao.getHibernateTemplate().update(userList.get(0)); } session.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, new MaxTryLoginException("超过最大登录尝试次数"+maxTryCount+",用户已被锁定")); } session.setAttribute("tryMaxCount", tryCount+1); } //觉得默认跳转的地方 if (defaultFailureUrl == null) { logger.debug("No failure URL set, sending 401 Unauthorized error"); response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed: " + exception.getMessage()); } else { saveException(request, exception); if (forwardToDestination) { logger.debug("Forwarding to " + defaultFailureUrl); request.getRequestDispatcher(defaultFailureUrl).forward(request, response); } else { logger.debug("Redirecting to " + defaultFailureUrl); redirectStrategy.sendRedirect(request, response, defaultFailureUrl); } } } /** * Caches the {@code AuthenticationException} for use in view rendering. * <p> * If {@code forwardToDestination} is set to true, request scope will be used, otherwise it will attempt to store * the exception in the session. If there is no session and {@code allowSessionCreation} is {@code true} a session * will be created. Otherwise the exception will not be stored. */ protected void saveException(HttpServletRequest request, AuthenticationException exception) { if (forwardToDestination) { request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception); } else { HttpSession session = request.getSession(false); if (session != null || allowSessionCreation) { if(request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)==null) request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception); } } }
session.getAttribute(TRY_MAX_COUNT); 这里记得登陆成功了之后要清掉
具体办法是:在这里清掉
public class AccessSavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
对应的配置文件
<form-login login-page="/common/login.jsp?form-login=true" always-use-default-target="false" authentication-failure-handler-ref="authenticationFailureHandler" authentication-success-handler-ref="savedRequestAwareAuthenticationSuccessHandler" /> <!-- 认证成功之后跳转到哪个页面 登陆成功之后记录登陆日志和决定跳到哪个页面 --> <beans:bean id="savedRequestAwareAuthenticationSuccessHandler" class="com.wsd.core.auth.security.AccessSavedRequestAwareAuthenticationSuccessHandler"> <beans:constructor-arg name="defaultTargetUrl" value="/auth/user/userList.html?default-target=true" /> <beans:property name="alwaysUseDefaultTargetUrl" value="false" /> <beans:property name="dispatcherMap"> <!-- 如果不配置这个属性,则defaultTargetUrl即生效 --> <beans:map key-type="java.lang.String"> <beans:entry key="ROLE_ADMIN" value="/auth/user/userList.html" /> <beans:entry key="ROLE_SUPPER_ADMIN" value="/auth/supperOrg/supperOrgList.html" /> </beans:map> </beans:property> </beans:bean>
这个是支持登陆成功之后, 进入没有登陆之前访问的地址。
下面是登陆多次失败的配置:
<!-- DAO认证 --> <beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <beans:property name="userDetailsService" ref="userDetailsService"></beans:property> <beans:property name="hideUserNotFoundExceptions" value="false"/><!-- 没有这个将不能准确地报告异常 --> </beans:bean> <!-- 数据库验证账号密码权限 --> <beans:bean id="userDetailsService" class="com.wsd.core.auth.security.AccessUserDetailsService" /> <!-- 认证管理器,实现用户认证的入口,主要实现UserDetailsService接口即可 --> <authentication-manager alias="authenticationManager" > <authentication-provider ref="daoAuthenticationProvider"> <password-encoder hash="plaintext"/><!-- <s:password-encoder hash="sha" />SHA --> </authentication-provider> </authentication-manager>
在源码里可以看到DaoAuthenticationProvide
r的父类AbstractUserDetailsAuthenticationProvider有一个属性hideUserNotFoundExceptions
在配置中即可看出 hideUserNotFoundExceptions = true代表隐藏真实的异常, 改为false即可显示你自己的异常了。
不过要继承AuthenticationException这个异常哦。