1. 自定义user-service后,封装自定义异常信息返回

通常情况下,抛UsernameNotFoundException异常信息是捕捉不了,跟踪源码后发现

  1. try {
  2. user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
  3. } catch (UsernameNotFoundException notFound) {
  4. logger.debug("User '" + username + "' not found");
  5. if (hideUserNotFoundExceptions) {
  6. throw new BadCredentialsException(messages.getMessage(
  7. "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
  8. } else {
  9. throw notFound;
  10. }
  11. }

而默认情况下,hideUserNotFoundExceptions为true。所以就会导致明明抛UsernameNotFoundException,但前台还是只能捕获Bad credentials的问题。

解决办法我们可以直接覆盖 org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider 的类,然后修改hideUserNotFoundExceptions为false。

当然,这样的解决办法并不好。所以,我们还是走正规的途径,自定义 org.springframework.security.authentication.dao.DaoAuthenticationProvider 来替换默认的即可,即修改配置文件并定义provider,这就是IoC的伟大之处。

原来authentication-manager中简单的定义user-service-ref

  1. <authentication-manager alias="authenticationManager">
  2. <authentication-provider user-service-ref="myUserDetailsService">
  3. <!-- 密码加密方式  -->
  4. <password-encoder hash="md5" />
  5. </authentication-provider>
  6. </authentication-manager>

现在修改如下:

  1. <authentication-manager alias="authenticationManager">
  2. <authentication-provider ref="authenticationProvider" />
  3. </authentication-manager>
  4. <b:bean id="authenticationProvider"
  5. class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
  6. <b:property name="userDetailsService" ref="myUserDetailsService" />
  7. <b:property name="hideUserNotFoundExceptions" value="false" />
  8. <b:property name="passwordEncoder" ref="passwordEncoder"></b:property>
  9. </b:bean>
  10. <b:bean
  11. class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"
  12. id="passwordEncoder"></b:bean>

这样修改后,在登录页面获取的异常已经是自己抛出去的UsernameNotFoundException了。

(注:这里保留了md5加密方式,但是原始的加密,没加salt,之后会继续修改为安全性高一些的md5+salt加密。现在这世道普通的md5加密和明文没多大区别。)

2. 国际化资源i18n信息

若想封装国际化资源信息到页面(不想打硬编码信息到代码内),又不想自己构造Properties对象的话,可以参考SpringSecurity3中的获取资源文件方法。(也是看源码的时候学习到的)

在SpringSecurity3中的message都是通过这样的方式得到的:

protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

 
通过提供的静态方法,我们很方便的得到国际化资源信息。但无奈SpringSecurityMessageSource硬编码写死了只是获取org.springframework.security.messages的资源文件(英文信息)。如下:
 
  1. public SpringSecurityMessageSource() {
  2. setBasename("org.springframework.security.messages");
  3. }
 
通常情况下,这个并不符合我们的使用,并且很多情况下,使用SpringSecurity3自定义抛出的异常信息的话,也会出现不符合语言习惯的信息。
 
所以,这里是建议覆盖org.springframework.security.core.SpringSecurityMessageSource类,并指定获取应用中的默认国际化资源文件。
 
不过,你还是不想覆盖别人的类的话,也还可以自己模仿SpringSecurityMessageSource编写自己的获取MessageSourceAccessor的类,例如我就是这么做....
 
  1. public class SpringMessageSource extends ResourceBundleMessageSource {
  2. // ~ Constructors
  3. // ===================================================================================================
  4. public SpringMessageSource() {
  5. setBasename("com.foo.resources.messages_zh_CN");
  6. }
  7. // ~ Methods
  8. // ========================================================================================================
  9. public static MessageSourceAccessor getAccessor() {
  10. return new MessageSourceAccessor(new SpringMessageSource());
  11. }
  12. }
 
这样,我们就可以在自定义的userDetailsService类中,像SpringSecurity3那样方便的使用国际化资源文件了。
 
如:
  1. private MessageSourceAccessor messages = SpringMessageSource.getAccessor();
  2. ....
  3. public UserDetails loadUserByUsername(String username)
  4. throws UsernameNotFoundException, DataAccessException {
  5. if (StringUtils.isBlank(username)) {
  6. throw new UsernameNotFoundException(
  7. messages.getMessage("PasswordComparisonAuthenticator.badCredentials"),
  8. username);
  9. }
  10. ...
  11. }
 
3.添加验证码
 
在实际应用中,其实验证码是少不了的,不然很容易就被暴力破解了。添加验证码起码也可以增加一点安全性,而且添加验证码也比较简单。
 
添加自定义UsernamePasswordAuthenticationFilter,在验证username和password之前,我们加入验证码的判定。
 
在spring-security配置文件中的<http>代码块中添加
 
  1. <custom-filter before="FORM_LOGIN_FILTER" ref="validateCodeAuthenticationFilter" />
 
然后就是在beans内添加定义validateCodeAuthenticationFilter的bean代码
 
 
  1. <b:bean id="validateCodeAuthenticationFilter"
  2. class="com.foo.security.ValidateCodeAuthenticationFilter">
  3. <b:property name="postOnly" value="false"></b:property>
  4. <b:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></b:property>
  5. <b:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></b:property>
  6. <b:property name="authenticationManager" ref="authenticationManager"></b:property>
  7. </b:bean>
  8. <b:bean id="loginLogAuthenticationSuccessHandler"
  9. class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
  10. <b:property name="defaultTargetUrl" value="/index.do"></b:property>
  11. </b:bean>
  12. <b:bean id="simpleUrlAuthenticationFailureHandler"
  13. class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
  14. <b:property name="defaultFailureUrl" value="/login.jsp?login_error=1"></b:property>
  15. </b:bean>
最后是ValidateCodeAuthenticationFilter的源码:
 
  1. public class ValidateCodeAuthenticationFilter extends
  2. UsernamePasswordAuthenticationFilter {
  3. private boolean postOnly = true;
  4. private boolean allowEmptyValidateCode = false;
  5. private String sessionvalidateCodeField = DEFAULT_SESSION_VALIDATE_CODE_FIELD;
  6. private String validateCodeParameter = DEFAULT_VALIDATE_CODE_PARAMETER;
  7. public static final String DEFAULT_SESSION_VALIDATE_CODE_FIELD = "validateCode";
  8. public static final String DEFAULT_VALIDATE_CODE_PARAMETER = "validateCode";
  9. public static final String VALIDATE_CODE_FAILED_MSG_KEY = "validateCode.notEquals";
  10. @Override
  11. public Authentication attemptAuthentication(HttpServletRequest request,
  12. HttpServletResponse response) throws AuthenticationException {
  13. if (postOnly && !request.getMethod().equals("POST")) {
  14. throw new AuthenticationServiceException(
  15. "Authentication method not supported: "
  16. + request.getMethod());
  17. }
  18. String username = StringUtils.trimToEmpty(obtainUsername(request));
  19. String password = obtainPassword(request);
  20. if (password == null) {
  21. password = StringUtils.EMPTY;
  22. }
  23. UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
  24. username, password);
  25. // Place the last username attempted into HttpSession for views
  26. HttpSession session = request.getSession(false);
  27. if (session != null || getAllowSessionCreation()) {
  28. request.getSession().setAttribute(
  29. SPRING_SECURITY_LAST_USERNAME_KEY,
  30. TextEscapeUtils.escapeEntities(username));
  31. }
  32. // Allow subclasses to set the "details" property
  33. setDetails(request, authRequest);
  34. // check validate code
  35. if (!isAllowEmptyValidateCode())
  36. checkValidateCode(request);
  37. return this.getAuthenticationManager().authenticate(authRequest);
  38. }
  39. /**
  40. *
  41. * <li>比较session中的验证码和用户输入的验证码是否相等</li>
  42. *
  43. */
  44. protected void checkValidateCode(HttpServletRequest request) {
  45. String sessionValidateCode = obtainSessionValidateCode(request);
  46. String validateCodeParameter = obtainValidateCodeParameter(request);
  47. if (StringUtils.isEmpty(validateCodeParameter)
  48. || !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {
  49. throw new AuthenticationServiceException(
  50. messages.getMessage(VALIDATE_CODE_FAILED_MSG_KEY));
  51. }
  52. }
  53. private String obtainValidateCodeParameter(HttpServletRequest request) {
  54. return request.getParameter(validateCodeParameter);
  55. }
  56. protected String obtainSessionValidateCode(HttpServletRequest request) {
  57. Object obj = request.getSession()
  58. .getAttribute(sessionvalidateCodeField);
  59. return null == obj ? "" : obj.toString();
  60. }
  61. public boolean isPostOnly() {
  62. return postOnly;
  63. }
  64. @Override
  65. public void setPostOnly(boolean postOnly) {
  66. this.postOnly = postOnly;
  67. }
  68. public String getValidateCodeName() {
  69. return sessionvalidateCodeField;
  70. }
  71. public void setValidateCodeName(String validateCodeName) {
  72. this.sessionvalidateCodeField = validateCodeName;
  73. }
  74. public boolean isAllowEmptyValidateCode() {
  75. return allowEmptyValidateCode;
  76. }
  77. public void setAllowEmptyValidateCode(boolean allowEmptyValidateCode) {
  78. this.allowEmptyValidateCode = allowEmptyValidateCode;
  79. }
  80. }
附件中有生成CODE图片的JSP(相对比较简单的,但基本可以满足应用),还有文章中用到的一些关键配置文件与源码。
 
生成验证码的jsp页面调用时直接<img src="./validateCode.jsp"  />即可,但刷新时,记得在URL上增加随机数的参数,不然会有缓存导致刷新失败。
 
 
添加验证码部分有参考:http://www.iteye.com/topic/720867

使用SpringSecurity3用户验证(异常信息,验证码)的更多相关文章

  1. ASP.NET MVC5+EF6+EasyUI 后台管理系统(65)-MVC WebApi 用户验证 (1)

    系列目录 前言: WebAPI主要开放数据给手机APP,其他需要得知数据的系统,或者软件应用,所以移动端与系统的数据源往往是相通的. Web 用户的身份验证,及页面操作权限验证是B/S系统的基础功能, ...

  2. ASP.NET MVC 5 WEB API 用户验证

    参考博客:ASP.NET MVC5+EF6+EasyUI 后台管理系统(65)-MVC WebApi 用户验证 (1) 参考博客:MVC WebApi 用户验证 (2)构建ASP.NET MVC5+E ...

  3. Log4Net在MVC下的配置以及运用线程队列记录异常信息

    Log4Net是用来记录日志的,可以将程序运行过程中的信息输出到一些地方(文件.数据库.EventLog等),日志就是程序的黑匣子,可以通过日志查看系统的运行过程,从而发现系统的问题.日志的作用:将运 ...

  4. 模拟用户登录,内含验证码验证和request等操作

    模拟用户登录,内含验证码验证和jsp等操作 1.案例需求: 1. 访问带有验证码的登录页面login.jsp 2. 用户输入用户名,密码以及验证码. * 如果用户名和密码输入有误,跳转登录页面,提示: ...

  5. Django电商项目---完成登录验证和用户中心(个人信息)day3

    登录验证的实现 背景说明: 用户在商品界面选择商品后,在点击购物车或者结算订单之前 需要完成用户的登录验证,这里用装饰器来完成   创建装饰器类: df_user/user_decorator.py ...

  6. Spring MVC 学习笔记10 —— 实现简单的用户管理(4.3)用户登录显示全局异常信息

    </pre>Spring MVC 学习笔记10 -- 实现简单的用户管理(4.3)用户登录--显示全局异常信息<p></p><p></p>& ...

  7. Spring MVC 学习笔记9 —— 实现简单的用户管理(4)用户登录显示局部异常信息

    Spring MVC 学习笔记9 -- 实现简单的用户管理(4.2)用户登录--显示局部异常信息 第二部分:显示局部异常信息,而不是500错误页 1. 写一个方法,把UserException传进来. ...

  8. asp.ne如何使用javascript去验证客户端信息,如果验证成功则送往服务器端处理,否则在客户端提示用户(不返回到服务器端处理)

    一.问题 在网站一般都有很多地方需要用户去填写一些信息,然后用户点击提交,将信息送往后台储存到数据库中.在这一个过程我以前做法直接在button的click事件中来判断用户输入的数据是否完整和合法,虽 ...

  9. Android 后台发送邮件 (收集应用异常信息+Demo代码)

    上一次说了如何收集我们已经发布的应用程序的错误信息,方便我们调试完善程序.上次说的收集方法主要是把收集的信息通过Http的post请求把相关的异常信息变成请求参数发送到服务器.这个对做过web开发的人 ...

随机推荐

  1. bootstrap如何自定义5等分

    根据bootstrap源码改的1比5的栅格系统 /*5等分媒体查询样式begin*/ .col-xs-1-5,.col-sm-1-5,.col-md-1-5,.col-lg-1-5,.col-xs-4 ...

  2. 基于jdk调用天气服务案例及问题

    问题1:解析wsdl文件时出现 把网络上的wsdl保存到本地,把圈起来的那段删掉 代码: 当返回结果不是String类型时: 输入城市编码去查找 所以: 问题二:如果把本地wsdl文件删除的话需要 三 ...

  3. sql语句的学习(2)

    7.统计:学号.姓名.语文.数学.英语.总分.平均成绩 8.列出各门课程的平均成绩.课程,平均成绩 9.列出数学成绩的排名(要求显示字段:学号,姓名,成绩,排名) 10.列出数学成绩在2-3名的学生( ...

  4. 未知的生成错误 因为没有预加载,所以无法解析程序集 GalaSoft.MvvmLight

    使用wpf开发时,在ViewModel中引用了DevExpress注册的GalaSoft.MvvmLight命名空间,使用其ViewModelBase,在View界面中绑定事件时出现错误: 错误 13 ...

  5. POJ 3026 Borg Maze(Prim+BFS建邻接矩阵)

    ( ̄▽ ̄)" #include<iostream> #include<cstdio> #include<cstring> #include<algo ...

  6. Java IO 理解流的概念

    Java IO 理解流的概念 @author ixenos 在理解流时首先理解以下概念 1.流的来源和去向一般在构造器指出 2.方法中的形参一般是将流输出到某个位置,读取(INPUT)流从流读出数据( ...

  7. hdu_5925_Coconuts(离散化+dfs)

    题目链接:hdu_5925_Coconuts 题意: 给你一张很大的图,和小于200个的障碍点,问这张图中的联通块有多少个 题解: 由于障碍点只有200个,所以肯定有很多的空白部分,我们将这些空白部分 ...

  8. Docker 安装完启动服务报错

    [root@lh- ~]# docker images Cannot connect to the Docker daemon. Is the docker daemon running on thi ...

  9. jquery.cookie.js 的配置

    一个轻量级的cookie 插件,可以读取.写入.删除 cookie. jquery.cookie.js 的配置 首先包含jQuery的库文件,在后面包含 jquery.cookie.js 的库文件. ...

  10. Sublime Text学习笔记

    1.快捷键(Key Bindings)   Preferences -> Key Bindings ->Default   会打开一个配置文件,里面全是配置信息 2.代码片段(Snippe ...