SpringSecurity实战笔记之Security
=================================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