springSecurity = 认证(authentication) + 授权(authorization)
🥐 过滤器链
SpringSecurity对sevlet的安全支持底层其实就是基于servlet过滤器
SpringSecurity默认会配置过滤器链,在servlet应用中,过滤器的执行顺序是根据web.xml配置的先后顺序执行的,而Security可以在配置类中配置。
🎉认证(Authentication) 关于认证的几个核心类:
AuthenticationManager
AuthenticationProvider
UsernamePasswordAuthenticationFilter
UsernamePasswordAuthenticationToken
UserDetailsService
UserDetail
认证的流程: 用户发送认证请求,过滤器链执行到: UsernamePasswordAuthenticationFilter,该类继承了AbstractAuthenticationProcessingFilter,拦截请求后,执行dofilter()方法。 注意看源码:这个过滤器是匹配的/login的url,所以只会过滤登录请求,只有访问/login才会触发。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
if (!this.requiresAuthentication(request, response)) {
chain.doFilter(request, response);
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
authResult = this.attemptAuthentication(request, response);
if (authResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authResult, request, response);
} catch (InternalAuthenticationServiceException var8) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
this.unsuccessfulAuthentication(request, response, var8);
return;
} catch (AuthenticationException var9) {
this.unsuccessfulAuthentication(request, response, var9);
return;
}
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
this.successfulAuthentication(request, response, chain, authResult);
}
}
}
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
//过滤该url匹配(看出这个过滤器是过滤登录请求的)
super(new AntPathRequestMatcher("/login", "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//表示认证请求只能够是Post请求
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
//从请求中获取参数username,password
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
//建立一个未认证的UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//记录这个UsernamePasswordAuthenticationToken
this.setDetails(request, authRequest);
//然后将UsernamePasswordAuthenticationToken交给AuthenticationManager管理
return this.getAuthenticationManager().authenticate(authRequest);
}
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return this.usernameParameter;
}
public final String getPasswordParameter() {
return this.passwordParameter;
}
}
//建立一个未认证的UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest
= new UsernamePasswordAuthenticationToken(username, password);
可以看到根据请求参数中的username和password会新建一个Token,看下UsernamePasswordAuthenticationToken的源码:
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 520L;
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;
}
}
通过源码可以看到,UsernamepasswordAuthentication new的Token是未认证的,并且principal,credentials分别对应username,password.继续回到
//记录这个UsernamePasswordAuthenticationToken
this.setDetails(request, authRequest);
该步骤就是记录这个登录请求的未认证Token.
//然后将UsernamePasswordAuthenticationToken交给AuthenticationManager管理
return this.getAuthenticationManager().authenticate(authRequest);
将这个未认证的Token交给当前的AuthenticationManager处理。而manager管理了一系列的AuthenticationProvider。
看看AuthenticationProvider
public interface AuthenticationProvider {
//真正的认证处理,返回一个已经认证类。
Authentication authenticate(Authentication var1) throws AuthenticationException;
//能否处理相应的Token类。
boolean supports(Class<?> var1);
}
下面是AuthenticationManager的一个实现类部分代码,可以看到manager先遍历自身管理的provider,
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
//获取provider集合迭代
Iterator var8 = this.getProviders().iterator();
//遍历provider集合
while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
//判断该provider是否能够处理该Token类型登录请求传递过来的就是我们之前说的UsernamePasswordAuthenticationToken)。
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
//真正的认证逻辑,返回的是一个已认证类,
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (AuthenticationException var14) {
lastException = var14;
}
}
}
provider处理之后,如果成功返回了一个已经认证的..Token,那么就会将这个认证信息记录到session中。 Security登录认证UsernamePassword...Token默认的Provider是DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider. 先看看父类AbstractUserDetailsAuthenticationProvider的关键代码
//认证逻辑
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
//从..Token中获取用户名信息
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//关键代码,根据用户名认证。DaoAuthenticationProvider实现
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
this.preAuthenticationChecks.check(user);
//关键代码:用户名密码认证
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
DaoAuthenticationProvider关键代码:
/**返回了一个UserDetails对象,
* 用户查询认证
**/
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
//userDetailsSerivce(可以类比为UserDao)和userDetail(类比为UserDto)
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 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"));
}
}
}
😊 整个登录认证的过程就梳理完成了,通过阅读源码我们可以知道自己可以自定义登录认证的逻辑: 1.我们只需要编写一个AuthenticationProvider实现类, supports(Class clazz)方法去匹配我们要处理的认证类(即UsernamePasswordAuthenticationToken), authenticate(authentication), 2.在实现类中注入UserDetailService,所以我们可以自定义一个UserDetailService进行注入,UserDetail也相应重写。 3.Security配置类中配置生效该实现类即可实现自定义登录认证。 下面是示例代码🐱🚀: 编写一个AuthenticationProvider实现类:
@Component
@Slf4j
public class MyUserProvider implements AuthenticationProvider {
//注入自定义的UserService(实现UserDetailsService)
@Autowired
private UserService userService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
String pwd = (String) authentication.getCredentials();
UserDetails userDetails = userService.loadUserByUsername(username);
if (!userDetails.isEnabled()){
throw new UsernameNotFoundException("该账户已被停用");
}
if (!passwordEncoder.matches(pwd,userDetails.getPassword())){
throw new UsernameNotFoundException("账户或者密码错误");
}
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, userDetails.getPassword(), userDetails.getAuthorities());
return usernamePasswordAuthenticationToken;
}
@Override
public boolean supports(Class<?> aClass) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
}
}
自定义UserService
@Component
@Slf4j
public class UserService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails userDto = (UserDetails) userDao.findUserDtoByUsername(username);
if (userDto == null){
throw new UsernameNotFoundException("账户不存在");
}
if (!userDto.isAccountNonLocked()){
throw new UsernameNotFoundException("账户被锁定,无法使用");
}
if (userDto.getAuthorities() == null || userDto.getAuthorities().isEmpty()){
log.error("用户:"+userDto.getUsername()+"没有角色可以查询");
throw new UsernameNotFoundException("服务器内部错误");
}
return userDto;
}
}
自定义封装的UseDto,实现UserDetail
@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDto implements UserDetails {
private String flag;
private String status;
private String password;
private String username;
private String role;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
if (role != null && !StringUtils.isEmpty(role))
authorities.add(new SimpleGrantedAuthority(role));
return authorities;
}
@Override
public String getPassword() {
return password ;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 账户是否锁定
* @return
*/
@Override
public boolean isAccountNonLocked() {
return "Y".equals(flag)?true:false;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 账户是否可用
* @return
*/
@Override
public boolean isEnabled() {
return "0".equals(status)?true:false;
}
}
最后在Security配置类中启用生效:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserProvider myUserProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**")
.authenticated()
.and()
.formLogin()
.permitAll()
.successForwardUrl("/login/success");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myUserProvider);
}
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}