SpringSecurity实战笔记之Security

发布时间 2023-08-18 11:02:32作者: 咔咔皮卡丘

=================================Spring Security========================================


一、默认配置


1、默认会对所有请求都需要进行认证与授权;
2、默认使用httpBasic方式进行登录
3、默认的用户名为user,密码在启动应用时在console中有打印
4、自定义配置:

package com.imooc.security.browser;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
* WebSecurity 配置类
*/
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic() //httpBasic登录
// http.formLogin() //表单登录
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
}
}

 

二、基本原理


1、Spring Security 是一组过滤器链,包含以下过滤器
UsernamePasswordAuthenticationFilter
httpBasicAuthenticationFilter
(这两个过滤器两选一,默认是选择httpBasic)
RememberMeAuthenticationFilter
.
.
.
ExceptionTranslationFilter //对下面的拦截抛出的异常,进行对应的处理返回
FilterSecurityInterceptor //最后一道拦截,用户可自定义配置,可以很复杂


2、流程(以Username Password Authentication Filter为例)
-请求首先经过UsernamePasswordAuthenticationFilter,如果请求中有用户名及密码,UsernamePasswordAuthenticationFilter就会处理,没有的话就会放行;
-请求到达了FilterSecurityInterceptor,由于用户未进行认证,被拦截下来并抛出未认证异常;
-异常到达了ExceptionTranslationFilter,ExceptionTranslationFilter根据异常为认证并向前找到认证方式为表单认证,则返回了表单登录页面;
-用户进行表单登录,UsernamePasswordAuthenticationFilter再次判断请求中有用户名及密码,此时有,则处理了,将认证通过信息保存下来;
-请求到达了FilterSecurityInterceptor,发现用户已认证,则放行,请求到达了controller方法中;


三、自定义用户认证逻辑


1、如何处理用户信息获取逻辑
//用户信息通过UserDetailsService接口类获取
package com.imooc.security.browser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

@Component
public class MyUserDetailService implements UserDetailsService{
private Logger logger = LoggerFactory.getLogger(MyUserDetailService.class);

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("用户名:"+username);
return new User(username,"123456", AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}

2、处理用户校验逻辑
2.1、密码是否匹配,只需告诉Spring Security从数据为中读出来的密码是多少就ok了
2.2、用户状态是否正常,是否被冻结了等(UserDetails的实现中)
private final boolean accountNonExpired; //没有过期
private final boolean accountNonLocked; //没有冻结
private final boolean credentialsNonExpired; //密码没有过期
private final boolean enabled; //用户账号是否可用
3、处理密码加密解密
3.1、使用的接口PasswordEncoder(package org.springframework.security.crypto.password;)
3.2、要配置了才会生效
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();//这个类实现了PasswordEncoder接口
}
3.3、添加用户时
用户密码要经过passwordEncode(用户密码);处理后才存入数据库

3.4、用户登录时
直接使用数据库中的密码存入User构造函数中
四、个性化用户认证逻辑
1、自定义登录页面
1.1、需求:如果用户没有登录,请求html页面时,跳转到html登录页面,页面用户可以在配置文件中自定义,登录成功后跳转到原来请求的页面;
请求接口时,返回401状态及提示信息,让前端跳转到其应用的登录页面中,登录成功后,跳转到原来的请求
1.2、默认标准登录页面:在browser模块resources下的resources目录下创建imooc-loginIn.html
1.3、安全配置(先将csrf关闭)
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.httpBasic()
http.formLogin()
.loginPage("/authentication/require") // 登录controller
.loginProcessingUrl("/authentication/form") //登录action_url,默认是/login
.and()
.authorizeRequests()
.antMatchers("/authentication/require","/imooc-loginIn.html").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}

1.4、在browser模块:自定义Controller,在方法内判断是返回登录页面,还是返回401状态码和错误信息
package com.imooc.security.browser;

