1. 自定义user-service后,封装自定义异常信息返回
通常情况下,抛UsernameNotFoundException异常信息是捕捉不了,跟踪源码后发现
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
} catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
throw notFound;
}
}
而默认情况下,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
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUserDetailsService">
<!-- 密码加密方式 -->
<password-encoder hash="md5" />
</authentication-provider>
</authentication-manager>
现在修改如下:
<authentication-manager alias="authenticationManager">
<authentication-provider ref="authenticationProvider" />
</authentication-manager>
<b:bean id="authenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<b:property name="userDetailsService" ref="myUserDetailsService" />
<b:property name="hideUserNotFoundExceptions" value="false" />
<b:property name="passwordEncoder" ref="passwordEncoder"></b:property>
</b:bean>
<b:bean
class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"
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的资源文件(英文信息)。如下:
public SpringSecurityMessageSource() {
setBasename("org.springframework.security.messages");
}
通常情况下,这个并不符合我们的使用,并且很多情况下,使用SpringSecurity3自定义抛出的异常信息的话,也会出现不符合语言习惯的信息。
所以,这里是建议覆盖org.springframework.security.core.SpringSecurityMessageSource类,并指定获取应用中的默认国际化资源文件。
不过,你还是不想覆盖别人的类的话,也还可以自己模仿SpringSecurityMessageSource编写自己的获取MessageSourceAccessor的类,例如我就是这么做....
public class SpringMessageSource extends ResourceBundleMessageSource {
// ~ Constructors
// ===================================================================================================
public SpringMessageSource() {
setBasename("com.foo.resources.messages_zh_CN");
}
// ~ Methods
// ========================================================================================================
public static MessageSourceAccessor getAccessor() {
return new MessageSourceAccessor(new SpringMessageSource());
}
}
这样,我们就可以在自定义的userDetailsService类中,像SpringSecurity3那样方便的使用国际化资源文件了。
如:
private MessageSourceAccessor messages = SpringMessageSource.getAccessor();
....
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if (StringUtils.isBlank(username)) {
throw new UsernameNotFoundException(
messages.getMessage("PasswordComparisonAuthenticator.badCredentials"),
username);
}
...
}
3.添加验证码
在实际应用中,其实验证码是少不了的,不然很容易就被暴力破解了。添加验证码起码也可以增加一点安全性,而且添加验证码也比较简单。
添加自定义UsernamePasswordAuthenticationFilter,在验证username和password之前,我们加入验证码的判定。
在spring-security配置文件中的<http>代码块中添加
<custom-filter before="FORM_LOGIN_FILTER" ref="validateCodeAuthenticationFilter" />
然后就是在beans内添加定义validateCodeAuthenticationFilter的bean代码
<b:bean id="validateCodeAuthenticationFilter"
class="com.foo.security.ValidateCodeAuthenticationFilter">
<b:property name="postOnly" value="false"></b:property>
<b:property name="authenticationSuccessHandler" ref="loginLogAuthenticationSuccessHandler"></b:property>
<b:property name="authenticationFailureHandler" ref="simpleUrlAuthenticationFailureHandler"></b:property>
<b:property name="authenticationManager" ref="authenticationManager"></b:property>
</b:bean>
<b:bean id="loginLogAuthenticationSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<b:property name="defaultTargetUrl" value="/index.do"></b:property>
</b:bean>
<b:bean id="simpleUrlAuthenticationFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<b:property name="defaultFailureUrl" value="/login.jsp?login_error=1"></b:property>
</b:bean>
最后是ValidateCodeAuthenticationFilter的源码:
public class ValidateCodeAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
private boolean postOnly = true;
private boolean allowEmptyValidateCode = false;
private String sessionvalidateCodeField = DEFAULT_SESSION_VALIDATE_CODE_FIELD;
private String validateCodeParameter = DEFAULT_VALIDATE_CODE_PARAMETER;
public static final String DEFAULT_SESSION_VALIDATE_CODE_FIELD = "validateCode";
public static final String DEFAULT_VALIDATE_CODE_PARAMETER = "validateCode";
public static final String VALIDATE_CODE_FAILED_MSG_KEY = "validateCode.notEquals";
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: "
+ request.getMethod());
}
String username = StringUtils.trimToEmpty(obtainUsername(request));
String password = obtainPassword(request);
if (password == null) {
password = StringUtils.EMPTY;
}
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Place the last username attempted into HttpSession for views
HttpSession session = request.getSession(false);
if (session != null || getAllowSessionCreation()) {
request.getSession().setAttribute(
SPRING_SECURITY_LAST_USERNAME_KEY,
TextEscapeUtils.escapeEntities(username));
}
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// check validate code
if (!isAllowEmptyValidateCode())
checkValidateCode(request);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
*
* <li>比较session中的验证码和用户输入的验证码是否相等</li>
*
*/
protected void checkValidateCode(HttpServletRequest request) {
String sessionValidateCode = obtainSessionValidateCode(request);
String validateCodeParameter = obtainValidateCodeParameter(request);
if (StringUtils.isEmpty(validateCodeParameter)
|| !sessionValidateCode.equalsIgnoreCase(validateCodeParameter)) {
throw new AuthenticationServiceException(
messages.getMessage(VALIDATE_CODE_FAILED_MSG_KEY));
}
}
private String obtainValidateCodeParameter(HttpServletRequest request) {
return request.getParameter(validateCodeParameter);
}
protected String obtainSessionValidateCode(HttpServletRequest request) {
Object obj = request.getSession()
.getAttribute(sessionvalidateCodeField);
return null == obj ? "" : obj.toString();
}
public boolean isPostOnly() {
return postOnly;
}
@Override
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public String getValidateCodeName() {
return sessionvalidateCodeField;
}
public void setValidateCodeName(String validateCodeName) {
this.sessionvalidateCodeField = validateCodeName;
}
public boolean isAllowEmptyValidateCode() {
return allowEmptyValidateCode;
}
public void setAllowEmptyValidateCode(boolean allowEmptyValidateCode) {
this.allowEmptyValidateCode = allowEmptyValidateCode;
}
}
附件中有生成CODE图片的JSP(相对比较简单的,但基本可以满足应用),还有文章中用到的一些关键配置文件与源码。
生成验证码的jsp页面调用时直接<img src="./validateCode.jsp" />即可,但刷新时,记得在URL上增加随机数的参数,不然会有缓存导致刷新失败。
分享到:
相关推荐
综上所述,`Spring Security图片验证重构终极版`可能是一个涵盖了验证码生成、验证、用户体验优化和安全强化的全面解决方案,其源码分析有助于理解如何在实际项目中高效、安全地实施验证码功能。对于学习`Spring ...
本文将深入探讨如何利用Spring Security 3.2.5版本来构建符合中国式安全需求的应用,主要涉及以下几个关键知识点: 1. **Spring Security概述** Spring Security是Spring生态系统中的一员,它提供了一套全面的...
3. **单点登录(Single Sign-On, SSO)**:单点登录是一种用户只需要一次登录即可访问多个相关系统的身份验证机制。在这个实例中,SSO可能采用了会话管理技术,当用户在一个应用中成功登录后,其身份可以在整个系统...
在本压缩包文件"十九、商城 - 运营商登录-Spring Security(7)5.youlexuan.zip"中,我们关注的核心是Spring Security在电商商城系统中的应用,特别是在运营商登录功能上的实现。Spring Security是一款强大的安全...
3. **集成CAS客户端库**:在Jeecg项目中,你需要引入CAS客户端库,如`spring-security-cas`,这是一个Spring Security扩展,用于支持CAS认证。配置Spring Security XML文件(如`security-context.xml`),设置CAS...
这个"带验证码的登录功能-基于自定义过滤器-demo"项目提供了一个实际的案例,演示了如何在Spring Boot、Spring Security、Mybatis、MySQL和Vue.js集成环境中实现带有验证码的登录功能。以下是对该项目中涉及的技术点...
在Java Web开发中,验证码是一种常见的安全机制,用于防止自动机器人或...此外,随着技术的发展,现在有许多现成的库可以简化验证码的实现,例如使用Spring框架的`spring-security`模块,或者第三方库如` JCaptcha`。
安全方面,以下几点至关重要: - **密码哈希**:使用强哈希函数(如bcrypt、scrypt或Argon2)并添加随机盐,防止彩虹表攻击。 - **防止SQL注入**:确保所有用户输入都经过适当的清理或参数化,防止恶意SQL命令执行...
7. **Ajax技术**:为了提供更好的用户体验,可以使用Ajax异步技术进行登录注册验证,这样用户无需刷新整个页面就能获取反馈信息。 8. **验证码技术**:防止机器人自动注册,通常会在注册页面添加验证码,通过图像、...
3. **数据库管理**:系统可能使用MySQL、Oracle或PostgreSQL等关系型数据库存储用户信息。SQL查询语句用于创建表、插入、更新和删除用户数据,同时可能涉及事务处理和索引优化。 4. **安全机制**:用户登录通常涉及...
除了Spring Security提供的认证和授权机制,还需要注意以下几点: - 密码加密:用户的密码通常需要进行哈希加密存储,防止数据泄露后的直接使用。 - 验证码:防止恶意自动注册或登录,可以添加图形验证码或短信...
3. **验证成功后的操作**:如果验证成功,我们可以创建一个新的Session对象,将用户信息存入Session,然后重定向到受保护的页面。若验证失败,则返回登录页面并显示错误信息。 4. **保护受密码保护的页面**:在受...
SSO(Single Sign-On)单点登录是一种身份验证机制,允许用户在一次登录后,能够访问多个相互关联的应用系统,而无需再次输入凭证。在Java环境下实现SSO,通常涉及以下几个核心知识点: 1. **原理**:SSO的核心思想...
在Web开发中,Java使用`HttpSession`对象来存储用户会话信息,例如用户ID、登录状态等。当用户成功登录后,会在服务器端创建一个包含这些信息的会话。 2. **共享会话状态**:在多应用系统中,要实现单态登录,必须...
7. **异常处理**:在注册过程中可能会遇到各种异常情况,如用户已存在、验证码错误等,Spring提供了一套完整的异常处理机制,可以优雅地处理这些问题。 8. **安全控制**:Spring Security是Spring的一个扩展,用于...
在本项目"一个小的Java登录管理系统"中,我们聚焦于几个关键的技术点,包括验证码的实现、分页处理以及MySQL数据库的使用。这些是构建一个高效、用户友好的Web应用程序的基础。 首先,验证码(CAPTCHA)是防止恶意...
2. **服务器端处理**:后端使用Java编写,通常使用Servlet、JSP或Spring MVC等框架来处理用户的登录请求,验证用户身份。 3. **数据持久化**:用户信息存储在数据库中,如MySQL、Oracle或MongoDB等。Java通过JDBC...
通过在发布过程中添加验证码功能,系统有效地防止了垃圾信息的泛滥,提升了用户体验,同时也减轻了服务器的负担。 在源代码层面,开发者对原系统的安全性进行了加强,这可能包括但不限于以下几个方面: 1. **权限...