记录一下 Spring Secyrity 表单登陆的相关配置, 以及如何添加自定义逻辑。
- 环境配置
项目 GitHub 地址: sign-demo
- 下载项目
- 使用mvn导入相关依赖
- 配置 application.yaml 相关配置
新建 persistent_logins 表, 用作记住密码的记录, 建表语句如下
create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)"
4. 运行 MainApplication
5. 访问 http://localhost:7016/my-login.html
默认用户 admin 默认密码 admin
- 具体实现
简单说一下相关配置及实现
security 配置类
SecurityConfig.java
package com.niu.sign.config;
import com.niu.sign.handler.CustomLogoutSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* security 配置
*
* @author [nza]
* @version 1.0 [2020/10/10 15:59]
* @createTime [2020/10/10 15:59]
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
/**
* URL 白名单
*/
private static final String[] URL_WHITE_LIST = new String[]{"/authentication/require", "/my-login.html", "/code/*"};
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()
// 登录页
.loginPage("/authentication/require")
// 登录处理url
.loginProcessingUrl("/authentication/form")
// 登录成功处理器
.successHandler(customAuthenticationSuccessHandler)
// 登录失败处理器
.failureHandler(customAuthenticationFailureHandler)
.and()
.rememberMe()
// 记住我存储器
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(3600)
.userDetailsService(userDetailsService)
.and()
.authorizeRequests()
// 白名单
.antMatchers(URL_WHITE_LIST)
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable()
.logout()
// 退出登录url
.logoutUrl("/signOut")
// 退出登录成功处理器
.logoutSuccessHandler(new CustomLogoutSuccessHandler("/signOut"))
.deleteCookies("JSESSIONID");
}
@Bean
public PersistentTokenRepository persistentTokenRepository() {
// 记住我存储器
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
由于 Spring Security 的默认登录逻辑没有实现验证码相关的逻辑, 所以需要自己来实现,
这里的思路是在Sping Security 的过滤器链的最前面添加自定义的验证码过滤器。
ValidateCodeFilter.java
package com.niu.sign.validate;
import com.niu.sign.exception.ValidateCodeException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 验证码校验Filter
*
* @author [nza]
* @version 1.0 [2020/10/12 15:18]
* @createTime [2020/10/12 15:18]
*/
@Component("validateCodeFilter")
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
/**
* 验证码校验失败处理器
*/
@Autowired
private AuthenticationFailureHandler customAuthenticationFailureHandler;
/**
* 存放所有需要校验验证码的url
*/
private Map<String, ValidateCodeType> urlMap = new HashMap<>();
/**
* 验证请求url与配置的url是否匹配的工具类
*/
private AntPathMatcher pathMatcher = new AntPathMatcher();
/**
* 系统中的校验码处理器
*/
@Autowired
private ValidateCodeProcessorHolder validateCodeProcessorHolder;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
ValidateCodeType type = getValidateCodeType(request);
if (type != null) {
logger.info("校验请求(" + request.getRequestURI() + ")中的验证码,验证码类型" + type);
try {
validateCodeProcessorHolder.findValidateCodeProcessor(type)
.validate(new ServletWebRequest(request, response));
logger.info("验证码校验通过");
} catch (ValidateCodeException exception) {
customAuthenticationFailureHandler.onAuthenticationFailure(request, response, exception);
return;
}
}
chain.doFilter(request, response);
}
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
// 图片验证码url
urlMap.put("/authentication/form", ValidateCodeType.IMAGE);
// 短信验证码url
urlMap.put("/authentication/mobile", ValidateCodeType.SMS);
}
/**
* 获取校验码的类型,如果当前请求不需要校验,则返回null
*
* @param request 请求
* @return {@link ValidateCodeType}
*/
private ValidateCodeType getValidateCodeType(HttpServletRequest request) {
ValidateCodeType result = null;
if (!StringUtils.equalsIgnoreCase(request.getMethod(), "get")) {
Set<String> urls = urlMap.keySet();
for (String url : urls) {
if (pathMatcher.match(url, request.getRequestURI())) {
result = urlMap.get(url);
}
}
}
return result;
}
}
配置自定义验证码过滤器
ValidateCodeSecurityConfig.java
package com.niu.sign.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
/**
* 验证码过滤器配置类
*
* @author [nza]
* @version 1.0 [2020/10/13 13:39]
* @createTime [2020/10/13 13:39]
*/
@Component("validateCodeSecurityConfig")
public class ValidateCodeSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private Filter validateCodeFilter;
@Override
public void configure(HttpSecurity http) {
// 将自定义Filter添加到过滤器链
http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class);
}
}
- 总结
为 spring security 添加验证码逻辑只需要两步
- 实现自定义验证码过滤器
- 将自定义验证码过滤器配置到过滤器链的最前面即可