import com.imooc.security.browser.support.SimpleResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@RestController
public class BrowserSecurityController {

private Logger logger = LoggerFactory.getLogger(BrowserSecurityController.class);

//Spring Security将当前的请求缓存到HttpSessionRequestCache中
private RequestCache requestCache = new HttpSessionRequestCache();

//Spring 跳转工具类
private RedirectStrategy redirectStrategy =new DefaultRedirectStrategy();

/**
* 当需要身份认证时,跳转到这里
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code= HttpStatus.UNAUTHORIZED) //指定返回状态码为401
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {

//获取引用跳转的请求
SavedRequest savedRequest = requestCache.getRequest(request,response);

if(savedRequest!=null){
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引发跳转的请求是:{}",targetUrl);
if(StringUtils.endsWithIgnoreCase(targetUrl,".html")){
redirectStrategy.sendRedirect(request,response,"/imooc-loginIn.html");
}
}
return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页面");
}
}

1.5、用户可以在配置文件中自定义登录页面,以上的"/imooc-loginIn.html"是从配置文件中读取,以下类写在core模块中
需求:读取imooc.security.browser.loginPage=/demo-signIn.html的值
1.5.1、读取配置文件类SecurityProperties
package com.imooc.security.core.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {

private BrowserProperties browser = new BrowserProperties();

public BrowserProperties getBrowser() {
return browser;
}

public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}
1.5.2、Browser对象类,有loginPage属性
package com.imooc.security.core.properties;

public class BrowserProperties {

private String loginPage = "/imooc-loginIn.html";

public String getLoginPage() {
return loginPage;
}

public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}
1.5.3、将读取配置文件类SecurityProperties注册到容器中
package com.imooc.security.core;

import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}

1.5.4、这样SecurityProperties就可以被其它类通过@Autowired注入使用
String loginPage =securityProperties.getBrowser().getLoginPage();


2、自定义登录成功处理(默认是跳转到引发登录的页面或接口)
2.1、继承SavedRequestAwareAuthenticationSuccessHandler类(Spring Security 默认的Success 处理器),同时支持用户自定义是跳转还是返回json(imooc.security.browser.loginType=REDIRECT)
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private ObjectMapper objectMapper;

@Autowired
private SecurityProperties securityProperties;

/**
* 登录成功后会被调用
* @param request
* @param response
* @param authentication //保存着所有的认证信息
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response,Authentication authentication) throws IOException, ServletException {

logger.info("登录成功");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}else {
//如果不是Json就调父类的方法,父类的方法就是跳转
super.onAuthenticationSuccess(request,response,authentication);
}
}
}

2.2、修改安全配置
.successHandler(imoocAuthenticationSuccessHandler)

 

3、自定义登录失败的处理(默认是跳转到登录登录页面)
3.1、继承SimpleUrlAuthenticationFailureHandler类(Spring Security 默认的Failure处理器),同时支持用户自定义是跳转还是返回json(imooc.security.browser.loginType=REDIRECT)
@Component("imoocAuthenticationFailureHandler")
public class ImoocAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired
private ObjectMapper objectMapper;

@Autowired
private SecurityProperties securityProperties;

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {

logger.info("登录失败");

if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}else{
super.onAuthenticationFailure(request,response,e);
}
}
}

3.2、修改安全配置
.failureHandler(imoocAuthenticationFailureHandler)
五、认证流程源码及详解
1、认证处理流程说明
-从UsernamePasswordAuthenticationFilter到AuthenticationManager(不做任务实现);
-AuthenticationManager调用AuthenticationProvider(重头戏),里面用到UsserDetailsService,UserDetails;
-如果存在失败就调用上节课所讲登录失败的方法;
-如果没有异常,就再回到UsernamePasswordAuthenticationFilter,进行密码是否过期判断,通过后将认证标识为true,并将信息保存在Authentication对象中。
2、认证结果如何在多个请求之间共享(session)
2.1、关键类:SecurityContext(包装了Authentication,重写了equals,hasCode方法)、SecurityContextHolder(线程级的变量)、SecurityContextPersistenceFilter
2.2、在调成功处理器之前,AbstractPreAuthenticationProcessingFilter中有SecurityContextHolder.getContext().setAuthentication(authResult);//放入SecurityContext中,其它线程也是可以读取得到的
2.3、SecurityContextPersistenceFilter过滤器位于最前,请求进来时,判断session中SecurityContext是否存在,有就取出来放入线程中,向后传递。没有,直接往下走。请求离开进,判断线程中SecurityContext是否存在,有就将其放入session中。
3、获取认证用户信息
@GetMapping("/me")
public Object getCurrentUser(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication;
}
//或
@GetMapping("/me")
public Object getCurrentUser(Authentication authentication){
return authentication;
}

/**
* 只取用户detail信息
* @param user
* @return
*/
@GetMapping("/me")
public Object getCurrentUser(@AuthenticationPrincipal UserDetails user){
return user;
}
六、图片验证码功能(core模块)
1、生成图片验证码
1.1、根据随机数生成图片
1.2、将随机数存Session中
1.3、将生成的图片写到接口的响应中
1.4、页面代码
<td>图形验证码:</td>
<td>
<input type="text" name="imageCode">
<img src="/code/image" alt="验证码" onclick="this.setAttribute('src','/code/image')">

</td>
1.5、后台代码
package com.imooc.security.core.validate.code;

import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

@RestController
public class ValidateCodeController {

private static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";

private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
//1、根据随机数生成图片
ImageCode imageCode = generate(request);
//2、将随机数存Session中
sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
//3、将生成的图片写到接口的响应中
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
}


/**
* 根据随机数生成图片
* @param request
* @return
*/
private ImageCode generate(HttpServletRequest request) {
int width = 67;
int height = 23;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

Graphics g = image.getGraphics();

Random random = new Random();

g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}

String sRand = "";
for (int i = 0; i < 4; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}

g.dispose();

return new ImageCode(image, sRand, 60);
}

