SpringSecurity是使用filter来对资源进行保护的,当初始化SpringSecurity时会创建一个 SpringSecurityFilterChain的过滤器链,它实现了servlet的filter,因此外部的请求会经过该链



从上面的流程中我们可以看到几个重要的组件
token是进行认证所需要的凭证,不同的authenticationProvider支持的token不一样,所以不同的认证方式同样对应着不同的token;比如使用账号密码和手机验证码使用的token就不一样
SpringSecurity为我们定义好了authentication的接口
Authentication
public interface Authentication extends Principal, Serializable {
Collection extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

UsernamePasswordAuthenticationToken(框架提供的)
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 530L;
private final Object principal;
private Object credentials;
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
public Object getCredentials() {
return this.credentials;
}
public Object getPrincipal() {
return this.principal;
}
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
}
PhoneCodeAuthenticationToken(自定义token)
public class PhoneCodeAuthenticationToken extends AbstractAuthenticationToken {
List extends GrantedAuthority> authorities;
Object credentials;
Object details;
Object principal;
public PhoneCodeAuthenticationToken(Object principal, Object credentials) {
super((Collection)null);
this.principal = principal;
this.credentials = credentials;
this.setAuthenticated(false);
}
public PhoneCodeAuthenticationToken(Object principal, Object credentials, Collection extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return credentials;
}
@Override
public Object getPrincipal() {
return principal;
}
}
它的作用是查询用户信息,封装成UserDetais的实现类返回,在authenticationProvider中被使用到
官方定义接口
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
UserDetails(框架提供)
public interface UserDetails extends Serializable {
Collection extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
主要包括username、password、authorities三部分,用来表达当前用户的信息,当然我们可以使用User(框架提供)来构建UserDetails实现类也可以自定义UserDetails的实现类来包含更多的信息
该类用来写token的认证逻辑
接口定义
public interface AuthenticationProvider {
//认证逻辑
Authentication authenticate(Authentication var1) throws AuthenticationException;
//支持token类型
boolean supports(Class> var1);
}
public class SMSAuthenticationProvider implements AuthenticationProvider {
public static Map codeMap = new HashMap<>();
private UserDetailsService userDetailsService;
public SMSAuthenticationProvider(UserDetailsService userDetailsService){
this.userDetailsService = userDetailsService;
}
/**
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String phone = (String) authentication.getPrincipal();
String code = (String) authentication.getCredentials();
String cacheCode = codeMap.get(phone);
if(cacheCode == null || !code.equals(cacheCode)){
throw new InternalAuthenticationServiceException(
"code error");
}
SMSUserDetais details = (SMSUserDetais) userDetailsService.loadUserByUsername(phone);
if(details == null){
throw new UsernameNotFoundException("not found this phone");
}
List extends GrantedAuthority> authorities = new ArrayList<>();
return new PhoneCodeAuthenticationToken(details.getUsername(),details.getPassword(),authorities);
}
@Override
public boolean supports(Class> authentication) {
return PhoneCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public void setUserDetailsService(UserDetailsService userDetailsService){
this.userDetailsService = userDetailsService;
}
}
框架本身也提供了一个provider,针对UsernamePasswordAuthenticationToken
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public DaoAuthenticationProvider() {
this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode("userNotFoundPassword");
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}
对符合条件的url进行过滤,框架提供了一个UsernamePasswordAuthenticationFilter处理账号密码登录,下面我们自定义一个处理验证码登录的filter
/**
* 短信登录过滤器
* @author liqi
*/
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SMS_MOBILE = "phone";
public static final String CODE = "code";
public SmsAuthenticationFilter() {
super(new AntPathRequestMatcher("/login/sms", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
String loginName = request.getParameter(SMS_MOBILE);
if (StringUtils.isEmpty(loginName)) {
throw new AuthenticationServiceException("手机号不能为空");
}
String code = request.getParameter(CODE);
if (StringUtils.isEmpty(code)) {
throw new AuthenticationServiceException("手机验证码不能为空");
}
PhoneCodeAuthenticationToken authRequest = new PhoneCodeAuthenticationToken(loginName,code);
authRequest.setDetails(super.authenticationDetailsSource.buildDetails(request));
System.out.println(authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
配置该filter
SecurityConfigurerAdapter securityConfigurerAdapter = new SecurityConfigurerAdapter() {
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
// 手机号+短信登录
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AbstractAuthenticationProcessingFilter smsFilter = new SmsAuthenticationFilter();
smsFilter.setAuthenticationManager(authenticationManager);
smsFilter.setAuthenticationSuccessHandler(successHandler);
smsFilter.setAuthenticationFailureHandler(failureHandler);
httpSecurity.addFilterBefore(smsFilter, UsernamePasswordAuthenticationFilter.class);
}
};
http.apply(securityConfigurerAdapter);
package com.codexie.security.config;
import com.codexie.security.filter.SmsAuthenticationFilter;
import com.codexie.security.provider.SMSAuthenticationProvider;
import com.codexie.security.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UserDetailsServiceImpl userDetailsService;
@Autowired
SuccessHandler successHandler;
@Autowired
DeniedHandler deniedHandler;
@Autowired
FailureHandler failureHandler;
public SecurityConfig() {
}
@Bean
public SMSAuthenticationProvider smsAuthenticationProvider(){
SMSAuthenticationProvider provider = new SMSAuthenticationProvider(userDetailsService);
return provider;
}
/**
* 添加自定义认证方式
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//将自定义的provider添加至list
auth.authenticationProvider(smsAuthenticationProvider())
//设置内置的provider的userDetailsService以及passwordEncoder
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//不定义没有password grant_type
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/public/**", "/webjars/**", "/v2/**", "/swagger**", "/static/**", "/resources/**");
//web.httpFirewall(new DefaultHttpFirewall());//StrictHttpFirewall 去除验url非法验证防火墙
}
@Bean
public PasswordEncoder passwordEncoder(){
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login*").permitAll()
.antMatchers("/login/code").permitAll()
.antMatchers("/logout*").permitAll()
.antMatchers("/druid/**").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login") // 登录页面
.loginProcessingUrl("/login.do") // 登录处理url
.failureUrl("/login?authentication_error=1")
.defaultSuccessUrl("/main")
.usernameParameter("username")
.passwordParameter("password")
.and().logout()
.logoutUrl("/logout.do")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/")
.and().csrf().disable()
.exceptionHandling()
.accessDeniedPage("/login?authorization_error=2");
SecurityConfigurerAdapter securityConfigurerAdapter = new SecurityConfigurerAdapter() {
@Override
public void configure(HttpSecurity httpSecurity) throws Exception {
// 手机号+短信登录
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
AbstractAuthenticationProcessingFilter smsFilter = new SmsAuthenticationFilter();
smsFilter.setAuthenticationManager(authenticationManager);
smsFilter.setAuthenticationSuccessHandler(successHandler);
smsFilter.setAuthenticationFailureHandler(failureHandler);
httpSecurity.addFilterBefore(smsFilter, UsernamePasswordAuthenticationFilter.class);
}
};
http.apply(securityConfigurerAdapter);
}
}