S - 使用SpringSecurity3用户验证几点体会(异常信息,验证码)

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

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

Java代码  

  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

Xml代码  

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

现在修改如下:

Xml代码  

  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的资源文件(英文信息)。如下:

Java代码  

  1. public SpringSecurityMessageSource() {
  2. setBasename("org.springframework.security.messages");
  3. }

通常情况下,这个并不符合我们的使用,并且很多情况下,使用SpringSecurity3自定义抛出的异常信息的话,也会出现不符合语言习惯的信息。

所以,这里是建议覆盖org.springframework.security.core.SpringSecurityMessageSource类,并指定获取应用中的默认国际化资源文件。

不过,你还是不想覆盖别人的类的话,也还可以自己模仿SpringSecurityMessageSource编写自己的获取MessageSourceAccessor的类,例如我就是这么做....

Java代码  

  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那样方便的使用国际化资源文件了。

如:

Java代码  

  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>代码块中添加

Xml代码  

  1. <custom-filter before="FORM_LOGIN_FILTER" ref="validateCodeAuthenticationFilter" />

然后就是在beans内添加定义validateCodeAuthenticationFilter的bean代码

Xml代码  

  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的源码:

Java代码  

  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

时间: 2024-09-29 04:39:42

S - 使用SpringSecurity3用户验证几点体会(异常信息,验证码)的相关文章

使用SpringSecurity3用户验证(异常信息,验证码)

1. 自定义user-service后,封装自定义异常信息返回 通常情况下,抛UsernameNotFoundException异常信息是捕捉不了,跟踪源码后发现 Java代码   try { user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication); } catch (UsernameNotFoundException notFound) { logger.debug("User '&

NopCommerce源代码分析之用户验证和权限管理

目录 1.  介绍 2.  UML 2.1  实体类UML图 2.2  业务相关UML图 3.  核心代码分析 3.1  实体类源代码 3.2  业务相关源代码 3.3  相关控制器源代码 3.4  相关View源代码 4.  总结 1.  介绍 1.1  nopcommerce介绍 nopcommerce是国外的一个高质量的开源b2c网站系统,基于EntityFramework4.0和MVC3.0,使用Razor模板引擎,有很强的插件机制,包括支付配送功能都是通过插件来实现的. nopcomm

ASP.NET与ASP.NET Core用户验证Cookie并存解决方案

在你将现有的用户登录(Sign In)站点从ASP.NET迁移至ASP.NET Core时,你将面临这样一个问题——如何让ASP.NET与ASP.NET Core用户验证Cookie并存,让ASP.NET应用与ASP.NET Core应用分别使用各自的Cookie?因为ASP.NET用的是FormsAuthentication,ASP.NET Core用的是claims-based authentication,而且它们的加密算法不一样. 我们采取的解决方法是在ASP.NET Core中登录成功

MVC WebApi 用户验证 (2)

构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(66)-MVC WebApi 用户验证 (2) 前言: 构建ASP.NET MVC5+EF6+EasyUI 1.4.3+Unity4.x注入的后台管理系统(65)-MVC WebApi 用户验证 (1) 回顾上一节,我们利用webapi简单的登录并进行了同域访问与跨域访问来获得Token,您可以跳转到上一节下载代码来一起动手. 继续上一篇的文章,我们接下来演示利用拿到的Token来访问接口,管理接

apache的相关设置-给目录或者文件设置用户验证

给目录或者文件设置用户验证 再对应的主机配置文件中加入如下配置: <Directory /data/www/admin.php>    AllowOverride AuthConfig    AuthName "The administrator authentication!"    AuthType Basic    AuthUserFile /data/.htpasswd    require valid-user</Directory> Director

Oracle 用户验证日志

1.sysdba/sysoper 权限用户验证日志;2.非sysdba/sysoper 权限用户验证日志;3.关于sqlcode; 1.sysdba/sysoper 权限用户验证日志:在数据库设置了参数 audit_sys_operations=true 的情况下,系统会根据 audit_trail 参数的设置记录 sysdba/sysoper 权限用户日志到 audit_file_dest 参数设置的目录下,记录日志的内容包括(数据库启动操作.登录验证信息.DML操作),其它非 sysdba/

Java Swing界面编程(23)---事件处理:编写用户验证登录用例

LoginCheck: package com.beyole.util; class LoginCheck {//编写登录验证类 private String userName;//用户名 private String password;//密码 public LoginCheck(String userName,String password)//复写构造方法 { this.userName=userName;//为用户名赋值 this.password=password;//为密码赋值 }

H3C设备console口配置本地用户验证

很简单的一个问题,既然有人提到了,那我就在这里记录一下. <H3C>sys System View: return to User View with Ctrl+Z. [H3C]local-user admin [H3C-luser-admin]password cipher 12345678 Updating user(s) information, please wait.... [H3C-luser-admin]service-type telnet level 3 [H3C-luser

spring4.0整合mongodb3.0.4项目实践(用户验证)

我们的项目用到了spring框架和mongdb数据库,随着mongodb升级到3.0已有半年时间,我们也开始随之升级,但是3.0的用户验证有所更改,导致原来的很多配置无法再用. 经过几天的尝试后,终于成功的用spring配置验证. 升级用了两个新的jar包,分别是pring-data-mongodb1.7.2(http://pan.baidu.com/s/1bnkAA67)和mongodb-java-driver3.0.2(http://pan.baidu.com/s/1jG6bc3c): sp