/**
* 生成随机背景条纹
*
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}
2、图形验证码校验
2.1、验证码校验过滤器
/**
* 自定义验证码过滤器,要将其配置到安全配置中
*/
public class ValidateCodeFilter extends OncePerRequestFilter {

private AuthenticationFailureHandler authenticationFailureHandler;

private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

if(StringUtils.equals("/authentication/form",request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(),"post")){
try{
validate(new ServletWebRequest(request));
}catch (ValidateCodeException e){
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
}
}
filterChain.doFilter(request,response);
}

private void validate(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {

ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(servletWebRequest, ValidateCodeController.SESSION_KEY);

String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(),"imageCode");

if(StringUtils.isBlank(codeInRequest)){
throw new ValidateCodeException("验证码的值不能为空");
}
if(codeInSession==null){
throw new ValidateCodeException("验证码不存在");
}
if(codeInSession.isExpired()){
sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("验证码已过期");
}
if(!StringUtils.equals(codeInRequest,codeInSession.getCode())){
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(servletWebRequest,ValidateCodeController.SESSION_KEY);
}

public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}

2.2、拦截器注册
@Override
protected void configure(HttpSecurity http) throws Exception {

//登录验证码过滤器,添加到UsernamePasswordAuthenticationFilter之前
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);

//http.httpBasic()
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/require") // 登录controller
.loginProcessingUrl("/authentication/form") //登录action_url,默认是/login
.successHandler(imoocAuthenticationSuccessHandler)
.failureHandler(imoocAuthenticationFailureHandler)
.and()
.authorizeRequests()
.antMatchers("/authentication/require",securityProperties.getBrowser().getLoginPage(),"/code/image").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
3、重构代码
3.1、验证码基本参数可配置
需求:请求配置(配置值在调用接口时传递)优先于应用级配置(配置值写在imooc-security-demok )优先于默认配置(配置值写在imooc-security-core中)
-(默认配置)将验证码基本参数:长度,高度,位数,有效时间配置在(core模块)ImageCodeProperties类中,将该类作为ValidateCodeProperties属性,ValidateCodeProperties又作为SecurityProperties属性;
-(应用级配置)在应用的Application.properties文件中配置验证码基本参数,如果不配置就读取默认配置
imooc.security.code.image.width=100
imooc.security.code.image.height=20
imooc.security.code.image.length=6
imooc.security.code.image.expireIn=60
-(请求配置)假设允许前端指定验证码图片的长度width,controller接口为/code/image?width=200
controller方法中width的读取方式为(request为ServletWebRequest):
int width = ServletRequestUtils.getIntParameter(request.getRequest(),"width",securityProperties.getCode().getImage().getWidth());

3.2、验证码拦截的接口可配置
-同理在ImageCodeProperties添加urls属性,但不用配置默认值(因为登录的路径会写死在代码中),用于承载应用的Application.properties的配置
imooc.security.code.image.urls=/user/*
-在拦截器中(ValidateCodeFilter)读取配置文件的urls,然后与当前请求进行匹配,如果匹配成功就进行验证码校验,对应代码片段
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String[] configUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrls(), ",");
for(String configUrl : configUrls){
urls.add(configUrl);
}
urls.add("/authentication/form");
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

String requestURI = request.getRequestURI();

boolean action = false;
for(String url:urls){
if(antPathMatcher.match(url,requestURI)){
action=true;
}
}
if(action){
try{
validate(new ServletWebRequest(request));
}catch (ValidateCodeException e){
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;
}
}
filterChain.doFilter(request,response);
}

3.3、验证码的生成逻辑可配置
-将ValidateCodeController类中ImageCode imageCode = generate(request);(//1、根据随机数生成图片) 的方法抽取出来,放在ValidateCodeGenerator接口中,由ImageCodeGenerator类去实现,然后注入
@Autowired
private ValidateCodeGenerator imageCodeGenerator;
再调用
ImageCode imageCode =imageCodeGenerator.generate(new ServletWebRequest(request));
-bean配置:
@Configuration
public class ValidateCodeBeanConfig {

@Autowired
private SecurityProperties securityProperties;

@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator") //这个注解的作用是如果不存在imageCodeGenerator这个Bean时,才使用以下配置(写框架必备知识)
public ValidateCodeGenerator imageCodeGenerator(){
ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
}
七、实现"记住我"(browser模块)
1、记住我功能基本原理
认证成功后,RemeberMeService通过TokenRepository将Token写入数据库,同时也将token写入浏览器Cookie
当用户再次访问时,会经过RememberMeAuthenticationFilter,读取Cookie中的Token,RemeberMeService通过TokenRepository查找数据库中的token,如果有记录,就将记录中的username取出来,再去调UserDetailsService获取用户信息,再将用户信息SecurityContext中
2、记住我功能具体实现
2.1、页面代码
<tr>
<td colspan="2"><input type="checkbox" name="remember-me">记住我</td>
</tr>
2.2、配置
/**
* 记住我Repository
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
//tokenRepository.setCreateTableOnStartup(true);//启动时,自动创建一张表
return tokenRepository;
}
在安全配置中添加
.rememberMe()
.tokenRepository(persistentTokenRepository()) //记住我Repository
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds()) //失效时间
.userDetailsService(userDetailsService) //取到用户信息后,使用的userDetailsService获取用户信息
八、实现短信验证码登录(core)
1、短信验证码的发送
-创建短信验证码对象ValidateCode,具有code及expireTime两个属性,由于图片验证码也具有这两个属性,所以将原有的图片验证码对象继承ValidateCode
-创建短信验证码基本参数配置对象SmsCodeProperties,具有length,expireIn,url属性,由于图片验证码ImageCodeProperties也具有这些属性,所以将ImageCodeProperties继承SmsCodeProperties
-创建短信验证码的生成器SmsCodeGenerator
-发送方法,要作用接口的方式,方便接入不同的短信供应商
-最后,重构校验码发送代码,由于校验码发送的流程都是相同的(生成,保存,发送),所以使用模板模式,将这三个方法合并为VlidateCodeProcessors接口的一个方法,然后这个方法有一个抽象实现类AbstractValidateCodeProcessor,实现了VlidateCodeProcessors的方法,有生成,保存,发送三个方法,由于发送方法不能统一,故有ImageCodeProcessor,SmsCodeSender分别继承AbstractValidateCodeProcessor,分别实现发送方法。
对于controller中调用那个CodeProcessor,以及生成器中调用哪个CodeGenerator,使用了
/**
* 收集系统中所有的{@link ValidateCodeProcessor}接口的实现
*/
@Autowired
private Map<String,ValidateCodeProcessor> validateCodeProcessors;
/**
* 收集系统中所有的{@link ValidateCodeGenerator}接口的实现
*/
@Autowired
private Map<String,ValidateCodeGenerator> validateCodeGenerators;
再根据url中的关键字获取对应的实现类
validateCodeProcessors.get(type+"CodeProcessor").create(new ServletWebRequest(request,response));
ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(type + "CodeGenerator");
2、验证码登录
-SmsCodeAuthenticationToken:参照UsernamePasswordAuthenticationToken创建
-SmsCodeAuthenticationFilter:参照UsernamePasswordAuthenticationFilter创建
-SmsCodeAuthenticationProvider
public class SmsCodeAuthenticationProvider implements AuthenticationProvider{

private UserDetailsService userDetailsService;

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
if(user==null){
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
SmsCodeAuthenticationToken authenticationTokenResult = new SmsCodeAuthenticationToken(user,user.getAuthorities());

authenticationTokenResult.setDetails(authenticationToken.getDetails());

return authenticationTokenResult;
}

@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}

public UserDetailsService getUserDetailsService() {
return userDetailsService;
}

public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
-SmsCodeFilter:参照ValidateCodeFilter创建
-SmsCodeAuthenticationSecurityConfig:将SmsCodeAuthenticationFilter,SmsCodeAuthenticationProvider配置到Spring Security中,然后再将此配置apply到BrowserSecurityConfig中
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity> {

@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;

@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private SecurityProperties securityProperties;

@Override
public void configure(HttpSecurity http) throws Exception {

SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);

SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);

http.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter,UsernamePasswordAuthenticationFilter.class);

}
}
3、代码重构
-将图片验证码过滤器及短信验证码过法器合并为一个公共类
-将配置文件按作用分模块、分类配置,最后通过apply引用,去除重复
-将有两处地方及以上使用到的常量保存在SecurityConstants